从零开始搭建React项目
# 从零开始搭建React项目
# 1. Webpack基础概念
webpack主要作用:
- 代码转换:TypeScript 编译成 JavaScript、SCSS 编译成 CSS 等。
- 文件优化:压缩 JavaScript、CSS、HTML 代码,压缩合并图片等。
- 代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载。
- 模块合并:在采用模块化的项目里会有很多个模块和文件,需要构建功能把模块分类合并成一个文件。
- 自动刷新:监听本地源代码的变化,自动重新构建、刷新浏览器。
- 代码校验:在代码被提交到仓库前需要校验代码是否符合规范,以及单元测试是否通过。
- 自动发布:更新完代码后,自动构建出线上发布代码并传输给发布系统。
Webpack 有以下几个核心概念:
- Entry:入口,Webpack 执行构建的第一步将从 Entry 开始,可抽象成输入。
- Module:模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。
- Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。
- Loader:模块转换器,用于把模块原内容按照需求转换成新内容。
- Plugin:扩展插件,在 Webpack 构建流程中的特定时机会广播出对应的事件,插件可以监听这些事件的发生,在特定时机做对应的事情。
- Output:输出结果,在 Webpack 经过一系列处理并得出最终想要的代码后输出结果。
构建过程:
- 从 Entry 里配置的 Module 开始递归解析 Entry 依赖的所有 Module。
- 每找到一个 Module, 就会根据配置的 Loader 去找出对应的转换规则。
- 对 Module 进行转换后,再解析出当前 Module 依赖的 Module。
- 这些模块会以 Entry 为单位进行分组,一个 Entry 和其所有依赖的 Module 被分到一个组也就是一个 Chunk。
- 最后 Webpack 会把所有 Chunk 转换成文件输出。
- 在整个流程中 Webpack 会在恰当的时机执行 Plugin 里定义的逻辑。
# 2. 项目需求
使用Webpack 4.x 搭建项目,满足以下需求:
- 使用ES6语言
- 使用React框架
- 自动生成HTML
- webpack-dev-server
- 加载样式(CSS、SCSS)
- 加载静态资源(图片、字体)
- 使用第三方库
- 其他(clean,merge,source map)
# 3. 实战
# 3.1 基础环境
NodeJS版本10.4.1。
初始化生成一个
package.json
文件。npm init -y
1本地安装webpack
webpack4版本命名行相关的功能独立到Webpack-cli。
npm i webpack webpack-cli -D
1新建webpack.config.js 配置文件。
webpack默认会查找 webpack.config.js 作为配置文件。 自定义配置文件名称: webpack --config xx.js
1
2
3
4基础配置
webpack配置采用commonJS规范,通过module.export导出一个描述如何构建的 Object 对象。 module.exports={ entry:{}, //入口配置* output:{}, //出口配置* modules:{}, //module.rules loader plugins:[], //插件 devServer:{} //开发服务器 }
1
2
3
4
5
6
7
8
9
# 3.2 ES6
# 3.2.1 前置步骤
按如下结构创建相应文件与目录:
│ package.json │ webpack.config.js ├─dist │ index.html └─src main.js
1
2
3
4
5
6main.js 内容:
class Ui { constructor(name, age) { this.name = name; this.age = age; } coding() { return `${this.name} is coding`; } } let ui= new Ui('hqz','18'); document.getElementById('app').innerText=ui.coding();
1
2
3
4
5
6
7
8
9
10
11
12
13
14index.html 内容:
<html> <head> <meta charset="UTF-8"> <title>ES6</title> </head> <body> <div id="app"></div> <script src="bundle.js"></script> //手动引入打包后的JS </body> </html>
1
2
3
4
5
6
7
8
9
10webpack配置
const path=require("path"); module.exports={ entry:'./src/main.js', //入口文件 output:{ //出口配置 filename: 'bundle.js', //出口文件名 path: path.resolve(__dirname, 'dist') //出口文件路径 }, module: {}, //module.rules loader plugins:[], //插件 devServer:{} //开发服务器 };
1
2
3
4
5
6
7
8
9
10
11
12
# 3.2.2 配置Babel
目前浏览器对ES6的标准支持不全,所以我们需要把ES6转换成ES5,包含以下两件事:
- 把新的 ES6 语法用 ES5 实现。
- 给新的 API 注入 polyfill。
babel可以方便的完成以上2件事。 babel-preset-env的工作方式类似于babel-preset-latest,但它允许您指定环境并仅转换该环境中缺少的功能。
babel 7.X的主要变更:
- 删除对未维护的 Node 版本的支持:0.10,0.12,2,5
- 删除“Stage” & 年度预设(preset-es2015 等), 用@babel/preset-env 取代。
- 对部分软件包进行重命名(e.g. babel-core-->@babel/core)
简单升级:
利用babel-upgrade
npm i babel-upgrade -g babel-upgrade --write
1
2重新安装包
修改配置文件中的包名
配置:
本地安装Babel
npm i -D @babel/core @babel/preset-env babel-loader @babel/plugin-transform-object-rest-spread @babel/plugin-transform-export-extensions @babel/plugin-transform-class-properties @babel/plugin-syntax-dynamic-import
1
2配置webpack
在module.rules中添加如下: module: { rules: [{ test: /\.js$/, //使用正则匹配所有需要使用babel-loader的文件 use: { loader: "babel-loader", //指明要使用的loader options: { //传入loader的参数 presets: [ //用于解析一组语法特性 [ "@babel/preset-env", //包含当前所有 ECMAScript 标准里的最新特性 { "targets": { //指定需要兼容的浏览器类型和版本 "browsers": [ "> 1%", //支持市场份额超过1%的浏览器。 "ie >= 9" //支持IE9以上的版本 ] } } ] ], plugins: [ //用于解析某个语法特性 "@babel/plugin-proposal-object-rest-spread", //解析对象的扩展运算符(ES2018) "@babel/plugin-proposal-export-default-from", //解析额外的export语法:export v from "xx/xx" "@babel/plugin-proposal-export-namespace-from", //解析额外的export语法:export v as vv from "xx/xx"; "@babel/plugin-proposal-class-properties", //解析class中的静态属性 "@babel/plugin-syntax-dynamic-import" //解析import方法 ] } } }] },
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32执行编译
package.json 的 script字段添加如下: "build": "webpack" 执行 npm run build
1
2
3
4编译完成后,dist文件夹下多出一个bundle.js文件,打包成功。
# 3.3 React
# 3.3.1 前置步骤
本地安装react
npm i -D react react-dom
1mian.js
import React, { Component } from 'react'; import ReactDOM from 'react-dom'; class App extends Component { render() { return <h1>Hello word!</h1> } } ReactDOM.render(<App />, document.getElementById('app'));
1
2
3
4
5
6
7
8
9
# 3.3.2 配置Babel
Babel也可用于解析JSX,需要使用babel-preset-react。
本地安装babel-preset-react
npm i -D @babel/preset-react
1配置webpack (在ES6环境的基础上)
在module.rules中添加如下: module: { rules: [{ test: /\.js$/, use: { loader: "babel-loader", options: { presets: ["@babel/preset-env","@babel/preset-react"] //用于解析ES6+React } } }] },
1
2
3
4
5
6
7
8
9
10
11
12
13
# 3.4 自动生成HTML
# 3.4.1 单个页面
使用html-webpack-plugin 自动生成HTML,并引入相应文件。
将index.html移到src目录下,并重命名:
│ package.json │ webpack.config.js ├─dist └─src main.js template.html
1
2
3
4
5
6template.html 内容:
<html> <head> <meta charset="UTF-8"> <title>自动生成HTML</title> </head> <body> <div id="app"></div> //去掉手动引入script </body> </html>
1
2
3
4
5
6
7
8
9本地安装
npm i -D html-webpack-plugin
1webpack配置
const HtmlWebpackPlugin = require('html-webpack-plugin'); //引入插件 plugins:[ new HtmlWebpackPlugin({ template:'./src/template.html', //html模板 }) ]
1
2
3
4
5
6
7编译
编译后生成的HTML
<html> <head> <meta charset="UTF-8"> <title>自动生成HTML</title> </head> <body> <div id="app"></div> <script type="text/javascript" src="bundle.js"></script> //自动引入JS文件 </body> </html>
1
2
3
4
5
6
7
8
9
10
11
# 3.4.2 多个页面
如果单页应用中需要多个页面入口,或者多页应用时配置多个html时,那么就需要实例化该插件多次。
目录结构要求:
│ package.json │ webpack.config.js │ template.html ├─dist └─src ├─A │ A.js └─B B.js
1
2
3
4
5
6
7
8
9配置webpack
const HtmlWebpackPlugin = require('html-webpack-plugin'); //引入插件 entry: { //多入口使用对象形式配置,chunk名称为key值 A: './src/pages/A/A.js', B: './src/pages/B/B.js', }, output:{ filename:'[name].bundle.js', //[name]代表chunk名称 path: path.resolve(__dirname, 'dist') }, plugins:[ new HtmlWebpackPlugin({ chunks: ['A'], //要引入的chunk filename:'A.html', //生成的文件名 template:'template.html', //模板文件 }), new HtmlWebpackPlugin({ chunks:['B'], filename:'B.html', template:'template.html' }), ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23编译后的目录结构
│ package.json │ webpack.config.js │ template.html ├─dist | A.bundle.js | A.html │ B.bundle.js │ B.html └─src ├─A │ A.js └─B B.js
1
2
3
4
5
6
7
8
9
10
11
12
13
# 3.5 Webpack-dev-server
webpack-dev-server提供了一个简单的服务器,用于访问 webpack 构建好的静态文件,我们日常开发时可以使用它来调试前端代码。 webpack-dev-server将构建好的项目存在内存中。
DevServer 支持模块热替换, 可在不刷新整个网页的情况下实时预览页面。 原理是当一个源码发生变化时,只重新编译发生变化的模块,再用新输出的模块替换掉浏览器中对应的老模块。
# 3.5.1 devServer
本地安装devServer
npm i -D webpack-dev-server
1配置webpack:
const webpack = require('webpack'); plugins: [ new webpack.HotModuleReplacementPlugin() //启用 HMR (webpack 4) ], devServer:{ hot: true, //开启模块热替换 contentBase: './dist', //将dist目录下的文件,作为额外可访问文件 host: '0.0.0.0', //DevServer 服务监听的地址,默认是localhost。当需要同个局域网可访问你的服务时,可设成0.0.0.0 port: 3000, //DevServer 服务监听的端口,默认8080 https: false, //是否使用HTTPS服务 open: true //自动打开网页,地址是host:port }, 只有在通过 DevServer 去启动 Webpack 时配置文件里 devServer 才会生效,因为这些参数所对应的功能都是 DevServer 提供的,Webpack 本身并不认识 devServer 配置项。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16执行编译
package.json 的 script字段添加如下: "start": "webpack-dev-server" 执行 npm start
1
2
3
4
# 3.5.2 open-browser-webpack-plugin
本地安装
npm i -D open-browser-webpack-plugin
1配置webpack
const OpenBrowserPlugin = require('open-browser-webpack-plugin'); plugins: [ new OpenBrowserPlugin({ url: 'http://192.168.1.87:3000/A.html' }) //开启服务后,自动打开的地址 ]
1
2
3
4
5执行 npm start后,会自动打开http://192.168.1.87:3000/A.html页。
# 3.6 加载样式
webpack本身只认得JS文件,其他非JS文件需要用loader进行转换。
# 3.6.1 加载CSS
处理css文件,需要用到以下两个loader:
- css-loader 负责解析 CSS 代码,主要是为了处理 CSS 中的依赖,例如 @import 和 url() 等引用外部文件的声明。
- style-loader 会将 css-loader 解析的结果转变成 JS 代码,运行时动态插入 style 标签来让 CSS 代码生效。
本地安装loader
npm i -D css-loader style-loader
1配置webpack
module: { rules: [ { test: /\.css$/, //使用正则匹配所有需要使用此loader的文件 use: [ //先由css-loader处理后,在交给style-loader处理 'style-loader', { loader:'css-loader', options:{ //传入css-loader的参数 minimize:true, //是否压缩css代码 module:true //是否使用css module } } ] }, ] },
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 3.6.2 加载SCSS
先将SCSS转成CSS,后续处理同上。
本地安装
npm i -D sass-loader node-sass
1配置webpack
module: { rules: [ { test: /\.scss$/, //使用正则匹配所有需要使用此loader的文件 use: ['style-loader','css-loader', 'sass-loader'] //处理顺序:sass-loader->css-loader->style-loader }, ] },
1
2
3
4
5
6
7
8
# 3.7 加载静态资源
# 3.7.1 加载图片&字体
file-loader, url-loader可用于处理图片,字体等静态资源。
url-loader封装了file-loader:
- 文件大小小于limit参数时,url-loader将会把文件转为DataURL。
- 文件大小大于limit,url-loader会调用file-loader进行处理。
本地安装loader
npm i -D file-loader url-loader
1配置webpack
module: { rules: [ { test: /\.(png|svg|jpg|gif)$/, use: [{ loader: 'url-loader', options: { limit: 1024 * 30, //30KB 以下的文件采用 url-loader fallback: 'file-loader', //否则采用 file-loader,默认值就是 file-loader outputPath: 'images', //图片输出路径,相对于output.path } }] }, { test: /\.(eot|ttf|woff|svg)$/, use: [{ loader: 'url-loader', options: { limit: 1024 * 30, //30KB 以下的文件采用 url-loader fallback: 'file-loader', //否则采用 file-loader,默认值就是 file-loader outputPath: 'fonts', //字体输出路径 } }] }, ] },
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26编译后:
- 大于30KB的资源,用file-loader处理,复制到dist/images目录下。
- 小于30KB的资源,和js一起打包,形成dataURL形式。
# 3.7.2 copy-webpack-plugin
将不需要webpack处理的静态资源(e.g. favicon),原样输出到指定目录下。
本地安装
npm i copy-webpack-plugin -D
1配置webpack
const CopyWebpackPlugin = require('copy-webpack-plugin'); plugins:[ new CopyWebpackPlugin([{ from: './src/assets/public', // 将此目录下的文件 to:'./public' // 输出到此目录,相对于output.path目录 }]) ]
1
2
3
4
5
6
7
8编译后 src/assets/public 下的文件将原封不动的输出到 dist/public 目录下。
# 3.8 第三方库
通过ProvidePlugin引用某些模块作为应用运行时的变量,从而不必每次都用 require
或者 import
, 是内置的插件。
安装jquery
npm i -D jquery
1配置webpack
plugins:[ new webpack.ProvidePlugin({ $: 'jquery', }) ]
1
2
3
4
5
6在JS文件中就可直接使用jquery,不用导入。
# 3.9 其他
# 3.9.1 clean-webpack-plugin
webpack打包的文件都放在dist文件夹下,但webpack无法追踪到哪些文件是实际项目中用到的。所以建议在每次构建前都清理下dist文件夹。
本地安装插件
npm i -D clean-webpack-plugin
1配置webpack
const CleanWebpackPlugin=require('clean-webpack-plugin'); new CleanWebpackPlugin(['dist'])
1
2
3
# 3.9.2 Webpack-merge
实际项目开发中,一般会有三份配置文件:
- webpack.dev.js 开发环境:devserver配置,生成SourceMap
- webpack.prod.js 生产环境:压缩代码
- webpack.common.js 公共配置
可使用webpack-merge合并配置。
本地安装
npm i -D webpack-merge
1拆分webpack配置
webpack.prod.js: const merge = require("webpack-merge"); const common = require("./webpack.common.js"); //引入公共配置 const CleanWebpackPlugin = require("clean-webpack-plugin"); module.exports = merge(common, { //合并配置 plugins: [ new CleanWebpackPlugin(["dist"]) ], });
1
2
3
4
5
6
7
8
9
10
11修改package.json的script字段
"build": "webpack --config webpack.prod.js", "start": "webpack-dev-server --config webpack.dev.js"
1
2
# 3.9.3 source map
React, ES6等经过webpack转换后,代码可读性非常差,不利于在浏览器中调试代码。可通过加载 Source Map 文件,在浏览器中调试源码。
各种source map的差异见:https://github.com/webpack/webpack/tree/master/examples/source-map
devtool: "cheap-module-eval-source-map" //开发环境
# 4. 优化
# 4.1 模式
我们在日常的前端开发工作中,一般都会有开发&生产两套构建环境。
webpack 4.X新增用mode字段指定当前环境,并启用相应模式下的webpack内置的优化。
module.exports = {
mode: 'production'
};
2
3
选项 | 描述 |
---|---|
development (开发环境) | process.env.NODE_ENV =development 并启用以下插件: NamedChunksPlugin , NamedModulesPlugin |
production (生产环境) | process.env.NODE_ENV =production 并启用以下插件: FlagDependencyUsagePlugin , FlagIncludedChunksPlugin , ModuleConcatenationPlugin , NoEmitOnErrorsPlugin , OccurrenceOrderPlugin , SideEffectsFlagPlugin , UglifyJsPlugin |
可通过optimization字段,手动配置或覆盖mode配置。
# 4.2 代码压缩
# 4.2.1 压缩JS文件
将mode设置成production,在此模式下optimization.minimize默认为true, webpack 会自动调用UglifyjsWebpackPlugin压缩JS代码。
若不需要压缩,可将optimization.minimize设置为false。
配置webpack.prod.js
mode: "production"
1optimization: { // minimize:false, //若不需要进行压缩,可使用此句禁用 // minimizer: [ //用于指定使用的压缩插件或自定义UglifyjsWebpackPlugin插件配置,覆盖默认配置。 // new UglifyJsPlugin({ /* your config */ }) // ] },
1
2
3
4
5
6
# 4.2.2 压缩CSS文件
3.7章节介绍了如何加载样式,但加载后的样式是写在JS文件中的。随着项目越来越大,js文件也会越来越大,所以,我们就需要对css文件进行分离并压缩。
本地安装插件
npm i -D mini-css-extract-plugin optimize-css-assets-webpack-plugin uglifyjs-webpack-plugin
1配置webpack:分离css文件
webpack.common.js: const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = { plugins: [ new MiniCssExtractPlugin({ filename: "[name].css", // [name]为chunk名称 }) ], module: { rules: [ { test: /\.(sc|c)ss$/, use: [ MiniCssExtractPlugin.loader, // style-loader改用MiniCssExtractPlugin 'css-loader', 'sass-loader', ], } ] } } 注:在Webpack4上用extract-text-webpack-plugin会出错,可以安装beta版本extract-text-webpack-plugin@next。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24配置webpack:压缩css文件
webpack.prod.js: const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"); //用于压缩CSS代码 const UglifyJsPlugin = require("uglifyjs-webpack-plugin"); //用于压缩JS代码 optimization: { minimizer: [ new UglifyJsPlugin({}), new OptimizeCSSAssetsPlugin({}) //压缩css ] },
1
2
3
4
5
6
7
8
9
10
# 4.2.3 压缩HTML文件
HtmlWebpackPlugin支持压缩输出的HTML文件。
plugins:[
new HtmlWebpackPlugin({
template:'src/index.html',
minify:{ //压缩输出
collapseWhitespace:true //折叠空白区域
minifyCSS: true, // 压缩 HTML 中出现的 CSS 代码
minifyJS: true // 压缩 HTML 中出现的 JS 代码
}
})
]
2
3
4
5
6
7
8
9
10
# 4.2.4 压缩图片
本地安装
npm i -D image-webpack-loader
1配置webpack
{ test: /\.(png|svg|jpg|gif)$/, use: [ { loader: 'url-loader', options: { limit: 1024 * 30, fallback: 'file-loader', outputPath: 'images', } }, 'image-webpack-loader' // 压缩图片 ] },
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 4.3 Tree Sharing
Tree Shaking 可以用来剔除 JavaScript 中用不上的代码。
Tree Shaking要求:
- 必须遵循 ES6 的模块规范(即 import 和 export)。
- 在项目
package.json
文件中,添加一个 "sideEffects" 属性。作用于引入重导出的模块。 - 引入一个能够删除未引用代码(dead code)的压缩工具(minifier)(例如
UglifyJSPlugin
)。
新建util.js
export funcA from './TESTA' export funcB from './TESTB' TESTA.js: import React from 'react'; export default function funcA() { return <h1>I'm funcA</h1>; } console.log("funcAfuncAfuncA") // 副作用代码 TESTB.js: import React from 'react'; export default function funcB() { return <h1>I'm funcB</h1>; } console.log("funcBfuncBfuncB") // 副作用代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16在A.js中引用util.funcA,则funcB就是用不上的代码。
import {funcA} from '../util';
1修改babel-loader配置。
presets: [ [ "env", {modules: false} // 关闭Babel的模块转换功能,保留原本的ES6模块化语法 ], "react" ]
1
2
3
4
5
6
7package.json添加sideEffects字段: 将文件标记为无副作用
"sideEffects": ["*.css","*.scss"] //避免样式文件被删除 注:此字段是webpack4新增的。
1
2
3开启压缩,参照4.2.1
执行编译后,A.bundle.js无funcB相关代码。
# 4.4 提取公共代码
提取公共代码的原理:用户第一次访问页面后,页面公共代码的文件已经被浏览器缓存起来。用户切换到其它页面时,存放公共代码的文件就不会再重新加载,而是直接从缓存中获取。 加快了其他页面的访问速度,减少了网络传输流量。
3种拆分方案浏览器的加载情况:
- 未拆分代码:所有代码被打包到一个JS文件中,当修改了代码,浏览器就得重新加载所有代码。
- 拆分公共代码:代码被打包成两个JS文件(公共代码 & 其余代码),修改其中一个文件的代码,浏览器只需要重新加载修改的代码文件。但第三方库的代码也属于公共代码,若修改了非第三方库的公共代码,那么浏览器也会重新加载不常变更的第三方库代码。
- 拆分公共代码&第三方库:代码被打包成3个以上的JS文件。
mode=production时,webpack 4.X 会默认对代码进行拆分,拆分的规则是:
- 模块被重复引用,或者是来自
node_modules
中的模块。 - 文件大于30kb。
- 在按需加载时,请求数量小于等于5。
- 在初始化加载时,请求数量小于等于3。
注:webpack 4.x 通过optimization.splitChunks配置, webpack 4.x之前可用CommonsChunkPlugin。
默认配置:
optimization: {
splitChunks: {
chunks: "async", //异步加载的模块
minSize: 30000, //模块大于30k会被抽离到公共模块
minChunks: 1, //被引用超过1次
maxAsyncRequests: 5, //异步模块,一次最多只能被加载5个
maxInitialRequests: 3, //入口模块最多只能加载3个
},
},
}
2
3
4
5
6
7
8
9
10
拆分公共代码配置:
optimization: {
splitChunks: {
chunks: "all", // 所有的 chunks 代码公共的部分分离出来成为一个单独的文件
},
},
}
2
3
4
5
6
拆分公共代码&第三方库配置:
- 根据所使用的技术栈,找出所有页面都需要用到的基础库。如react、react-dom 等库,把它们提取到一个单独vendor.js文件。
- 在剔除了各个页面中被 vendor.js 包含的部分代码外,再找出所有页面都依赖的公共部分的代码提取出来放到 common.js中去。
- 再为每个页面都生成一个单独的文件,这个文件中不再包含 vendor.js 和 common.js中包含的部分,而只包含各个页面单独需要的部分代码。
optimization: {
splitChunks: {
cacheGroups: { //缓存组
commons: { //提取入口文件之间的公共代码
chunks: 'all', //块的范围,有三个可选值:initial、async、all,默认为all
minChunks: 2, //被引用次数
minSize: 0, //文件大小
name: "common" //拆分出来块的名字
},
vendor: {
chunks: "all",
test: /node_modules/,//控制哪些模块被这个缓存组匹配到
name: "vendor",
priority: 10,
},
},
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
new HtmlWebpackPlugin({
chunks: ['A','common','vendor'], //引入拆分出来的chunk
filename:'A.html',
template:'template.html',
minify:{
collapseWhitespace:true
}
}),
2
3
4
5
6
7
8
编译完成后dist目录结构如下:
│ A.bundle.js
│ A.css
│ A.html
│ B.bundle.js
│ B.css
│ B.html
│ common.bundle.js //A和B之间的公共代码
│ vendor.bundle.js //node_modules下的第三方依赖代码
├─images
│ 230280f0ff880c8273b99fdae150dc96.png
└─public
ico.png
2
3
4
5
6
7
8
9
10
11
12
# 4.5 优化loader配置
使用 Loader 时可以通过 test 、 include、 exclude 三个配置项来命中 Loader 要应用规则的文件。 为了尽可能少的让文件被 Loader 处理,可以通过 include && exclude 去命中/排除文件。
示例配置:
babel-loader:
exclude: path.resolve(__dirname, 'node_modules')
url-loader:
include: path.resolve(__dirname, 'src/assets')
2
3
4
5
# 4.6 Resolve
Webpack 在启动后会从配置的入口模块出发找出所有依赖的模块,Resolve 配置 Webpack 如何寻找模块所对应的文件。
模块引入方式:
import * as m from './index.css' // 相对路径
import React from 'react' // 模块名
2
- 解析相对路径
- 查找相对当前模块的路径下是否有对应文件或文件夹
- 是文件则直接加载
- 是文件夹则继续查找文件夹下的 package.json 文件
- 有 package.json 文件则按照文件中 browser/module/main 字段的文件名来查找文件 (配置项:resolve.mainFields )
- 无 package.json 或者无 browser/module/main字段则查找 index.js 文件 (配置项:resolve.mainFiles)
- 解析模块名
- 查找当前文件目录下,父级目录及以上目录下的 node_modules 文件夹,看是否有对应名称的模块 (配置项:resolve.modules)
# 4.6.1 resolve.modules
resolve.modules 用于配置 Webpack 去哪些目录下寻找第三方模块。
默认值是 ['node_modules'],作用:
- 先去当前目录下的node_modules目录下查找第三方模块。
- 如果没找到,就去上级目录的node_modules目录下查找。
- 还是没找到,再去上级的node_modules目录下查找。
- 直到根目录。
通常安装的第三方模块都放在项目根目录下的 node_modules 目录下,就没有必要按照默认的方式去一层层的寻找,可以指明存放第三方模块的绝对路径,以减少寻找。
配置如下:
module.exports = {
resolve: {
// 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤
modules: [path.resolve(__dirname, 'node_modules')]
},
};
2
3
4
5
6
# 4.6.2 resolve.extensions
resolve.extensions 用于配置在尝试过程中用到的后缀列表,默认是:
extensions: ['.js', '.json']
如果这个列表越长,或者正确的后缀在越后面,就会造成尝试的次数越多。
建议:
- 后缀尝试列表要尽可能的小,不要把项目中不可能存在的情况写到后缀尝试列表中。
- 频率出现最高的文件后缀要优先放在最前面,以做到尽快的退出寻找过程。
- 在源码中写导入语句时,要尽可能的带上后缀,从而可以避免寻找过程。例如在你确定的情况下把
require('./data')
写成require('./data.json')
。
相关 Webpack 配置如下:
module.exports = {
resolve: {
// 尽可能的减少后缀尝试的可能性
extensions: ['.js'],
},
};
2
3
4
5
6
# 4.6.3 resolve.alias
resolve.alias
配置项通过别名来把原导入路径映射成一个新的导入路径,可简化import/require路径。
resolve:{
alias:{
'assets': path.resolve(__dirname, 'src','assets') //把导入语句里的 assets 关键字替换成 根目录/src/assets/
}
}
2
3
4
5
import img from 'assets/images/1.png'
==> import img from 'xx/src/assets/images/1.png'
。
css中的url不支持。
解决在webstorm中无法解析alias后的路径:
- 在webpack settings中指定webpack配置文件的路径。
- 由于我们脚手架的webpack配置中resolve.alias配置是直接写在alias字段下的,webstorm无法解析。所以需要在配置文件中配置resolve.alias字段。
# 4.7 Hash
在打包出来的文件名上加上文件内容的hash
是目前最常见的有效使用浏览器长缓存的方法,js文件如果有内容更新,hash
就会更新,浏览器请求路径变化所以更新缓存,如果js内容不变,hash
不变,直接用缓存。
hash类型:
- hash 跟整个项目的构建相关,只要项目里有文件更改,整个项目构建的hash值都会更改,并且全部文件都共用相同的hash值。
- chunkhash 和hash不一样,它根据不同的入口文件(Entry)进行依赖文件解析、构建对应的chunk,生成对应的哈希值。
- contenthash 更细致地根据内容更改,生成对应的哈希值。为css文件生成独立的hash,让css文件不受js文件的影响。
webpack配置:
output:{
filename:'[name].[chunkhash:8].bundle.js',
path: path.resolve(__dirname, 'dist')
},
plugins:[
new MiniCssExtractPlugin({
filename: "[name].[contenthash:8].css",
}),
],
2
3
4
5
6
7
8
9
# 4.8 遗留
# 4.8.1 热更新 (react-hot-loader)
安装
yarn add react-hot-loader
1webpack配置
1.使用HotModuleReplacementPlugin插件 plugins: [ new webpack.HotModuleReplacementPlugin(), ], 2.开启devServer的模块热替换 devServer:{ hot: true, //开启模块热替换 }, 3.在babel-loader中使用react-hot-loader/babel插件 plugins: ["react-hot-loader/babel"
1
2
3
4
5
6
7
8
9
10# 入口文件设置
import React, { Component } from 'react'; import { AppContainer } from 'react-hot-loader'; import ReactDOM from 'react-dom'; require('./a.css'); import Test from './Test'; function render(RootElement) { ReactDOM.render( <AppContainer> <RootElement /> </AppContainer>, document.getElementById('app') ); } render(Test); if (module.hot) { module.hot.accept('./Test', () => { render(Test); }); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 4.8.2 Proxy
proxy 用于配置 webpack-dev-server 将特定 URL 的请求代理到另外一台服务器上。例如:
proxy: {
'/api/test': {
target: "http://localhost:3000", // 将URL中带有/api/test的请求代理到本地的3000端口的服务上
pathRewrite: { '^/api': '' }, // 把URL中path部分的api移除掉
},
}
2
3
4
5
6
- 请求到
/api/test
会被代理到请求http://localhost:3000/api/users
。 - 请求到
/api/test
会被代理到请求http://localhost:3000/users
。