webpack 4 源码主流程分析(六):构建 module(上)

初始化 module

接上文,在 resolver 函数回调里,触发 normalModuleFactory.hooks:afterResolve 之后,回调里执行:

1
2
3
4
5
6
7
8
9
10
11
let createdModule = this.hooks.createModule.call(result); // result 即为 resolver 返回的组合对象 data
if (!createdModule) {
if (!result.request) {
return callback(new Error('Empty dependency (no request)'));
}

createdModule = new NormalModule(result);
}
createdModule = this.hooks.module.call(createdModule, result);

return callback(null, createdModule);

这里触发 normalModuleFactory.hooks:createModule,如果钩子里没有项目配置的自定义 module,则使用 webpack 生成的 module

add module

得到 module 实例,接着触发 normalModuleFactory.hooks:module 之后,跳出 factory 函数,执行 factory 函数回调进行依赖缓存后,跳出 create 函数执行 moduleFactory.create 的回调。回调里执行:

1
2
3
4
5
6
7
const addModuleResult = this.addModule(module); // 将这个 `module` 保存到全局的 `Compilation.modules` 数组中和 `_modules` 对象中,判断`_modules`是否有该 module 来设置是否已加载的标识
module = addModuleResult.module;

onModule(module); // 如果是入口文件还会将 modules 保存到 `Compilation.entries`

dependency.module = module;
module.addReason(null, dependency); // 添加该 `module` 被哪些模块依赖

然后调用 this.buildModule 进入 build 阶段。该方法做了回调缓存后,触发 compilation.hooks:buildModule,然后执行 module.build()

构建 module

/node_modules/webpack/lib/NormalModule.js 文件里执行 module.build, 设置一些属性后,直接调用了 this.doBuild

该方法里先执行了 this.createLoaderContext 得到loaderContext,为所有的 loader 提供上下文环境并共享,然后调用了 runLoaders

1
2
3
4
5
6
7
8
9
10
11
runLoaders(
{
resource: this.resource,
loaders: this.loaders,
context: loaderContext,
readResource: fs.readFile.bind(fs),
},
(err, result) => {
//...
}
);

loader-runner

该方法来自 loader-runner,通过各种 loader 处理源码后,得到一个处理后的 stringbuffer(可能还有个 sourcemap)。

还可以解析自定义 loader 编写一个 loader

主要流程为:

runLoaders -> iteratePitchingLoaders(按正序 require 每个 loader) -> loadLoader(对应的 loader 导出的函数赋值到 loaderContext.loader[].normal、pitch函数赋值到loaderContext.loader[].pitch,然后执行pitch函数(如果有的话)) -> processResource(转换 buffer 和设置 loaderIndex) -> iterateNormalLoaders(倒序执行所有 loader)-> runSyncOrAsync(同步或者异步执行 loader)

pitch 函数

  • 每个 loader 可以挂载一个 pitch 函数,该函数主要是用于利用 modulerequest,来提前做一些拦截处理的工作,并不实际处理 module 内容文档

  • 正序 require loader 并执行其 pitch 方法( loadLoader 里),在执行后的回调里,如果有除了 err 的参数还有其他参数,则执行 iterateNormalLoaders 越过剩下的未 requireloader 直接进入到执行 loader 的步骤。如果想没有其他参数,则执行 iteratePitchingLoaders 进行下个 loaderrequire。如代码所示:

    1
    2
    3
    4
    5
    6
    if (args.length > 0) {
    loaderContext.loaderIndex--;
    iterateNormalLoaders(options, loaderContext, args, callback);
    } else {
    iteratePitchingLoaders(options, loaderContext, callback);
    }
  • 倒序执行每个 loadernormal 方法 。

核心代码解析

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// 该方法按正序 require 每个 loader
function iteratePitchingLoaders(options, loaderContext, callback) {
// abort after last loader 读取所有 loader 后,执行 processResource 方法
if (loaderContext.loaderIndex >= loaderContext.loaders.length) return processResource(options, loaderContext, callback);

var currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex]; //选择第一个 loader

// iterate 增序后递归读取下一个 loader
if (currentLoaderObject.pitchExecuted) {
loaderContext.loaderIndex++;
return iteratePitchingLoaders(options, loaderContext, callback);
}

