在上一篇文章已经对webpack_cli启动过程进行了大致的分析,程序到最后就是执行webpack.run方法。现在对webpack的源码进行进一步的分析。
准备工作:
1、在github下载webpack的源码
2、在源码目录创建需要编译的源码和webpack.config.js配置
webpack/webpack-main/examples/demo
// build.js
const webpack = require("../../lib/webpack");
const config = require("./webpack.config");
const compiler = webpack(config);
compiler.run((err, stats) => {
if (err) {
console.error(err);
} else {
console.log(stats);
}
});
// webpack.config.js
const path = require("path");
const HtmlWebPackPlugin = require('html-webpack-plugin');
module.exports = {
mode: "development",
devtool: "source-map",
context: path.resolve(__dirname, "."),
entry: "./src/main.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "./build")
},
module: {
rules: [
{
test: /\.js$/,
use: "babel-loader"
}
]
},
plugins: [
new HtmlWebPackPlugin()
]
};
// main.js
console.log("Hello Webpack");
3、配置node.js调试环境
{
"version": "0.2.0",
"configurations": [
{
"type": "pwa-node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"/**"
],
"program": "${workspaceFolder}/examples/demo/build.js",
}
]
}
const compiler = webpack(config)
1、根据是否有回调处理,然后调用create()函数
const { compiler, watch } = create();
2、在create中根据webpack.config.js配置的信息处理是数组还是单个对象配置
if (Array.isArray(options)) {
compiler = createMultiCompiler();
} else {
compiler = createCompiler(webpackOptions);
}
3、如果是数组就调用createMultiCompiler,在函数中遍历调用createCompiler
4、如果是单个对象就createCompiler处理
5、返回complier
下面就来详细分析createCompiler函数中做的操作?
1、通过getNormalizedWebpackOptions()处理options
2、合并默认的配置信息applyWebpackOptionsBaseDefaults()
3、创建const compiler = new Compiler(options.context, options);
Compiler构造函数中初始化参数,在this.hooks中创建很多hooks
this.hooks = Object.freeze({
initialize: new SyncHook([]),
...
})
4、初始化NodeEnvironmentPlugin
new NodeEnvironmentPlugin({
infrastructureLogging: options.infrastructureLogging
}).apply(compiler);
5、注册webpack.config.js中plugins中的插件
if (Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else {
plugin.apply(compiler);
}
}
}
6、applyWebpackOptionsDefaults
7、调用environment、afterEnvironment hook
applyWebpackOptionsDefaults(options);
compiler.hooks.environment.call();
compiler.hooks.afterEnvironment.call();
8、new WebpackOptionsApply().process(options, compiler) 把webpack.config.js中的配置初始化为插件、赋值、一些hook的调用等
compiler.outputPath = options.output.path;
compiler.recordsInputPath = options.recordsInputPath || null;
compiler.recordsOutputPath = options.recordsOutputPath || null;
compiler.name = options.name;
if (options.externals) {
const ExternalsPlugin = require("./ExternalsPlugin");
new ExternalsPlugin(options.externalsType, options.externals).apply(
compiler
);
}
...省略
9、initialize hook的调用
10、返回compiler
compiler.hooks.initialize.call();
return compiler;
到这里构造函数的流程就已经完成了。
run函数中大多数代码是hook函数的触发和回调处理。
run函数的整个流程如下:
1、this.hooks.beforeRun.callAsync
2、this.hooks.run.callAsync
3、this.compile(onCompiled)
4、onCompiled = (err, compilation) => {}
在onCompiled中代码执行顺序如下:
this.emitAssets(compilation, err => {} -> this.hooks.emit.callAsync(compilation, err => {}
-> this.hooks.done.callAsync() -> finalCallback -> this.hooks.afterDone.call(stats);
下面来分析下run函数的核心方法,即this.compile编译过程:
1、调用编译之前的hook this.hooks.beforeCompile.callAsync
2、调用编译的hook this.hooks.compile.call(params)
3、创建compilation
const compilation = this.newCompilation(params)
4、调用编译的hook
this.hooks.make.callAsync(compilation)
这个时候就会来到EntryPlugin的compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => {}方法
后面分析这里做了什么操作?
说明:
如何能来到这里的呢?hooks.make是在什么时候注册的呢?
早在构造函数执行的时候new WebpackOptionsApply().process(options, compiler)中就注册了EntryOptionPlugin插件
new EntryOptionPlugin().apply(compiler);
技巧:先搜索源码中hooks.make监听的地方然后下断点
源码搜索new EntryPlugin().apply()的地方.然后下断点,然后查看函数调用栈找到EntryOptionPlugin.apply()方法
5、完成make的hook
this.hooks.finishMake.callAsync(compilation)
6、compilation.finish()
7、输出资源处理压缩优化等操作
compilation.seal()
8、调用完成之后的 hook
this.hooks.afterCompile.callAsync(compilation)
以上就是run方法整个流程的主干部分。
下面来分析编译过程的详细操作也就是EntryPlugin的compiler.hooks.make.tapAsync函数。
主要梳理方法的调用过程,不好理解的地方会做出说明。
1、compilation.addEntry()
2、 this._addEntryItem(context, entry, "dependencies", options, callback);
3、this.addModuleTree()
4、this.handleModuleCreation()
5、this.addModule()
6、this._handleModuleBuildAndDependencies()
7、this.buildModule(module, err => {}
8、this.buildQueue.add(module, callback);
9、_buildModule(module, callback) {}
补充说明:
this.buildQueue.add -> 如何调用到_buildModule()方法中的呢?
在Compilation的构造函数中有如下代码:
this.buildQueue = new AsyncQueue({
name: "build",
parent: this.factorizeQueue,
processor: this._buildModule.bind(this)
});
查看AsyncQueue的源码实现:
this._processor = processor = this._buildModule;
调用add方法最终会触发this._buildModule方法。
add(item, callback) {} -> _ensureProcessing() -> _startProcessing() -> this._processor()
10、_buildModule()
11、module.needBuild()
12、module.build()
build(options, compilation, resolver, fs, callback) {
const AbstractMethodError = require("./AbstractMethodError");
throw new AbstractMethodError();
}
build方法是一个抽象方法,所以调用的子类的方法,但是这个时候并不知道是哪个子类?
所以搜索build()方法全部下断点,得出这里调用的是NormalModule中的build()
12、NormalModule中的_doBuild()
13、runLoaders()
14、返回回调
以上就是整个编译过程的流程分析了。
高清无码大图下载