webpack是一个js打包工具,不一个完整的前端构建工具。它的流行得益于模块化和单页应用的流行。webpack提供扩展机制,在庞大的社区支持下各种场景基本它都可找到解决方案。本文的目的是教会你用webpack解决实战中常见的问题。
webpack原理
在深入实战前先要知道webpack的运行原理
webpack核心概念
-
entry
一个可执行模块或库的入口文件。 -
chunk
多个文件组成的一个代码块,例如把一个可执行模块和它所有依赖的模块组合和一个chunk
这体现了webpack的打包机制。 -
loader
文件转换器,例如把es6转换为es5,scss转换为css。 -
plugin
插件,用于扩展webpack的功能,在webpack构建生命周期的节点上加入扩展hook为webpack加入功能。
webpack构建流程
从启动webpack构建到输出结果经历了一系列过程,它们是:
- 解析webpack配置参数,合并从shell传入和
webpack.config.js
文件里配置的参数,生产最后的配置结果。 - 注册所有配置的插件,好让插件监听webpack构建生命周期的事件节点,以做出对应的反应。
- 从配置的
entry
入口文件开始解析文件构建AST语法树,找出每个文件所依赖的文件,递归下去。 - 在解析文件递归的过程中根据文件类型和loader配置找出合适的loader用来对文件进行转换。
- 递归完后得到每个文件的最终结果,根据
entry
配置生成代码块chunk
。 - 输出所有
chunk
到文件系统。
需要注意的是,在构建生命周期中有一系列插件在合适的时机做了合适的事情,比如UglifyJsPlugin
会在loader转换递归完后对结果再使用UglifyJs
压缩覆盖之前的结果。
场景和方案
通过各种场景和对应的解决方案让你深入掌握webpack
单页应用
demo redemo 一个单页应用需要配置一个entry
指明执行入口,webpack会为entry
生成一个包含这个入口所有依赖文件的chunk
,但要让它在浏览器里跑起来还需要一个HTML文件来加载chunk
生成的js文件,如果提取出了css还需要让HTML文件引入提取出的css。web-webpack-plugin里的WebPlugin
可以自动的完成这些工作。
webpack配置文件
const { WebPlugin } = require('web-webpack-plugin');
module.exports = {
entry: {
app: './src/doc/index.js',
},
plugins: [
// 一个WebPlugin对应生成一个html文件
new WebPlugin({
//输出的html文件名称
filename: 'index.html',
//这个html依赖的`entry`
requires: ['app'],
}),
],
};
requires: ['doc']
指明这个HTML依赖哪些entry
,entry
生成的js和css会自动注入到HTML里。 你还可以配置这些资源的注入方式,支持如下属性:
-
_dist
只有在生产环境下才引入该资源 -
_dev
只有在开发环境下才引入该资源 -
_inline
把该资源的内容潜入到html里 -
_ie
只有IE浏览器才需要引入的资源
要设置这些属性可以通过在js里配置
编写 webpack loader
module.exports = function (content) {
return replace(content);
};
loader
的入口需要导出一个函数,这个函数要干的事情就是转换一个文件的内容。 函数接收的参数content
是一个文件在转换前的字符串形式内容,需要返回一个新的字符串形式内容作为转换后的结果,所有通过模块化倒入的文件都会经过loader
。从这里可以看出loader
只能处理一个个单独的文件而不能处理代码块。想编写更复杂的loader可参考官方文档
编写 webpack plugin
demo end-webpack-plugin plugin
应用场景广泛,所以稍微复杂点。以end-webpack-plugin为例:
class EndWebpackPlugin {
constructor(doneCallback, failCallback) {
this.doneCallback = doneCallback;
this.failCallback = failCallback;
}
apply(compiler) {
// 监听webpack生命周期里的事件,做相应的处理
compiler.plugin('done', (stats) => {
this.doneCallback(stats);
});
compiler.plugin('failed', (err) => {
this.failCallback(err);
});
}
}
module.exports = EndWebpackPlugin;
loader
的入口需要导出一个class, 在new EndWebpackPlugin()
的时候通过构造函数传入这个插件需要的参数,在webpack启动的时候会先实例化plugin
再调用plugin
的apply
方法,插件需要在apply
函数里监听webpack生命周期里的事件,做相应的处理。 webpack plugin 里有2个核心概念:
-
Compiler
: 从webpack启动到推出只存在一个Compiler
,Compiler
存放着webpack配置 -
Compilation
: 由于webpack的监听文件变化自动编译机制,Compilation
代表一次编译。
Compiler
和 Compilation
都会广播一系列事件。 webpack生命周期里有非常多的事件可以在event-hooks和Compilation里查到。以上只是一个最简单的demo,更复杂的可以查看 how to write a plugin或参考web-webpack-plugin。