make 阶段:module
在上一节中,我们追踪了 compilation.addEntry 函数,围绕着 compilation 探讨了 Module 对象的创建过程。在本节中,我们继续来探讨 module 的打包过程。
# 目录
# _handleModuleBuildAndDependencies
this.buildModule(module, (err) => {
// ......
// Module 已经在 processDependenciesQueue 中处理,直接 callback
// This avoids deadlocks for circular dependencies
if (this.processDependenciesQueue.isProcessing(module)) {
return callback();
}
// 调用 processModuleDependencies 处理模块依赖
this.processModuleDependencies(module, (err) => {
// ......
callback(null, module);
});
});
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
这个函数调用 buildModule 打包模块,并且调用 processModuleDependencies 处理模块依赖。
# buildModule
buildModule(module, callback) {
this.buildQueue.add(module, callback);
}
1
2
3
2
3
buildModule 将 module 封装成任务放入 buildQueue 队列,buildQueue 队列的 processor 为 _buildModule
。在 processor 中将处理 module。
下面我们赵忠看下 _buildModule
函数。
function _buildModule(module, callback) {
// 检查 module 是否需要 build
module.needBuild(
{
compilation: this,
fileSystemInfo: this.fileSystemInfo,
valueCacheVersions: this.valueCacheVersions,
},
(err, needBuild) => {
// ......
if (!needBuild) {
// 不需要 build, 触发 CompilationHook.stillValidModule => call
this.hooks.stillValidModule.call(module);
return callback();
}
// 触发 CompilationHook.buildModule => call
this.hooks.buildModule.call(module);
// 将 module 加入到 builtModules
// this.builtModules = new WeakSet();
this.builtModules.add(module);
// 开始 build
module.build(
this.options,
this,
this.resolverFactory.get("normal", module.resolveOptions),
this.inputFileSystem,
(err) => {
// ......
}
);
}
);
}
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
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
因为每一个 module 对象都拥有自己的 module.needBuild 方法和 module.build 方法,下面以 NormalModule
为例继续探讨。
function needBuild(context, callback) {
const { fileSystemInfo, compilation, valueCacheVersions } = context;
// build if enforced
if (this._forceBuild) return callback(null, true);
// always try to build in case of an error
if (this.error) return callback(null, true);
// 配置 cacheable 和 snapshot 可以过滤掉部分无效的 build
// always build when module is not cacheable
if (!this.buildInfo.cacheable) return callback(null, true);
// build when there is no snapshot to check
if (!this.buildInfo.snapshot) return callback(null, true);
// build when valueDependencies have changed
/** @type {Map<string, string | Set<string>>} */
const valueDependencies = this.buildInfo.valueDependencies;
if (valueDependencies) {
if (!valueCacheVersions) return callback(null, true);
// 循环 buildInfo.valueDependencies,检查 context.valueCacheVersions 中依赖是否相同
for (const [key, value] of valueDependencies) {
if (value === undefined) return callback(null, true);
const current = valueCacheVersions.get(key);
if (
value !== current &&
(typeof value === "string" ||
typeof current === "string" ||
current === undefined ||
!isSubset(value, current))
) {
return callback(null, true);
}
}
}
// check snapshot for validity
fileSystemInfo.checkSnapshotValid(this.buildInfo.snapshot, (err, valid) => {
if (err) return callback(err);
if (!valid) return callback(null, true);
const hooks = NormalModule.getCompilationHooks(compilation);
// 如果 buildInfo.snapshot 是合法的,触发 CompilationHook.needBuild => callAsync
hooks.needBuild.callAsync(this, context, (err, needBuild) => {
// ......
callback(null, !!needBuild);
});
});
}
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
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
module.needBuild 是对 build 性能很好的优化手段。
# build
# Build
build 内部调用 _doBuild
。现在我们来看下 Build 的过程。
// lib/NormalModule.js
_doBuild(options, compilation, resolver, fs, hooks, callback) {
// 创建 loaderContext,提供给 loader 的 context(方法和属性)
const loaderContext = this._createLoaderContext(
resolver,
options,
compilation,
fs,
hooks
);
// 初始化 fileDependencies、contextDependencies、missingDependencies 容器
this.buildInfo.fileDependencies = new LazySet();
this.buildInfo.contextDependencies = new LazySet();
this.buildInfo.missingDependencies = new LazySet();
this.buildInfo.cacheable = true;
// 触发 CompilationHook.beforeLoaders => call
hooks.beforeLoaders.call(this.loaders, this, loaderContext);
// ......
if (this.loaders.length > 0) {
// 初始化 buildDependencies
this.buildInfo.buildDependencies = new LazySet();
}
// 调用 loader-runner.runLoaders 加载源文件
runLoaders(
{
// resource path
resource: this.resource,
// module loaders
loaders: this.loaders,
// module loader context
context: loaderContext,
// 处理资源的回调
processResource: (loaderContext, resourcePath, callback) => {
const resource = loaderContext.resource;
// 获取 resource 的 scheme
const scheme = getScheme(resource);
// CompilationHook.readResource 是 HookMap,可使用 .for
// 通过不同的 scheme 选择 HookMap 中的 Hook
hooks.readResource
.for(scheme)
// 调用 CompilationHook.readResource.scheme => callAsync
.callAsync(loaderContext, (err, result) => {
// ......
return callback(null, result);
});
},
},
(err, result) => {
// ......
// 收集 fileDependencies,contextDependencies 和 missingDependencies
this.buildInfo.fileDependencies.addAll(result.fileDependencies);
this.buildInfo.contextDependencies.addAll(result.contextDependencies);
this.buildInfo.missingDependencies.addAll(result.missingDependencies);
// 缓存 loader
for (const loader of this.loaders) {
this.buildInfo.buildDependencies.add(loader.loader);
}
this.buildInfo.cacheable = this.buildInfo.cacheable && result.cacheable;
// 处理 loader 返回的结果
processResult(err, result.result);
}
);
}
const processResult = (err, result) => {
// ......
// 获取 source、sourceMap 和 extraInfo
const source = result[0];
const sourceMap = result.length >= 1 ? result[1] : null;
const extraInfo = result.length >= 2 ? result[2] : null;
// ......
// 创建 RawSource 对象
this._source = this.createSource(
options.context,
this.binary ? asBuffer(source) : asString(source),
sourceMap,
compilation.compiler.root
);
// ......
// 如果 extraInfo 中提供了 AST,则缓存
this._ast =
typeof extraInfo === "object" &&
extraInfo !== null &&
extraInfo.webpackAST !== undefined
? extraInfo.webpackAST
: null;
return callback();
};
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
核心作用如下:
- runLoaders 调用
loader-runner
包的方法处理源文件,将源文件处理为 webpack 识别的 JavaScript 源代码。创建 loaderContext 可传递给 loader 使用。 - loader 处理的结果包含 Dependencies 和 content。Dependencies 将收集到 fileDependencies,contextDependencies 和 missingDependencies。content 将处理为 RawSource 对象。content 可能为 string 或者 buffer(二进制)。
# Parse
build(options, compilation, resolver, fs, callback) {
// ......
// 调用 _doBuild 完成 Build 工作,在回调中完成 Parse 工作。
return this._doBuild(options, compilation, resolver, fs, hooks, (err) => {
// 触发 CompilationHook.beforeParse => call
hooks.beforeParse.call(this);
// 如果配置了 module.noParse,退出 parse
const noParseRule = options.module && options.module.noParse;
if (this.shouldPreventParsing(noParseRule, this.request)) {
// We assume that we need module and exports
this.buildInfo.parsed = false;
this._initBuildHash(compilation);
return handleBuildDone();
}
let result;
// 获取到源代码(string | buffer)
const source = this._source.source();
// 使用 Parser parse 源代码为 AST
// 注意:result 不是 AST 而是 state,AST 在 JavaScriptParser parse 中 触发 JavaScriptParserHook.finish
result = this.parser.parse(this._ast || source, {
source,
current: this,
module: this,
compilation: compilation,
options: options,
});
handleParseResult(result);
});
}
const handleParseResult = (result) => {
// 依赖排序
this.dependencies.sort(
concatComparators(
compareSelect((a) => a.loc, compareLocations),
keepOriginalOrder(this.dependencies)
)
);
// 初始化 build hash
this._initBuildHash(compilation);
this._lastSuccessfulBuildMeta = this.buildMeta;
// 完成 build
return handleBuildDone();
};
const handleBuildDone = () => {
// 触发 CompilationHook.beforeSnapshot => call
hooks.beforeSnapshot.call(this);
// 获取创建快照的 options
const snapshotOptions = compilation.options.snapshot.module;
// 如果没有配置 cacheable 或者 snapshotOptions 直接 callback
if (!this.buildInfo.cacheable || !snapshotOptions) {
return callback();
}
// ......
// 检查依赖
checkDependencies(this.buildInfo.fileDependencies);
checkDependencies(this.buildInfo.missingDependencies);
checkDependencies(this.buildInfo.contextDependencies);
// ......
// 为依赖(file/context/missingDependencies)创建快照,写入 filesystem
// convert file/context/missingDependencies into filesystem snapshot
compilation.fileSystemInfo.createSnapshot(
startTime,
this.buildInfo.fileDependencies,
this.buildInfo.contextDependencies,
this.buildInfo.missingDependencies,
snapshotOptions,
(err, snapshot) => {
// ......
// 清空依赖并缓存快照
this.buildInfo.fileDependencies = undefined;
this.buildInfo.contextDependencies = undefined;
this.buildInfo.missingDependencies = undefined;
this.buildInfo.snapshot = snapshot;
return callback();
}
);
};
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
74
75
76
77
78
79
80
81
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
74
75
76
77
78
79
80
81
总结 build 的核心作用:
- 调用 _doBuild 完成 Build 工作,在回调中完成 Parse 工作。Build 就是使用匹配的 Loader 加载源文件为 source,Parse 就是根据源码生成 AST。
- 如果配置了 module.noParse,会跳过 Parse 过程。
- Parser.parse 将 source 转换为 AST。
NormalModule
是在NormalModuleFactory
中创建的,在 compiler.compile 中 newCompilationParams 函数中会创建 normalModuleFactory。NormalModule 在初始化时在 NormalModuleFactoryHook.factorize 触发 NormalModuleFactoryHook.resolve,在回调里执行 getParser。在调用 NormalModuleFactory.create 时即 _factorizeModule 中factory.create
时触发此钩子。- parser 和 generator 是在内置插件
JavascriptModulesPlugin
中加载的,在 CompilerHook.compilation 中 normalModuleFactoryHook.createParser 和 normalModuleFactoryHook.createGenerator 分别创建JavascriptParser
和JavascriptGenerator
。参见lib/javascript/JavascriptModulesPlugin.js
和lib/NormalModuleFactory.js
。
# JavascriptParser
// lib/javascript/JavascriptParser.js
const { Parser: AcornParser } = require("acorn");
const { importAssertions } = require("acorn-import-assertions");
const parser = AcornParser.extend(importAssertions);
parse(source, state) {
// ......
const ast = JavascriptParser._parse(source, {
sourceType: this.sourceType,
onComment: comments,
onInsertedSemicolon: pos => semicolons.add(pos)
});
// ......
// JavaScriptParserHook.program 为 SyncBailHook
if (this.hooks.program.call(ast, comments) === undefined) {
// 标记内部标记 isStrict、isAsmJs
this.detectMode(ast.body);
// preWalk AST
this.preWalkStatements(ast.body);
// 重置 prevStatement
this.prevStatement = undefined;
// blockPreWalk
this.blockPreWalkStatements(ast.body);
// 重置 prevStatement
this.prevStatement = undefined;
// Walk AST
this.walkStatements(ast.body);
}
// 触发 JavaScriptParserHook.finish => call
this.hooks.finish.call(ast, comments);
return state;
}
_parse(code, options) {
// ......
const ast = parser.parse(code, parserOptions);
// ......
return ast;
}
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
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
通过如上的过程,我们了解到 Module 将会经过 Build 和 Parse 的过程,这两个过程分别将文件加载为源代码、将源代码转换为 AST。Build 的过程是经由 loader-runner
包完成的,而 Parse 包是经由 acorn
包完成的。注意:由代码字符串生成 AST 的具体过程,此处不详细探讨,在 acorn 的源码解析中,我们会详细探讨。
# 参考
编辑 (opens new window)
上次更新: 2022/09/06, 14:25:16