前言
上文说到 webpack
准备好了参数,要创建 compiler
对象了。
创建完之后,则会执行 compiler.run
来开始编译,本文将阐述 new Compiler
到 compiler.run()
中间的过程。
整体过程都发生在createCompiler
这个函数体内。
/**
* @param {WebpackOptions} rawOptions options object
* @returns {Compiler} a compiler
*/
const createCompiler = rawOptions => {
new Compiler
在new之前,webpack会完成一次基础参数的初始化,这里只给日志输出格式和context进行了赋值
applyWebpackOptionsBaseDefaults(options);
webpack/lib/Compiler.js
是整个webpack的编译核心流程。new Compiler
的时候先在Tapable
注册了一堆钩子,例如常见的watch-run,run, before-run, 等等。更多的钩子可以在这里查看。
初始化文件操作
new NodeEnvironmentPlugin({
infrastructureLogging: options.infrastructureLogging
}).apply(compiler);
这里是在拓展compiler对象,增加对文件的一些操作,例如输入
,输出
,监听
,缓存
等方法。同时还注册了一个beforeRun
钩子的回调。
apply(compiler) {
const { infrastructureLogging } = this.options;
compiler.infrastructureLogger = createConsoleLogger({
level: infrastructureLogging.level || "info",
debug: infrastructureLogging.debug || false,
console:
infrastructureLogging.console ||
nodeConsole({
colors: infrastructureLogging.colors,
appendOnly: infrastructureLogging.appendOnly,
stream: infrastructureLogging.stream
})
});
compiler.inputFileSystem = new CachedInputFileSystem(fs, 60000);
const inputFileSystem = compiler.inputFileSystem;
compiler.outputFileSystem = fs;
compiler.intermediateFileSystem = fs;
compiler.watchFileSystem = new NodeWatchFileSystem(
compiler.inputFileSystem
);
compiler.hooks.beforeRun.tap("NodeEnvironmentPlugin", compiler => {
if (compiler.inputFileSystem === inputFileSystem) {
compiler.fsStartTime = Date.now();
inputFileSystem.purge();
}
});
}
这里的fs,不是nodejs的 file system,是用了一个第三方包graceful-fs
注册插件
if (Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else {
plugin.apply(compiler);
}
}
}
接下来webpack会把options注册的插件,都注册一遍。传入compiler
对象给插件内部使用,插件通过 compiler
提供的hook,可以在编译全流程注册钩子的回调函数。同时有些compiler
钩子又传入了compilation
对象,又可以在资源构建的时候注册 compilation
钩子回调。
environment ready
插件注册完之后,webpack 又一次给options赋值了一次默认参数。为什么和前面的applyWebpackOptionsBaseDefaults
一起呢。
这里调用了
applyWebpackOptionsDefaults(options);
又加了一波默认值。
加完之后调用了environment
和afterEnvironment
两个钩子。
compiler.hooks.environment.call();
compiler.hooks.afterEnvironment.call();
注册内置插件
环境初始化之后,webpack还需要执行一下它自己内部的默认插件。
new WebpackOptionsApply().process(options, compiler);
这里会根据你的配置,执行对应的插件。
挑几个和钩子有关系的讲讲,
解析entry
new EntryOptionPlugin().apply(compiler);
compiler.hooks.entryOption.call(options.context, options.entry);
这里就是生成构建所需的entry数据结构
/** @type {EntryOptions} */
const options = {
name,
filename: desc.filename,
runtime: desc.runtime,
layer: desc.layer,
dependOn: desc.dependOn,
publicPath: desc.publicPath,
chunkLoading: desc.chunkLoading,
wasmLoading: desc.wasmLoading,
library: desc.library
};
然后再调用EntryPlugin
在applay
方法里注册Compiler.hooks:compilation, make 这两个钩子函数。将来等compiler
对象里面,触发make
钩子的时候,在EntryPlugin
注册的回调会触发complition.addEntry(context, dep, options)
开始编译
这里是重点,不然找不到开始的入口
compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => {
compilation.addEntry(context, dep, options, err => {
callback(err);
});
}
);
注册resloverFactory钩子
webpack本身的一些插件调用完成之后,会调用afterPlugin
这个钩子。
compiler.hooks.afterPlugins.call(compiler);
接下来 webpack 在 compiler.resolverFactory 上注册了resolveOptions
钩子
compiler.resolverFactory.hooks.resolveOptions
.for("normal")
.tap("WebpackOptionsApply", resolveOptions => {
resolveOptions = cleverMerge(options.resolve, resolveOptions);
resolveOptions.fileSystem = compiler.inputFileSystem;
return resolveOptions;
});
compiler.resolverFactory.hooks.resolveOptions
.for("context")
.tap("WebpackOptionsApply", resolveOptions => {
resolveOptions = cleverMerge(options.resolve, resolveOptions);
resolveOptions.fileSystem = compiler.inputFileSystem;
resolveOptions.resolveToContext = true;
return resolveOptions;
});
compiler.resolverFactory.hooks.resolveOptions
.for("loader")
.tap("WebpackOptionsApply", resolveOptions => {
resolveOptions = cleverMerge(options.resolveLoader, resolveOptions);
resolveOptions.fileSystem = compiler.inputFileSystem;
return resolveOptions;
});
这里的目的是为 Factory.createResolver
提供默认的参数对象(含有相关的 resolve 项目配置项)。
然后再调用afterResolvers
钩子
compiler.hooks.afterResolvers.call(compiler);
初始化完成
到目前为止,compiler 对象上已经有了足够的东西开始我们的编译,就告诉外界初始化完成,以webpack的调性,必然还有一个钩子的触发。
compiler.hooks.initialize.call();
结语
webpack 编译前的所有事情已经交待清楚,下一篇将开始启动编译。
文章中提到的各种钩子的注册,烦请读者记下来,后续在整个编译过程中,前面注册的一些钩子,经常会在你遗漏的地方触发,这也是调试webpack过程中的一个痛点。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。