流程开始
入口
webpack
进行打包时,会根据命令找到打包的真实入口:node_modules/webpack/lib/webpack.js
文件下的webpack
函数,其主要代码如下:
const webpack = (options, callback) => {
const create = () => {
compiler = createCompiler(options);
return { compiler, watch, watchOptions };
};
const { compiler, watch } = create();
return compiler;
}
options
是webpack
配置文件和命令参数合并后的配置。create
方法通过createCompiler
创建一个Compiler
对象,该对象用于管理整个打包过程的主流程:
const createCompiler = rawOptions => {
// ...
// 1. 创建 Compiler 对象
const compiler = new Compiler(options.context);
compiler.options = options;
// 2. 用户在 webpack 配置中定义的插件进行应用
if (Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else {
plugin.apply(compiler);
}
}
}
applyWebpackOptionsDefaults(options);
compiler.hooks.environment.call();
compiler.hooks.afterEnvironment.call();
// 3. 内置插件应用
new WebpackOptionsApply().process(options, compiler);
compiler.hooks.initialize.call();
return compiler;
};
在获取了Compiler
对象后,开始应用用户自定义插件和webpack
内置插件。并且此时内置的EntryOptionPlugin
监听的entryOptions
钩子会被立即调用:
new EntryOptionPlugin().apply(compiler);
compiler.hooks.entryOption.call(options.context, options.entry);
EntryOptionPlugin
插件会在后续处理entry
时起到关键作用,因此需要留意。
run方法
在获取到compiler
对象后,会调用其run
方法,该方法准确的调用位置在webpack-cli
包当中:
const cli = new WebpackCLI();
await cli.run(args);
这里的cli
就是返回的compiler
对象。在webpack/lib/Compiler.js
中找到run
方法:
const run = () => {
this.hooks.beforeRun.callAsync(this, err => {
this.hooks.run.callAsync(this, err => {
this.readRecords(err => {
this.compile(onCompiled);
});
});
});
};
首先会执行beforeRun/run
钩子,这些不是很重要,可以暂且忽略。现在着重看一下compile
方法。
compile方法
compile
方法精简后的代码如下:
compile(callback) {
const params = this.newCompilationParams();
this.hooks.beforeCompile.callAsync(params, err => {
this.hooks.compile.call(params);
const compilation = this.newCompilation(params);
this.hooks.make.callAsync(compilation, err => {
this.hooks.finishMake.callAsync(compilation, err => {
process.nextTick(() => {
compilation.finish(err => {
compilation.seal(err => {
this.hooks.afterCompile.callAsync(compilation, err => {
return callback(null, compilation);
});
});
});
});
});
});
});
}
这里我们大概可以知道编译的主要流程:
beforeCompile => compile => make => finishMake => finish => seal => afterCompile => 执行回调
其中make
阶段是编译阶段,处理依赖和模块关系以及模块的编译。而seal
阶段主要是处理module
和chunk
的关系。这两个阶段都比较重要,后面会单独讲解。现在回到compile
方法的第一行代码:
const params = this.newCompilationParams()
// 实例化两个参数
newCompilationParams() {
const params = {
normalModuleFactory: this.createNormalModuleFactory(),
contextModuleFactory: this.createContextModuleFactory()
};
return params;
}
该方法返回了normalModuleFactory
和contextModuleFactory
两个工厂函数。其中normalModuleFactory
处理普通模块的创建,如import A from './a.js'
。而contextModuleFactory
处理如import './a.js'
模块的创建。
接下来是实例化一个compilation
对象:
// 实例化 compilation
const compilation = this.newCompilation(params);
newCompilation(params) {
const compilation = this.createCompilation();
compilation.name = this.name;
compilation.records = this.records;
this.hooks.thisCompilation.call(compilation, params);
this.hooks.compilation.call(compilation, params);
return compilation;
}
此时会触发thisCompilation
和compilation
两个钩子,用于监听打包过程中处理compilation
的事件回调。
onCompiled方法
待编译完成之后,会执行callback
,即onCompiled
方法,精简后的代码如下:
const onCompiled = (err, compilation) => {
this.emitAssets(compilation, err => {
this.emitRecords(err => {
this.hooks.done.callAsync(stats, err => {
this.cache.storeBuildDependencies(
compilation.buildDependencies,
err => {
return finalCallback(null, stats);
}
);
});
});
});
};
const finalCallback = (err, stats) => {
this.hooks.afterDone.call(stats);
};
该方法的主要任务是通过emitAssets
方法将打包后的chunk
正确的输出成文件形式:
emitAssets(compilation, callback) {
const emitFiles = err => {
// ...
};
this.hooks.emit.callAsync(compilation, err => {
outputPath = compilation.getPath(this.outputPath, {});
mkdirp(this.outputFileSystem, outputPath, emitFiles);
});
}
最终调用的主要钩子为:
emit => done => afterDone
总结
webpack
源码中有几个核心的概念:Compiler
、Compilation
和插件。
Compiler
对象主要用于控制打包过程的整个流程,如:
- run => 开始启动编译过程
- make => 进行编译
- seal => 编译完成,形成chunk
- emit => 输出文件
而Compilation
对象代表在这个打包过程中的打包产物,用于记录打包过程中的内容,如dependencies/modules/chunks
等等。
基于主打包流程和打包产物。插件则是监听打包过程中的某一个或多个过程,然后对compilation
等内容进行加工处理,最终得到打包好的内容。