webpack2事件流程控制和核心概念(二)

前言

  在写下一篇关于webpack2打包流程的文章之前,需要把webpack2中的一些概念来理一理。这样能提供更好的理解。

tapable

  webpack的整个流程是通过事件进行控制的。webpack在整个流程中对事件的执行顺序和错误的处理问题是需要考虑的,tapable刚好提供了这么一个解决方案。(tapable详细使用,自行查阅资料)

  • tapable事件流程控制事例
    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
    const tapable = require('tapable');
    const Tapable = new tapable();
    Tapable.plugin('name', function (value, cb) {
    setTimeout(function () {
    console.log(value);cb(null,500);
    }, value)
    })
    Tapable.plugin('name', function (value, cb) {
    setTimeout(function () {
    console.log(value);cb(null,200);
    }, value)
    })
    Tapable.plugin('name', function (value, cb) {
    setTimeout(function () {
    console.log(value);cb(null,'end');
    }, value)
    })
    Tapable.applyPluginsAsyncWaterfall('name', 1000, function (err,result) {
    console.log("end",err,result)
    });
    /*执行结果
    1000
    500
    200
    end null end
    */

  执行结果遵循先入先出原则,当前事件的处理函数执行cb()后才会执行下一个处理函数。注:applyPluginsAsyncWaterfall中,前一个处理函数执行cb(null,value)传入的第二个参数value作为下一个执行函数的传入参数值。

  • tapable错误处理事例
    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
    33
    const tapable = require('tapable');
    const Tapable = new tapable();
    Tapable.plugin('name', function (value, cb) {
    setTimeout(function () {
    console.log(value);cb(null,500);
    }, value)
    })
    Tapable.plugin('name', function (value, cb) {
    setTimeout(function () {
    console.log(value);cb(new Error('test error'));
    }, value)
    })
    Tapable.plugin('name', function (value, cb) {
    setTimeout(function () {
    console.log(value);cb(null,'end');
    }, value)
    })
    Tapable.applyPluginsAsyncWaterfall('name', 1000, function (err,result) {
    console.log("end",err,result)
    });
    /*执行结果
    1000
    500
    end Error: test error
    at Timeout._onTimeout (/Users/xxx/xxx/src/test.js:24:27)
    at ontimeout (timers.js:469:11)
    at tryOnTimeout (timers.js:304:5)
    at Timer.listOnTimeout (timers.js:264:5) undefined
    */

  第二个事件处理函数中抛出了错误cb(new Error('test error'))时,后面的处理函数将不会被执行,直接结束。执行applyPluginsAsyncWaterfall中的函数。

webpack概念

  webpack中有许多的实体,而其中部分核心实体和我们打包过程密不可分。

compiler 和 compilation

compiler源码解析:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// bin/webpack.js 文件
... //对配置文件和shell中参数进行处理生成options,加载部分插件的工作
var webpack = require("../lib/webpack.js");
try { //生成compiler
compiler = webpack(options);
...
// lib/webpack.js文件
//options是config配置和命令行参数解析后生成的配置信息
if(Array.isArray(options)) {
//多个options,生成MultiCompiler对象,包含多个compiler
compiler = new MultiCompiler(options.map(options => webpack(options)));
} else if(typeof options === "object") {
new WebpackOptionsDefaulter().process(options);
compiler = new Compiler();
compiler.context = options.context;
//赋值
compiler.options = options;
new NodeEnvironmentPlugin().apply(compiler);
if(options.plugins && Array.isArray(options.plugins)) {
//执行各插件的apply函数,compiler.apply函数继承于Tapable
compiler.apply.apply(compiler, options.plugins);
}
//触发environment,after-environment事件
compiler.applyPlugins("environment");
compiler.applyPlugins("after-environment");
//WebpackOptionsApply的process函数中根据配置信息对compiler中属性赋值
//根据配置信息执行插件,返回新的options
compiler.options = new WebpackOptionsApply().process(options, compiler);
....
// lib/Compiler.js文件
function Compiler() {
Tapable.call(this);
//环境配置信息,lib/webpack.js文件中WebpackOptionsApply的process函数对其赋值
this.outputPath = "";
this.outputFileSystem = null;
...
}
Compiler.prototype.watch = function(watchOptions, handler) {...
Compiler.prototype.run = function(callback) {...
Compiler.prototype.newCompilation = function(params) {...
....

  我们可以在源码中看出,在options配置信息生成后,compiler对象被webpack创建。compiler继承于Tapable,包含了所有配置信息。compiler控制着打包的整个流程,包括创建compilation对象,然后执行编译,文件输出等流程。

compilation源码解析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Compilation extends Tapable {
constructor(compiler) {
super();
this.compiler = compiler;
this.resolvers = compiler.resolvers;
...
this.entries = [];
...
this.chunks = [];
...
this.modules = [];
addEntry(context, entry, name, callback){...
seal(callback) {...
buildModule(module, optional, origin, dependencies, thisCallback) {...

  compilation也继承于Tapable,被compiler创建。不仅包含了webpack环境配置,模块和compiler等信息,还包含了模块和模块依赖的创建和构建,封装方法。chunk的创建和最终生成assets信息的方法等。每当一个文件发生改变时就会产生一个新的compilation。
(关于compilation中的各种方法,在下一篇的打包流程的文章中将被详细讲到。)

module

  • 所有的入口文件,文件中的依赖都会被创建为module,module是整个过程最核心,最基础的实体。chunk的封装和asset生成都是基于module。
  • module包含了NormalModule,MultiModule,ContextModule,DelegatedModule,ExternalModule等子类。
  • NormalModule为单一入口时创建的模块,MultiModule为多入口时创建模块,ContextModule为模块化规范(amd,common,es6等)使用而创建的模块。
  • module由NormalModuleFactory,ContextModuleFactory,DllModuleFactory,MultiModuleFactory等工厂模块所创建。

dependency

依赖在模块执行loader之后,通过acorn解析生成AST(抽象语法树)之时,被依赖解析插件遍历,最终将依赖加入dependencies数组,其实依赖也是模块。

chunk

chunk是由多个module的具体的拆分处理后生成,包含了一个或多个module。比如:异步加载的模块就会被创建生成一个新的chunk,使用CommonsChunkPlugin也会生成新chunk。

Template

将不同的chunk通过对应的依赖模板,封装成source,赋值给asset。