// load loader module 加载该 loader 模块
// 对应的 loader 导出的函数赋值到 loaderContext.loader[].normal
loadLoader(currentLoaderObject, function (err) {
if (err) {
loaderContext.cacheable(false);
return callback(err);
}
var fn = currentLoaderObject.pitch; //loadLoader 里会把 module 赋值到 loader.normal, pitch 赋值到 loader.pitch
currentLoaderObject.pitchExecuted = true;
if (!fn) return iteratePitchingLoaders(options, loaderContext, callback);

// 如果有的话,开始执行 pitch 函数,根据参数情况决定是否继续读取剩下的loader
runSyncOrAsync(fn, loaderContext, [loaderContext.remainingRequest, loaderContext.previousRequest, (currentLoaderObject.data = {})], function (err) {
if (err) return callback(err);
var args = Array.prototype.slice.call(arguments, 1);
if (args.length > 0) {
loaderContext.loaderIndex--;
iterateNormalLoaders(options, loaderContext, args, callback);
} else {
iteratePitchingLoaders(options, loaderContext, callback);
}
});
});
}

// 转换 buffer 和设置 loaderIndex
function processResource(options, loaderContext, callback) {
// set loader index to last loader 获取最后一个 loader 的 index
loaderContext.loaderIndex = loaderContext.loaders.length - 1;

var resourcePath = loaderContext.resourcePath;
if (resourcePath) {
loaderContext.addDependency(resourcePath);
// 转换为 buffer
options.readResource(resourcePath, function (err, buffer) {
if (err) return callback(err);
options.resourceBuffer = buffer; //得到buffer
iterateNormalLoaders(options, loaderContext, [buffer], callback);
});
} else {
iterateNormalLoaders(options, loaderContext, [null], callback);
}
}

//倒序执行所有 loader
function iterateNormalLoaders(options, loaderContext, args, callback) {
if (loaderContext.loaderIndex < 0) return callback(null, args); //执行完所有 loader 后退出,去执行 iteratePitchingLoaders 回调即 runLoaders 的回调

var currentLoaderObject = loaderContext.loaders[loaderContext.loaderIndex]; //获取对应 loader

// iterate 减序后递归执行下一个 loader
if (currentLoaderObject.normalExecuted) {
loaderContext.loaderIndex--;
return iterateNormalLoaders(options, loaderContext, args, callback);
}

var fn = currentLoaderObject.normal;
currentLoaderObject.normalExecuted = true;
if (!fn) {
return iterateNormalLoaders(options, loaderContext, args, callback);
}

convertArgs(args, currentLoaderObject.raw);

//执行 loader 函数
runSyncOrAsync(fn, loaderContext, args, function (err) {
//loader 执行结果的回调
if (err) return callback(err);

var args = Array.prototype.slice.call(arguments, 1); // arg:[] 为 loader 转换结果(字符串或者buffer+可能有的sourcemap)
iterateNormalLoaders(options, loaderContext, args, callback); //递归,并将转换结果一并传入
});
}

//同步或者异步执行 loader 函数
function runSyncOrAsync(fn, context, args, callback) {
var isSync = true;
var isDone = false;
var isError = false; // internal error
var reportedError = false;
//异步处理
context.async = function async() {
if (isDone) {
if (reportedError) return; // ignore
throw new Error('async(): The callback was already called.');
}
isSync = false;
return innerCallback;
};
// 异步后会执行此方法,loader 的结果会作为参数传导出来
var innerCallback = (context.callback = function () {
if (isDone) {
if (reportedError) return; // ignore
throw new Error('callback(): The callback was already called.');
}
isDone = true;
isSync = false;
try {
callback.apply(null, arguments); // arguments 为 loader 结果,第一个值为 null 第二个为字符串或者 buffer,第三个为 SourceMap
} catch (e) {
//...
}
});
try {
var result = (function LOADER_EXECUTION() {
return fn.apply(context, args); //执行 loader 函数,参数传递前一个 loader 的执行结果
})();
if (isSync) {
isDone = true;
if (result === undefined) return callback();
if (result && typeof result === 'object' && typeof result.then === 'function') {
return result.then(function (r) {
callback(null, r);
}, callback);
}
return callback(null, result);
}
} catch (e) {
//...
}
}

本章小结

  1. 实例化 NormalModule 得到初始化的 module,然后在 build 过程中先 run loader 处理源码,得到一个编译后的字符串或 buffer
  2. run loader 的过程中,先正序执行了每个 loaderpitch ,然后倒序执行了每个 loadernormal
---- 本文结束,感谢您的阅读 ----