webpack是一个js打包工具,不一个完整的前端构建工具。它的流行得益于模块化和单页应用的流行。webpack提供扩展机制,在庞大的社区支持下各种场景基本它都可找到解决方案。本文的目的是教会你用webpack解决实战中常见的问题。

webpack原理

在深入实战前先要知道webpack的运行原理

webpack核心概念

  • ​entry​​ 一个可执行模块或库的入口文件。
  • ​chunk​​​ 多个文件组成的一个代码块,例如把一个可执行模块和它所有依赖的模块组合和一个​​chunk​​ 这体现了webpack的打包机制。
  • ​loader​​ 文件转换器,例如把es6转换为es5,scss转换为css。
  • ​plugin​​ 插件,用于扩展webpack的功能,在webpack构建生命周期的节点上加入扩展hook为webpack加入功能。

webpack构建流程

从启动webpack构建到输出结果经历了一系列过程,它们是:

  1. 解析webpack配置参数,合并从shell传入和​​webpack.config.js​​文件里配置的参数,生产最后的配置结果。
  2. 注册所有配置的插件,好让插件监听webpack构建生命周期的事件节点,以做出对应的反应。
  3. 从配置的​​entry​​入口文件开始解析文件构建AST语法树,找出每个文件所依赖的文件,递归下去。
  4. 在解析文件递归的过程中根据文件类型和loader配置找出合适的loader用来对文件进行转换。
  5. 递归完后得到每个文件的最终结果,根据​​entry​​​配置生成代码块​​chunk​​。
  6. 输出所有​​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。