init 阶段:compiler
本节将探讨 webpack 构建流程中 init 阶段的如下工作:
- options 初始化
- compiler 初始化
- 编译环境初始化
- compiler.run
# 目录
# webpack
// lib/webpack.js
const webpack = (options, callback) => {
const create = () => {
// 校验 options
if (!asArray(options).every(webpackOptionsSchemaCheck)) {
getValidateSchema()(webpackOptionsSchema, options);
}
let compiler;
let watch = false;
let watchOptions;
// options 是数组,创建 MultiCompiler
if (Array.isArray(options)) {
compiler = createMultiCompiler(
options,
);
// 收集 watch 和 watchOptions
watch = options.some(options => options.watch);
watchOptions = options.map(options => options.watchOptions || {});
} else {
const webpackOptions = (options);
// 创建 compiler
compiler = createCompiler(webpackOptions);
watch = webpackOptions.watch;
watchOptions = webpackOptions.watchOptions || {};
}
return { compiler, watch, watchOptions };
};
if (callback) {
try {
const { compiler, watch, watchOptions } = create();
if (watch) {
// 配置了 watch,开启 Watching
compiler.watch(watchOptions, callback);
} else {
// 启动 compiler
compiler.run((err, stats) => {
// compile 完毕,关闭 compiler
compiler.close(err2 => {
callback(err || err2, stats);
});
});
}
return compiler;
} catch (err) {
process.nextTick(() => callback(err));
return null;
}
} else {
const { compiler, watch } = create();
// ......
return 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
53
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
53
下面是精简代码:
const webpack = (options, callback) => {
// 校验 options
if (!asArray(options).every(webpackOptionsSchemaCheck)) {
getValidateSchema()(webpackOptionsSchema, options);
}
// 创建 compiler
const compiler = createCompiler(options);
// 启动 compiler
compiler.run((err, stats) => {
// compile 完毕,关闭 compiler
compiler.close((err2) => {
callback(err || err2, stats);
});
});
return compiler;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
总结一下核心的作用:
- 校验 options
- 根据 options 生成并启动 compiler
# createCompiler
// lib/webpack.js
const createCompiler = rawOptions => {
// options 规整化
const options = getNormalizedWebpackOptions(rawOptions);
// merge webpack 基础默认的 options:context、infrastructureLogging
applyWebpackOptionsBaseDefaults(options);
// 新建 Compiler
const compiler = new Compiler(options.context, options);
// ========== 注册插件 ========== //
// 注册 NodeEnvironmentPlugin:beforeRun => 初始化 Node 环境
new NodeEnvironmentPlugin({
infrastructureLogging: options.infrastructureLogging
}).apply(compiler);
// 注册 options.plugins
if (Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else {
plugin.apply(compiler);
}
}
}
// merge webpack 默认 options: context、target、devtool等
applyWebpackOptionsDefaults(options);
// 触发 Hook.environment => call
compiler.hooks.environment.call();
// 触发 Hook.afterEnvironment => call
compiler.hooks.afterEnvironment.call();
// WebpackOptionsApply 根据 options 注册内置插件
new WebpackOptionsApply().process(options, compiler);
// 触发 Hook.initialize => call
compiler.hooks.initialize.call();
// 返回 compiler
return 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
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
总结核心作用如下:
- options 规整化、merge 默认的 options
- 创建 compiler 并返回
- 注册插件 NodeEnvironmentPlugin、options.plugins、基于 options 的内置插件
- 依次触发 Hook.environment、Hook.afterEnvironment、Hook.initialize
# compiler.run
run(callback) {
// 如果 compiler 已经在运行则报错
if (this.running) {
return callback(new ConcurrentCompilationError());
}
// 仅在出现 err 时执行
const finalCallback = (err, stats) => {
// 将 compiler 标记为 idle
this.idle = true;
// 触发 CacheHook.beginIdle => call
this.cache.beginIdle();
this.idle = true;
// 标记 compiler running 为 false
this.running = false;
if (err) {
// 触发 Hook.failed => call
this.hooks.failed.call(err);
}
if (callback !== undefined) callback(err, stats);
// 触发 Hook.afterDone => call
this.hooks.afterDone.call(stats);
};
// 标记 running 为 true
this.running = true;
const run = () => {
// 触发 Hook.beforeRun: callAsync
this.hooks.beforeRun.callAsync(this, err => {
if (err) return finalCallback(err);
// 触发 Hook.run: callAsync
this.hooks.run.callAsync(this, err => {
if (err) return finalCallback(err);
// 尝试从 records 恢复,反序列化
this.readRecords(err => {
if (err) return finalCallback(err);
// 开始 compile
this.compile(onCompiled);
});
});
});
};
// 初始为 false
if (this.idle) {
// 触发 CacheHook.endIdle
this.cache.endIdle(err => {
if (err) return finalCallback(err);
this.idle = false;
run();
});
} else {
run();
}
}
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
53
54
55
56
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
53
54
55
56
总结一下核心作用:
- 如果 compiler 为 idle 状态,触发 CacheHook.endIdle,并且执行 run。否则直接执行 run。
- compiler.run 依次触发 Hook.beforeRun、Hook.run。尝试从序列化的 records(compiler 的状态) 中恢复,并且调用 compile,compile 完成后执行 onCompiled。标记 compiler 为 running。
- 发生错误时执行 finalCallback,调用上层 callback,将 compile 标记为 idle,触发 CacheHook.beginIdle、Hook.failed 和 Hook.afterDone。去除 compiler 的 running 标记。
# compiler.compile
// lib/Compiler.js
compile(callback) {
// 使用缓存的 compilationParams
const params = this.newCompilationParams();
// 触发 Hook.beforeCompile => callAsync
this.hooks.beforeCompile.callAsync(params, err => {
if (err) return callback(err);
// 触发 Hook.compile => call
this.hooks.compile.call(params);
// 使用缓存的 compilation 对象
const compilation = this.newCompilation(params);
// 触发 Hook.make => callAsync
this.hooks.make.callAsync(compilation, err => {
if (err) return callback(err);
// 触发 Hook.finishMake => callAsync
this.hooks.finishMake.callAsync(compilation, err => {
if (err) return callback(err);
process.nextTick(() => {
// compilation.finish
compilation.finish(err => {
if (err) return callback(err);
// compilation.seal
compilation.seal(err => {
if (err) return callback(err);
// 触发 Hook.afterCompile => callAsync
this.hooks.afterCompile.callAsync(compilation, err => {
if (err) return callback(err);
return callback(null, compilation);
});
});
});
});
});
});
});
}
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
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
compile
的代码高度抽象化,具体的业务逻辑都封装到相应的插件中了。
总结一下核心作用:
- 依次触发 Hook.beforeCompile、Hook.compile、Hook.make、Hook.finishMake、Hook.afterCompile。
- compilation:创建 compilation 对象、触发 Hook.make、Hook.finishMake、执行 compilation.finish、compilation.seal、触发 Hook.afterCompile。
# newCompilationParams
这个函数或创建 normalModuleFactory 和 contextModuleFactory。
newCompilationParams() {
const params = {
// new NormalModuleFactory()
normalModuleFactory: this.createNormalModuleFactory(),
// new ContextModuleFactory()
contextModuleFactory: this.createContextModuleFactory()
};
return params;
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# newCompilation
// lib/Compiler.js
createCompilation(params) {
// 注意:这里并不会频繁实例化类,而是在 _cleanupLastCompilation 中将 _lastCompilation 清理之后复用
this._cleanupLastCompilation();
return (this._lastCompilation = new Compilation(this, params));
}
newCompilation(params) {
// 创建 compilation 实例
const compilation = this.createCompilation(params);
compilation.name = this.name;
compilation.records = this.records;
// 触发 Hook.thisCompilation => call
this.hooks.thisCompilation.call(compilation, params);
// 触发 Hook.compilation => call
this.hooks.compilation.call(compilation, params);
return compilation;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
核心作用如下:
- 创建或者复用 compilation。
- 依次触发 Hook.thisCompilation、Hook.compilation。
# compilation.addEntry
在 createCompiler
中我们了解到 new WebpackOptionsApply().process(options, compiler)
会根据 options 来注册各种不同的插件。
// lib/WebpackOptionsApply.js
new EntryOptionPlugin().apply(compiler);
// 触发 Hook.entryOption => call
compiler.hooks.entryOption.call(options.context, options.entry);
1
2
3
4
2
3
4
我们来探讨下这里注册的 EntryOptionPlugin 插件:
// lib/EntryOptionPlugin.js
class EntryOptionPlugin {
// ......
apply(compiler) {
// 注册 Callback: Hook.entryOption, Hook.entryOption 触发时,将执行 applyEntryOption
// Hook.entryOption 在此插件注册完毕后就会被触发
compiler.hooks.entryOption.tap("EntryOptionPlugin", (context, entry) => {
EntryOptionPlugin.applyEntryOption(compiler, context, entry);
return true;
});
}
static applyEntryOption(compiler, context, entry) {
// entry 是函数,注册 DynamicEntryPlugin
if (typeof entry === "function") {
const DynamicEntryPlugin = require("./DynamicEntryPlugin");
new DynamicEntryPlugin(context, entry).apply(compiler);
} else {
// entry 是 object,注册 EntryPlugin
const EntryPlugin = require("./EntryPlugin");
// 循环 entries 将 entry 生成 options
// entry: {
// b2: {
// dependOn: 'a2',
// import: './src/app.js',
// },
// },
for (const name of Object.keys(entry)) {
const desc = entry[name];
const options = EntryOptionPlugin.entryDescriptionToOptions(
compiler,
name,
desc
);
// 对于 entry 中每一个入口路径,注册插件 EntryPlugin
for (const entry of desc.import) {
new EntryPlugin(context, entry, options).apply(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
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
总结一下:
createCompiler
中注册WebpackOptionsApply
插件时,会注册EntryOptionPlugin
插件,在随后的Hook.entryOption
调用时,为每个 Entry 的 import 都注册了插件EntryPlugin
。- 对每个 Entry.import 所注册的
EntryPlugin
将会初始化打包入口。
接着来探讨下 EntryPlugin
插件:
class EntryPlugin {
apply(compiler) {
// 注册 Callback:Hook.compilation
// Hook.compilation 在 compiler.compile 函数中触发
compiler.hooks.compilation.tap(
"EntryPlugin",
(compilation, { normalModuleFactory }) => {
// dependencyFactories 记录某种 Dependency 应用哪种 ModuleFactory
// 记录 EntryDependency 使用 normalModuleFactory
compilation.dependencyFactories.set(
EntryDependency,
normalModuleFactory
);
}
);
const { entry, options, context } = this;
// 静态方法创建 EntryDependency
const dep = EntryPlugin.createDependency(entry, options);
// 注册 Callback: Hook.make => tapAsync
// Hook.make 在 compiler.compile 函数中触发
compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => {
// compilation.addEntry 添加 Entry,目的是创建 EntryDependency
compilation.addEntry(context, dep, options, (err) => {
callback(err);
});
});
}
static createDependency(entry, options) {
// 创建 EntryDependency 对象
const dep = new EntryDependency(entry);
// TODO webpack 6 remove string option
dep.loc = { name: typeof options === "object" ? options.name : options };
return dep;
}
}
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
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
总结一下:
- 在 Hook.compilation 触发时记录 EntryDependency 使用 normalModuleFactory,创建 EntryDependency,并在 Hook.make 触发时执行
compilation.addEntry
。
# compiler.Hook
compiler 有如下的 Hook。
this.hooks = Object.freeze({
/** @type {SyncHook<[]>} */
initialize: new SyncHook([]),
/** @type {SyncBailHook<[Compilation], boolean>} */
shouldEmit: new SyncBailHook(["compilation"]),
/** @type {AsyncSeriesHook<[Stats]>} */
done: new AsyncSeriesHook(["stats"]),
/** @type {SyncHook<[Stats]>} */
afterDone: new SyncHook(["stats"]),
/** @type {AsyncSeriesHook<[]>} */
additionalPass: new AsyncSeriesHook([]),
/** @type {AsyncSeriesHook<[Compiler]>} */
beforeRun: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<[Compiler]>} */
run: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<[Compilation]>} */
emit: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook<[string, AssetEmittedInfo]>} */
assetEmitted: new AsyncSeriesHook(["file", "info"]),
/** @type {AsyncSeriesHook<[Compilation]>} */
afterEmit: new AsyncSeriesHook(["compilation"]),
/** @type {SyncHook<[Compilation, CompilationParams]>} */
thisCompilation: new SyncHook(["compilation", "params"]),
/** @type {SyncHook<[Compilation, CompilationParams]>} */
compilation: new SyncHook(["compilation", "params"]),
/** @type {SyncHook<[NormalModuleFactory]>} */
normalModuleFactory: new SyncHook(["normalModuleFactory"]),
/** @type {SyncHook<[ContextModuleFactory]>} */
contextModuleFactory: new SyncHook(["contextModuleFactory"]),
/** @type {AsyncSeriesHook<[CompilationParams]>} */
beforeCompile: new AsyncSeriesHook(["params"]),
/** @type {SyncHook<[CompilationParams]>} */
compile: new SyncHook(["params"]),
/** @type {AsyncParallelHook<[Compilation]>} */
make: new AsyncParallelHook(["compilation"]),
/** @type {AsyncParallelHook<[Compilation]>} */
finishMake: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook<[Compilation]>} */
afterCompile: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook<[]>} */
readRecords: new AsyncSeriesHook([]),
/** @type {AsyncSeriesHook<[]>} */
emitRecords: new AsyncSeriesHook([]),
/** @type {AsyncSeriesHook<[Compiler]>} */
watchRun: new AsyncSeriesHook(["compiler"]),
/** @type {SyncHook<[Error]>} */
failed: new SyncHook(["error"]),
/** @type {SyncHook<[string | null, number]>} */
invalid: new SyncHook(["filename", "changeTime"]),
/** @type {SyncHook<[]>} */
watchClose: new SyncHook([]),
/** @type {AsyncSeriesHook<[]>} */
shutdown: new AsyncSeriesHook([]),
/** @type {SyncBailHook<[string, string, any[]], true>} */
infrastructureLog: new SyncBailHook(["origin", "type", "args"]),
/** @type {SyncHook<[]>} */
environment: new SyncHook([]),
/** @type {SyncHook<[]>} */
afterEnvironment: new SyncHook([]),
/** @type {SyncHook<[Compiler]>} */
afterPlugins: new SyncHook(["compiler"]),
/** @type {SyncHook<[Compiler]>} */
afterResolvers: new SyncHook(["compiler"]),
/** @type {SyncBailHook<[string, Entry], boolean>} */
entryOption: new SyncBailHook(["context", "entry"])
});
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
注意:Object.freeze 只冻结顶层,Callbacks 并不会被冻结。
# 参考
编辑 (opens new window)
上次更新: 2022/09/06, 14:25:16