webpack工作流程
webpack构建的核心是完成内容的转换和资源的合并。
分初始化阶段、构建阶段、生成阶段来讲解webpack工作流程。
初始化阶段
- 初始化参数
将命令行参数和用户配置文件进行合并,使用schema-utils库的getValidateSchema方法进行参数校验
- 创建编译对象、实例化配置信息
webpack支持多个配置对象,比如一个library有多个构建目标,就需要传入多个配置对象,每个配置对象都要执行。compiler对象包含了webpack环境所有的配置信息,比如options、loaders、plugins。
createCompiler
源码123行
if (Array.isArray(options)) {
compiler = createMultiCompiler{options}
...
}else {
compiler = createCompiler(options)
...
}
const createCompiler = rawOptions => {
const options = getNormalizedWebpackOptions(rawOptions);
applyWebpackOptionsBaseDefaults(options);
const compiler = new Compiler(options.context, options);
new NodeEnvironmentPlugin({
infrastructureLogging: options.infrastructureLogging
}).apply(compiler);
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();
new WebpackOptionsApply().process(options, compiler);
compiler.hooks.initialize.call();
return compiler;
};
接下来针对createCompiler创建流程细化
NodeEnvironmentPlugin
实例化NodeEnvironmentPlugin
//65行
new NodeEnvironmentPlugin({
infrastructureLogging: options.infrastructureLogging
}).apply(compiler);
NodeEnvironmentPlugin可以对文件输入、输出、缓存、监听。源码
class NodeEnvironmentPlugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
...
//文件输入
compiler.inputFileSystem = new CachedInputFileSystem(fs, 60000);
//文件输出
compiler.outputFilesSystem = fs; //const fs = require("graceful-fs");
//文件缓存
compiler.intermdiateFileSystem = fs;
//文件监听
compiler.watchFileSystem = new NodeMatchFileSystem{ compiler.inputFileSystem };
//项目配置插件提供了`inputFileSystem`对象就使用项目提供的
compiler.hooks.beforeRun.tap("NodeEnvironmentPlugin", compiler => {
if (compiler.inputFileSystem === inputFileSystem) {
compiler.fsStartTime = Date.now();
inputFileSystem.purge();
}
}
}
}
解构options.plugins(注册plugins)
判断plugins属性是否为数组,是数组然后解构为一个一个的plugin,直达
遍历arguments对象,arguments成员->插件实例调用自身的apply方法执行注册流程。
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.options的重新赋值
compiler.hooks.environment.call();
compiler.hooks.afterEnvironment.call();
new WebpackOptionsApply().process(options, compiler);
在WebpackOptionsApply类里面提供一个process方法。process(options, compiler) { }。而它主要做了以下几件事情
- 将传入的options上的属性赋值给compiler对于对象
- 根据options配置是否需要注册一些内部自带的插件和resolverFactory.hooks
- 解析entry
new EntryOptionPlugin().apply(compiler);compiler.hooks.entryOption.call(options.context, options.entry); - 返回options
resolverFactory.hooks主要包含normal、context、loader
初始化
compiler.hooks.initialize.call();
构建阶段
构建阶段主要有以下几个流程:
- 开始编译
- 确认编译入口(读取entires配置,递归遍历所有入口文件)
- 编译模块(从entry文件开始,调用loader对模块进行转译,通过
acorn转换为AST) - 完成模块编译
compiler.run
const run = () => {
this.hooks.beforeRun.callAsync(this, err => {
if (err) return finalCallback(err);
this.hooks.run.callAsync(this, err => {
if (err) return finalCallback(err);
this.readRecords(err => {
if (err) return finalCallback(err);
this.compile(onCompiled);
});
});
});
};
compiler.compile
在compile方法依次执行了以下几个hook
- beforeCompile
- compile
- make
- finishMake
- afterCompile
在complie阶段通过const compilation = this.newCompilation(params);实例化了Compilation,对于compilation对象表现了当前的模块资源、编译生成的资源、变化的文件以及被跟踪依赖的状态信息,代表一次资源的构建。
生成阶段
输出资源、写入文件系统
关于Loader、Plugin
Loader 是对一个个的文件进行处理,它是一个转换器,将A文件进行编译成B文件。
Plugin 是贯穿在整个构建生命周期,可以对不同阶段的构建产物进行处理。
loader
webpack的loader本质是一个ESM模块,它导出一个函数,这个函数就是对打包资源进行转换然后输出结果。
function loader(source) => {
//source 资源文件内容
//TODO 处理过程
return '加工后的输出'
}
loader.pitch = function () {
//pitch loader
//TODO
}
module.exports = loader;
plugin
plugin是导出一个class,其中类包含了一个固定的方法名为apply,apply方法的第一个参数为compiler,我们可以通过拿到compiler对象的hooks进行添加事件.
Tapable: 一个工具库,其中包含了很多hook。
class EchoPlugin {
constructor(options){
//options
}
apply(compiler) {
//tap: 同步/异步;topAsync:异步
//可以在compiler任意生命周期内进行操作,举例emit的钩子
compiler.hooks.emit.tap('EchoPlugin', compilation => {
//TODO
}
}
}
总体流程图
推荐链接剑指前端Offer