@zhouyy
2018-04-12T01:10:53.000000Z
字数 18579
阅读 560
webpack
entry是配置模块的入口,可抽象成输入,Webpack 执行构建的第一步将从入口开始搜寻及递归解析出所有入口依赖的模块。
entry 配置是必填的,若不填则将导致 Webpack 报错退出。
Webpack 在寻找相对路径的文件时会以 context 为根目录,context 默认为执行启动 Webpack 时所在的当前工作目录。 如果想改变 context 的默认配置,则可以在配置文件里这样设置它:
module.exports = {context: path.resolve(__dirname, 'app')}
注意, context 必须是一个绝对路径的字符串。 除此之外,还可以通过在启动 Webpack 时带上参数 webpack --context 来设置 context。
之所以在这里先介绍 context,是因为 Entry 的路径和其依赖的模块的路径可能采用相对于 context 的路径来描述,context 会影响到这些相对路径所指向的真实文件。
Entry 类型可以是以下三种中的一种或者相互组合:
| 类型 | 例子 | 含义 |
|---|---|---|
| string | './app/entry' | 入口模块的文件路径,可以是相对路径。 |
| array | ['./app/entry1', './app/entry2'] | 入口模块的文件路径,可以是相对路径。 |
| object | { a: './app/entry-a', b: ['./app/entry-b1', './app/entry-b2']} | 配置多个入口,每个入口生成一个 Chunk |
如果是 array 类型,则搭配 output.library 配置项使用时,只有数组里的最后一个入口文件的模块会被导出。
Webpack 会为每个生成的 Chunk 取一个名称,Chunk 的名称和 Entry 的配置有关:
假如项目里有多个页面需要为每个页面的入口配置一个 Entry ,但这些页面的数量可能会不断增长,则这时 Entry 的配置会受到到其他因素的影响导致不能写成静态的值。其解决方法是把 Entry 设置成一个函数去动态返回上面所说的配置,代码如下:
// 同步函数entry: () => {return {a:'./pages/a',b:'./pages/b',}};// 异步函数entry: () => {return new Promise((resolve)=>{resolve({a:'./pages/a',b:'./pages/b',});});};
output 配置如何输出最终想要的代码。output 是一个 object,里面包含一系列配置项.
output.filename 配置输出文件的名称,为string 类型。 如果只有一个输出文件,则可以把它写成静态不变的:
filename: 'bundle.js'
但是在有多个 Chunk 要输出时,就需要借助模版和变量了。前面说到 Webpack 会为每个 Chunk取一个名称,可以根据 Chunk 的名称来区分输出的文件名:
filename: '[name].js'
代码里的 [name] 代表用内置的 name 变量去替换[name],这时你可以把它看作一个字符串模块函数, 每个要输出的 Chunk 都会通过这个函数去拼接出输出的文件名称。
内置变量除了 name 还包括:
| 量名 | 含义 |
|---|---|
| id | Chunk 的唯一标识,从0开始 |
| name | Chunk 的名称 |
| hash | Chunk 的唯一标识的 Hash 值 |
| chunkhash | Chunk 内容的 Hash 值 |
其中 hash 和 chunkhash 的长度是可指定的,[hash:8] 代表取8位 Hash 值,默认是20位。
注意: ExtractTextWebpackPlugin 插件是使用 contenthash 来代表哈希值而不是 chunkhash, 原因在于 ExtractTextWebpackPlugin 提取出来的内容是代码内容本身而不是由一组模块组成的 Chunk。
output.chunkFilename 配置无入口的 Chunk 在输出时的文件名称。 chunkFilename 和上面的 filename 非常类似,但 chunkFilename 只用于指定在运行过程中生成的 Chunk 在输出时的文件名称。 常见的会在运行时生成 Chunk 场景有在使用 CommonChunkPlugin、使用 import('path/to/module') 动态加载等时。 chunkFilename 支持和 filename 一致的内置变量。
output.path 配置输出文件存放在本地的目录,必须是 string 类型的绝对路径。通常通过 Node.js 的 path 模块去获取绝对路径:
path: path.resolve(__dirname, 'dist_[hash]')
在复杂的项目里可能会有一些构建出的资源需要异步加载,加载这些异步资源需要对应的 URL 地址。
output.publicPath 配置发布到线上资源的 URL 前缀,为string 类型。 默认值是空字符串 '',即使用相对路径。
这样说可能有点抽象,举个例子,需要把构建出的资源文件上传到 CDN 服务上,以利于加快页面的打开速度。配置代码如下:
filename:'[name]_[chunkhash:8].js'publicPath: 'https://cdn.example.com/assets/'这时发布到线上的 HTML 在引入 JavaScript 文件时就需要:<script src='https://cdn.example.com/assets/a_12345678.js'></script>使用该配置项时要小心,稍有不慎将导致资源加载404错误。output.path 和 output.publicPath 都支持字符串模版,内置变量只有一个:hash 代表一次编译操作的 Hash 值。
Webpack 输出的部分代码块可能需要异步加载,而异步加载是通过 JSONP 方式实现的。 JSONP 的原理是动态地向 HTML 中插入一个 <script src="url"></script>标签去加载异步资源。 output.crossOriginLoading 则是用于配置这个异步插入的标签的 crossorigin 值。
jsonp可用于解决主流浏览器的跨域数据访问的问题,其中的原理是利用元素的跨域能力.web页面上凡是拥有
“src”这个属性的标签都拥有跨域的能力,比如<script>,<img>
JSONP,JSON width Padding,采用json 作为传输数据的载体,具体的实现通过script标签跨域应用来完成。
script 标签的 crossorigin 属性可以取以下值:
当用 Webpack 去构建一个可以被其他模块导入使用的库时需要用到它们。
它们通常搭配在一起使用。
output.libraryTarget 是字符串的枚举类型,支持以下配置。
编写的库将通过 var 被赋值给通过 library 指定名称的变量。
假如配置了 output.library='LibraryName',则输出和使用的代码如下:
// Webpack 输出的代码var LibraryName = lib_code;// 使用库的方法LibraryName.doSomething();
假如 output.library 为空,则将直接输出:
lib_code
其中 lib_code 代指导出库的代码内容,是有返回值的一个自执行函数。
编写的库将通过 CommonJS 规范导出。
假如配置了 output.library='LibraryName',则输出和使用的代码如下:
// Webpack 输出的代码exports['LibraryName'] = lib_code;// 使用库的方法require('library-name-in-npm')['LibraryName'].doSomething();
其中 library-name-in-npm 是指模块发布到 Npm 代码仓库时的名称。
编写的库将通过 CommonJS2 规范导出,输出和使用的代码如下:
// Webpack 输出的代码module.exports = lib_code;// 使用库的方法require('library-name-in-npm').doSomething();
CommonJS2 和 CommonJS 规范很相似,差别在于 CommonJS 只能用 exports 导出,而 CommonJS2 在 CommonJS 的基础上增加了 module.exports 的导出方式。
在 output.libraryTarget 为 commonjs2 时,配置 output.library 将没有意义。
编写的库将通过 this 被赋值给通过 library 指定的名称,输出和使用的代码如下:
// Webpack 输出的代码this['LibraryName'] = lib_code;// 使用库的方法this.LibraryName.doSomething();
编写的库将通过 window 被赋值给通过 library 指定的名称,即把库挂载到 window 上,输出和使用的代码如下:
// Webpack 输出的代码window['LibraryName'] = lib_code;// 使用库的方法window.LibraryName.doSomething();
编写的库将通过 global 被赋值给通过 library 指定的名称,即把库挂载到 global 上,输出和使用的代码如下:
// Webpack 输出的代码global['LibraryName'] = lib_code;// 使用库的方法global.LibraryName.doSomething();
output.libraryExport 配置要导出的模块中哪些子模块需要被导出。 它只有在 output.libraryTarget 被设置成 commonjs 或者 commonjs2 时使用才有意义。
假如要导出的模块源代码是:
export const a=1;export default b=2;
现在你想让构建输出的代码只导出其中的 a,可以把 output.libraryExport 设置成 a,那么构建输出的代码和使用方法将变成如下:
// Webpack 输出的代码module.exports = lib_code['a'];// 使用库的方法require('library-name-in-npm')===1;
module 配置如何处理模块。
rules 配置模块的读取和解析规则,通常用来配置 Loader。其类型是一个数组,数组里每一项都描述了如何去处理部分文件。 配置一项 rules 时大致通过以下方式:
test 、 include 、 exclude 三个配置项来命中 Loader 要应用规则的文件。use 配置项来应用 Loader,可以只应用一个 Loader 或者按照从后往前的顺序应用一组 Loader,同时还可以分别给 Loader 传入参数。从右到左执行,通过 enforce 选项可以让其中一个 Loader 的执行顺序放到最前或者最后。
module: {rules: [{// 命中 JavaScript 文件test: /\.js$/,// 用 babel-loader 转换 JavaScript 文件// ?cacheDirectory 表示传给 babel-loader 的参数,用于缓存 babel 编译结果加快重新编译速度use: ['babel-loader?cacheDirectory'],// 只命中src目录里的js文件,加快 Webpack 搜索速度include: path.resolve(__dirname, 'src')},{// 命中 SCSS 文件test: /\.scss$/,// 使用一组 Loader 去处理 SCSS 文件。// 处理顺序为从后到前,即先交给 sass-loader 处理,再把结果交给 css-loader 最后再给 style-loader。use: ['style-loader', 'css-loader', 'sass-loader'],// 排除 node_modules 目录下的文件exclude: path.resolve(__dirname, 'node_modules'),},{// 对非文本文件采用 file-loader 加载test: /\.(gif|png|jpe?g|eot|woff|ttf|svg|pdf)$/,use: ['file-loader'],},]}
在 Loader 需要传入很多参数时,你还可以通过一个 Object 来描述,例如在上面的 babel-loader 配置中有如下代码:
use: [{loader:'babel-loader',options:{cacheDirectory:true,},// enforce:'post' 的含义是把该 Loader 的执行顺序放到最后// enforce 的值还可以是 pre,代表把 Loader 的执行顺序放到最前面enforce:'post'},// 省略其它 Loader]
上面的例子中 test include exclude 这三个命中文件的配置项只传入了一个字符串或正则,其实它们还都支持数组类型,使用如下:
{test:[/\.jsx?$/,/\.tsx?$/],include:[path.resolve(__dirname, 'src'),path.resolve(__dirname, 'tests'),],exclude:[path.resolve(__dirname, 'node_modules'),path.resolve(__dirname, 'bower_modules'),]}
数组里的每项之间是或的关系,即文件路径符合数组中的任何一个条件就会被命中。
noParse 配置项可以让 Webpack 忽略对部分没采用模块化的文件的递归解析和处理,这样做的好处是能提高构建性能。 原因是一些库例如 jQuery 、ChartJS 它们庞大又没有采用模块化标准,让 Webpack 去解析这些文件耗时又没有意义。
noParse 是可选配置项,类型需要是 RegExp、[RegExp]、function 其中一个。
例如想要忽略掉 jQuery 、ChartJS,可以使用如下代码:
// 使用正则表达式noParse: /jquery|chartjs/// 使用函数,从 Webpack 3.0.0 开始支持noParse: (content)=> {// content 代表一个模块的文件路径// 返回 true or falsereturn /jquery|chartjs/.test(content);}
注意被忽略掉的文件里不应该包含 import 、 require 、 define 等模块化语句,不然会导致构建出的代码中包含无法在浏览器环境下执行的模块化语句。
因为 Webpack 是以模块化的 JavaScript 文件为入口,所以内置了对模块化 JavaScript 的解析功能,支持 AMD、CommonJS、SystemJS、ES6。 parser 属性可以更细粒度粗体文本的配置哪些模块语法要解析哪些不解析,和 noParse 配置项的区别在于 parser 可以精确到语法层面, 而 noParse 只能控制哪些文件不被解析。 parser 使用如下:
module: {rules: [{test: /\.js$/,use: ['babel-loader'],parser: {amd: false, // 禁用 AMDcommonjs: false, // 禁用 CommonJSsystem: false, // 禁用 SystemJSharmony: false, // 禁用 ES6 import/exportrequireInclude: false, // 禁用 require.includerequireEnsure: false, // 禁用 require.ensurerequireContext: false, // 禁用 require.contextbrowserify: false, // 禁用 browserifyrequireJs: false, // 禁用 requirejs}},]}
Webpack 在启动后会从配置的入口模块出发找出所有依赖的模块,Resolve 配置 Webpack 如何寻找模块所对应的文件。 Webpack 内置 JavaScript 模块化语法解析功能,默认会采用模块化标准里约定好的规则去寻找,但你也可以根据自己的需要修改默认的规则。
resolve.alias 配置项通过别名来把原导入路径映射成一个新的导入路径。例如使用以下配置:
// Webpack alias 配置resolve:{alias:{components: './src/components/'}}
当你通过 import Button from 'components/button' 导入时,实际上被 alias 等价替换成了 import Button from './src/components/button'。
以上 alias 配置的含义是把导入语句里的 components 关键字替换成 ./src/components/。
这样做可能会命中太多的导入语句,alias 还支持 $ 符号来缩小范围到只命中以关键字结尾的导入语句:
resolve:{alias:{'react$': '/path/to/react.min.js'}}
react$ 只会命中以 react 结尾的导入语句,即只会把 import 'react' 关键字替换成 import '/path/to/react.min.js'。
有一些第三方模块会针对不同环境提供几份代码。 例如分别提供采用 ES5 和 ES6 的2份代码,这2份代码的位置写在 package.json 文件里,如下:
{"jsnext:main": "es/index.js",// 采用 ES6 语法的代码入口文件"main": "lib/index.js" // 采用 ES5 语法的代码入口文件}
Webpack 会根据 mainFields 的配置去决定优先采用那份代码,mainFields 默认如下:
mainFields: ['browser', 'main']
Webpack 会按照数组里的顺序去package.json 文件里寻找,只会使用找到的第一个。
假如你想优先采用 ES6 的那份代码,可以这样配置:
mainFields: ['jsnext:main', 'browser', 'main']
在导入语句没带文件后缀时,Webpack 会自动带上后缀后去尝试访问文件是否存在。 resolve.extensions 用于配置在尝试过程中用到的后缀列表,默认是:
extensions: ['.js', '.json']
也就是说当遇到 require('./data') 这样的导入语句时,Webpack 会先去寻找 ./data.js 文件,如果该文件不存在就去寻找 ./data.json 文件, 如果还是找不到就报错。
假如你想让 Webpack 优先使用目录下的 TypeScript 文件,可以这样配置:
extensions: ['.ts', '.js', '.json']
resolve.modules 配置 Webpack 去哪些目录下寻找第三方模块,默认是只会去 node_modules 目录下寻找。 有时你的项目里会有一些模块会大量被其它模块依赖和导入,由于其它模块的位置分布不定,针对不同的文件都要去计算被导入模块文件的相对路径, 这个路径有时候会很长,就像这样 import '../../../components/button' 这时你可以利用 modules 配置项优化,假如那些被大量导入的模块都在 ./src/components 目录下,把 modules 配置成
modules:['./src/components','node_modules']
后,你可以简单通过 import 'button' 导入。
resolve.descriptionFiles 配置描述第三方模块的文件名称,也就是 package.json 文件。默认如下:
descriptionFiles: ['package.json']
resolve.enforceExtension 如果配置为 true 所有导入语句都必须要带文件后缀, 例如开启前 import './foo' 能正常工作,开启后就必须写成 import './foo.js'。
enforceModuleExtension 和 enforceExtension 作用类似,但 enforceModuleExtension 只对 node_modules 下的模块生效。 enforceModuleExtension 通常搭配 enforceExtension 使用,在 enforceExtension:true 时,因为安装的第三方模块中大多数导入语句没带文件后缀, 所以这时通过配置 enforceModuleExtension:false 来兼容第三方模块。
Plugin 用于扩展 Webpack 功能,各种各样的 Plugin 几乎让 Webpack 可以做任何构建相关的事情。
Plugin 的配置很简单,plugins 配置项接受一个数组,数组里每一项都是一个要使用的 Plugin 的实例,Plugin 需要的参数通过构造函数传入。
const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');module.exports = {plugins: [// 所有页面都会用到的公共代码提取到 common 代码块中new CommonsChunkPlugin({name: 'common',chunks: ['a', 'b']}),]};
使用 Plugin 的难点在于掌握 Plugin 本身提供的配置项,而不是如何在 Webpack 中接入 Plugin。
几乎所有 Webpack 无法直接实现的功能都能在社区找到开源的 Plugin 去解决,你需要善于使用搜索引擎去寻找解决问题的方法。
要配置 DevServer ,除了在配置文件里通过 devServer 传入参数外,还可以通过命令行参数传入。 注意只有在通过 DevServer 去启动 Webpack 时配置文件里 devServer 才会生效,因为这些参数所对应的功能都是 DevServer 提供的,Webpack 本身并不认识 devServer 配置项。
devServer.hot 配置是否启用 使用DevServer 中提到的模块热替换功能。 DevServer 默认的行为是在发现源代码被更新后会通过自动刷新整个页面来做到实时预览,开启模块热替换功能后将在不刷新整个页面的情况下通过用新模块替换老模块来做到实时预览。
DevServer 的实时预览功能依赖一个注入到页面里的代理客户端去接受来自 DevServer 的命令和负责刷新网页的工作。 devServer.inline 用于配置是否自动注入这个代理客户端到将运行在页面里的 Chunk 里去,默认是会自动注入。 DevServer 会根据你是否开启 inline 来调整它的自动刷新策略:
如果你想使用 DevServer 去自动刷新网页实现实时预览,最方便的方法是直接开启 inline。
devServer.historyApiFallback 用于方便的开发使用了 HTML5 History API 的单页应用。 这类单页应用要求服务器在针对任何命中的路由时都返回一个对应的 HTML 文件,例如在访问 http://localhost/user 和 http://localhost/home 时都返回 index.html 文件, 浏览器端的 JavaScript 代码会从 URL 里解析出当前页面的状态,显示出对应的界面。
配置 historyApiFallback 最简单的做法是:
historyApiFallback: true
这会导致任何请求都会返回 index.html 文件,这只能用于只有一个 HTML 文件的应用。
如果你的应用由多个单页应用组成,这就需要 DevServer 根据不同的请求来返回不同的 HTML 文件,配置如下:
historyApiFallback: {// 使用正则匹配命中路由rewrites: [// /user 开头的都返回 user.html{ from: /^\/user/, to: '/user.html' },{ from: /^\/game/, to: '/game.html' },// 其它的都返回 index.html{ from: /./, to: '/index.html' },]}
devServer.contentBase 配置 DevServer HTTP 服务器的文件根目录。 默认情况下为当前执行目录,通常是项目根目录,所有一般情况下你不必设置它,除非你有额外的文件需要被 DevServer 服务。 例如你想把项目根目录下的 public 目录设置成 DevServer 服务器的文件根目录,你可以这样配置:
devServer:{contentBase: path.join(__dirname, 'public')}
这里需要指出可能会让你疑惑的地方,DevServer 服务器通过 HTTP 服务暴露出的文件分为两类:
contentBase只能用来配置暴露本地文件的规则,你可以通过 contentBase:false 来关闭暴露本地文件。
devServer.headers 配置项可以在 HTTP 响应中注入一些 HTTP 响应头,使用如下:
devServer:{headers: {'X-foo':'bar'}}
devServer.host 配置项用于配置 DevServer 服务监听的地址。 例如你想要局域网中的其它设备访问你本地的服务,可以在启动 DevServer 时带上 --host 0.0.0.0。 host 的默认值是 127.0.0.1 即只有本地可以访问 DevServer 的 HTTP 服务。
devServer.port 配置项用于配置 DevServer 服务监听的端口,默认使用 8080 端口。 如果 8080 端口已经被其它程序占有就使用 8081,如果 8081 还是被占用就使用 8082,以此类推。
devServer.allowedHosts 配置一个白名单列表,只有 HTTP 请求的 HOST 在列表里才正常返回,使用如下:
allowedHosts: [// 匹配单个域名'host.com','sub.host.com',// host2.com 和所有的子域名 *.host2.com 都将匹配'.host2.com']
devServer.disableHostCheck 配置项用于配置是否关闭用于 DNS 重绑定的 HTTP 请求的 HOST 检查。 DevServer 默认只接受来自本地的请求,关闭后可以接受来自任何 HOST 的请求。 它通常用于搭配 --host 0.0.0.0 使用,因为你想要其它设备访问你本地的服务,但访问时是直接通过 IP 地址访问而不是 HOST 访问,所以需要关闭 HOST 检查。
https
DevServer 默认使用 HTTP 协议服务,它也能通过 HTTPS 协议服务。 有些情况下你必须使用 HTTPS,例如 HTTP2 和 Service Worker 就必须运行在 HTTPS 之上。 要切换成 HTTPS 服务,最简单的方式是:
devServer:{https: true}
DevServer 会自动的为你生成一份 HTTPS 证书。
如果你想用自己的证书可以这样配置:
devServer:{https: {key: fs.readFileSync('path/to/server.key'),cert: fs.readFileSync('path/to/server.crt'),ca: fs.readFileSync('path/to/ca.pem')}}
devServer.clientLogLevel 配置在客户端的日志等级,这会影响到你在浏览器开发者工具控制台里看到的日志内容。 clientLogLevel 是枚举类型,可取如下之一的值 none | error | warning | info。 默认为 info 级别,即输出所有类型的日志,设置成 none 可以不输出任何日志。
devServer.compress 配置是否启用 gzip 压缩。boolean 为类型,默认为 false。
devServer.open 用于在 DevServer 启动且第一次构建完时自动用你系统上默认的浏览器去打开要开发的网页。 同时还提供 devServer.openPage 配置项用于打开指定 URL 的网页。
之前的章节分别讲述了每个配置项的具体含义,但没有描述它们所处的位置和数据结构,下面通过一份代码来描述清楚:
const path = require('path');module.exports = {// entry 表示 入口,Webpack 执行构建的第一步将从 Entry 开始,可抽象成输入。// 类型可以是 string | object | arrayentry: './app/entry', // 只有1个入口,入口只有1个文件entry: ['./app/entry1', './app/entry2'], // 只有1个入口,入口有2个文件entry: { // 有2个入口a: './app/entry-a',b: ['./app/entry-b1', './app/entry-b2']},// 如何输出结果:在 Webpack 经过一系列处理后,如何输出最终想要的代码。output: {// 输出文件存放的目录,必须是 string 类型的绝对路径。path: path.resolve(__dirname, 'dist'),// 输出文件的名称filename: 'bundle.js', // 完整的名称filename: '[name].js', // 当配置了多个 entry 时,通过名称模版为不同的 entry 生成不同的文件名称filename: '[chunkhash].js', // 根据文件内容 hash 值生成文件名称,用于浏览器长时间缓存文件// 发布到线上的所有资源的 URL 前缀,string 类型publicPath: '/assets/', // 放到指定目录下publicPath: '', // 放到根目录下publicPath: 'https://cdn.example.com/', // 放到 CDN 上去// 导出库的名称,string 类型// 不填它时,默认输出格式是匿名的立即执行函数library: 'MyLibrary',// 导出库的类型,枚举类型,默认是 var// 可以是 umd | umd2 | commonjs2 | commonjs | amd | this | var | assign | window | global | jsonp ,libraryTarget: 'umd',// 是否包含有用的文件路径信息到生成的代码里去,boolean 类型pathinfo: true,// 附加 Chunk 的文件名称chunkFilename: '[id].js',chunkFilename: '[chunkhash].js',// JSONP 异步加载资源时的回调函数名称,需要和服务端搭配使用jsonpFunction: 'myWebpackJsonp',// 生成的 Source Map 文件名称sourceMapFilename: '[file].map',// 浏览器开发者工具里显示的源码模块名称devtoolModuleFilenameTemplate: 'webpack:///[resource-path]',// 异步加载跨域的资源时使用的方式crossOriginLoading: 'use-credentials',crossOriginLoading: 'anonymous',crossOriginLoading: false,},// 配置模块相关module: {rules: [ // 配置 Loader{test: /\.jsx?$/, // 正则匹配命中要使用 Loader 的文件include: [ // 只会命中这里面的文件path.resolve(__dirname, 'app')],exclude: [ // 忽略这里面的文件path.resolve(__dirname, 'app/demo-files')],use: [ // 使用那些 Loader,有先后次序,从后往前执行'style-loader', // 直接使用 Loader 的名称{loader: 'css-loader',options: { // 给 html-loader 传一些参数}}]},],noParse: [ // 不用解析和处理的模块/special-library\.js$/ // 用正则匹配],},// 配置插件plugins: [],// 配置寻找模块的规则resolve: {modules: [ // 寻找模块的根目录,array 类型,默认以 node_modules 为根目录'node_modules',path.resolve(__dirname, 'app')],extensions: ['.js', '.json', '.jsx', '.css'], // 模块的后缀名alias: { // 模块别名配置,用于映射模块// 把 'module' 映射 'new-module',同样的 'module/path/file' 也会被映射成 'new-module/path/file''module': 'new-module',// 使用结尾符号 $ 后,把 'only-module' 映射成 'new-module',// 但是不像上面的,'module/path/file' 不会被映射成 'new-module/path/file''only-module$': 'new-module',},alias: [ // alias 还支持使用数组来更详细的配置{name: 'module', // 老的模块alias: 'new-module', // 新的模块// 是否是只映射模块,如果是 true 只有 'module' 会被映射,如果是 false 'module/inner/path' 也会被映射onlyModule: true,}],symlinks: true, // 是否跟随文件软链接去搜寻模块的路径descriptionFiles: ['package.json'], // 模块的描述文件mainFields: ['main'], // 模块的描述文件里的描述入口的文件的字段名称enforceExtension: false, // 是否强制导入语句必须要写明文件后缀},// 输出文件性能检查配置performance: {hints: 'warning', // 有性能问题时输出警告hints: 'error', // 有性能问题时输出错误hints: false, // 关闭性能检查maxAssetSize: 200000, // 最大文件大小 (单位 bytes)maxEntrypointSize: 400000, // 最大入口文件大小 (单位 bytes)assetFilter: function(assetFilename) { // 过滤要检查的文件return assetFilename.endsWith('.css') || assetFilename.endsWith('.js');}},devtool: 'source-map', // 配置 source-map 类型context: __dirname, // Webpack 使用的根目录,string 类型必须是绝对路径// 配置输出代码的运行环境target: 'web', // 浏览器,默认target: 'webworker', // WebWorkertarget: 'node', // Node.js,使用 `require` 语句加载 Chunk 代码target: 'async-node', // Node.js,异步加载 Chunk 代码target: 'node-webkit', // nw.jstarget: 'electron-main', // electron, 主线程target: 'electron-renderer', // electron, 渲染线程externals: { // 使用来自 JavaScript 运行环境提供的全局变量jquery: 'jQuery'},stats: { // 控制台输出日志控制assets: true,colors: true,errors: true,errorDetails: true,hash: true,},devServer: { // DevServer 相关的配置proxy: { // 代理到后端服务接口'/api': 'http://localhost:3000'},contentBase: path.join(__dirname, 'public'), // 配置 DevServer HTTP 服务器的文件根目录compress: true, // 是否开启 gzip 压缩historyApiFallback: true, // 是否开发 HTML5 History API 网页hot: true, // 是否开启模块热替换功能https: false, // 是否开启 HTTPS 模式},profile: true, // 是否捕捉 Webpack 构建的性能信息,用于分析什么原因导致构建性能不佳cache: false, // 是否启用缓存提升构建速度watch: true, // 是否开始watchOptions: { // 监听模式选项// 不监听的文件或文件夹,支持正则匹配。默认为空ignored: /node_modules/,// 监听到变化发生后会等300ms再去执行动作,防止文件更新太快导致重新编译频率太高// 默认为300msaggregateTimeout: 300,// 判断文件是否发生变化是不停的去询问系统指定文件有没有变化,默认每秒问 1000 次poll: 1000},}
本节将带你走进这个黑盒,看看 Webpack 是如何运行的。
Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:
在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。
Webpack 的构建流程可以分为以下三大阶段:
图 监听模式的构建流程

在每个大阶段中又会发生很多事件,Webpack 会把这些事件广播出来供给 Plugin 使用,下面来一一介绍。
| 事件名 | 解释 |
|---|---|
| 初始化参数 | 从配置文件和 Shell 语句中读取与合并参数,得出最终的参数。 这个过程中还会执行配置文件中的插件实例化语句 new Plugin()。 |
| 实例化 Compiler | 用上一步得到的参数初始化 Compiler 实例,Compiler 负责文件监听和启动编译。Compiler 实例中包含了完整的 Webpack 配置,全局只有一个 Compiler 实例。 |
| 加载插件 | 依次调用插件的 apply 方法,让插件可以监听后续的所有事件节点。同时给插件传入 compiler 实例的引用,以方便插件通过 compiler 调用 Webpack 提供的 API。 |
| environment | 开始应用 Node.js 风格的文件系统到 compiler 对象,以方便后续的文件寻找和读取。 |
| entry-option | 读取配置的 Entrys,为每个 Entry 实例化一个对应的 EntryPlugin,为后面该 Entry 的递归解析工作做准备。 |
| after-plugins | 调用完所有内置的和配置的插件的 apply 方法。 |
| after-resolvers | 根据配置初始化完 resolver,resolver 负责在文件系统中寻找指定路径的文件。 |
编译阶段
事件名 解释
run 启动一次新的编译。
watch-run 和 run 类似,区别在于它是在监听模式下启动的编译,在这个事件中可以获取到是哪些文件发生了变化导致重新启动一次新的编译。
compile 该事件是为了告诉插件一次新的编译将要启动,同时会给插件带上 compiler 对象。
compilation 当 Webpack 以开发模式运行时,每当检测到文件变化,一次新的 Compilation 将被创建。一个 Compilation 对象包含了当前的模块资源、编译生成资源、变化的文件等。Compilation 对象也提供了很多事件回调供插件做扩展。
make 一个新的 Compilation 创建完毕,即将从 Entry 开始读取文件,根据文件类型和配置的 Loader 对文件进行编译,编译完后再找出该文件依赖的文件,递归的编译和解析。
after-compile 一次 Compilation 执行完成。
invalid 当遇到文件不存在、文件编译错误等异常时会触发该事件,该事件不会导致 Webpack 退出。
在编译阶段中,最重要的要数 compilation 事件了,因为在 compilation 阶段调用了 Loader 完成了每个模块的转换操作,在 compilation 阶段又包括很多小的事件,它们分别是:
事件名 解释
build-module 使用对应的 Loader 去转换一个模块。
normal-module-loader 在用 Loader 对一个模块转换完后,使用 acorn 解析转换后的内容,输出对应的抽象语法树(AST),以方便 Webpack 后面对代码的分析。
program 从配置的入口模块开始,分析其 AST,当遇到 require 等导入其它模块语句时,便将其加入到依赖的模块列表,同时对新找出的依赖模块递归分析,最终搞清所有模块的依赖关系。
seal 所有模块及其依赖的模块都通过 Loader 转换完成后,根据依赖关系开始生成 Chunk。
输出阶段
事件名 解释
should-emit 所有需要输出的文件已经生成好,询问插件哪些文件需要输出,哪些不需要。
emit 确定好要输出哪些文件后,执行文件输出,可以在这里获取和修改输出内容。
after-emit 文件输出完毕。
done 成功完成一次完成的编译和输出流程。
failed 如果在编译和输出流程中遇到异常导致 Webpack 退出时,就会直接跳转到本步骤,插件可以在本事件中获取到具体的错误原因。
在输出阶段已经得到了各个模块经过转换后的结果和其依赖关系,并且把相关模块组合在一起形成一个个 Chunk。 在输出阶段会根据 Chunk 的类型,使用对应的模版生成最终要要输出的文件内容。
至于如何把 Chunk 输出为具体的文件,详情可以阅读 5-2输出文件分析。