对webpack核心文件进行了抽离,webpack并没有使用promise、await这样的东西,布满了各种callback回掉函数,部分内容我用await替换只是为了方便看懂逻辑。包括了启动webpack.js、Compiler、Compilation、ModuleFactory等最核心的流程。
webpack.js
function webpack(){
compiler = new Compiler(options.context);
compiler.options = options;
// 往comiler中注册插件
new NodeEnvironmentPlugin().apply(compiler);
// 执行config中的插件
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else {
plugin.apply(compiler);
}
}
compiler.hooks.environment.call();
compiler.hooks.afterEnvironment.call();
// 执行webpack内置插件,EntryOptionPlugin、HarmonyModulesPlugin、LoaderPlugin
compiler.options = new WebpackOptionsApply().process(options, compiler);
// 调用run开始编译
compiler.run(callback);
return compiler;
}
Compiler
简化代码
class Compiler extends Tapable {
run(){
// 执行之前,添加一个钩子
await this.hooks.beforeRun.callAsync(this);
await this.hooks.run.callAsync(this);
await this.compile(); // 触发beforeCompile、compile、make、afterCompile
// 将最终的js输出到output的path中
await this.emitAssets(compilation);
// 编译(compilation)完成
this.hooks.done.callAsync();
}
compile(callback) {
// 拿到参数
const params = this.newCompilationParams();
// params 有 normalModuleFactory,contextModuleFactory 两个工厂和 compilationDependencies
// 执行编译前钩子
await this.hooks.beforeCompile.callAsync(params);
// 告诉插件编编译即将启动
this.hooks.compile.call(params);
// 创建compilation
const compilation = this.newCompilation(params); // 触发thisCompilation、compilation
// make 开始编译
await this.hooks.make.callAsync(compilation);// 传入新建的Compilation
// 触发compilation 的 finishModules 所有模块都完成构建
await compilation.finish();
// 编译(compilation)停止接收新模块时触发
await compilation.seal();
// 编译结束
this.hooks.afterCompile.callAsync(compilation, () => {
return callback(null, compilation);
});
}
newCompilation(params) {
const compilation = new Compilation(this);
// 触发 compilation 事件之前执行
this.hooks.thisCompilation.call(compilation, params);
// 编译(compilation)创建之后,执行插件。
this.hooks.compilation.call(compilation, params);
return compilation;
}
emitAssets(compilation){
// 生成资源到 output 目录之前
await this.hooks.emit.callAsync(compilation);
await this.outputFileSystem.mkdirp(compilation.getPath(this.outputPath));
compilation.getAssets.forEach(item=>{
/*
写入文件
*/
this.hooks.assetEmitted.callAsync(file, content);
});
// 生成资源到 output 目录之后
this.hooks.afterEmit.callAsync(compilation)
}
}
compiler生命周期
钩子 | 时间点 |
---|---|
beforeRun | 执行之前 |
run | 启动一次新的编译 |
beforeCompile | 执行编译前 |
compile | 编译即将启动 |
创建Compilation对象 | |
thisCompilation | 触发 compilation 事件之前 |
compilation | 编译(compilation)创建之后 |
make | 开始编译 |
compilation.finish | 所有模块都完成构建 |
compilation.seal | 对所有module和chunk进行整理,生成编译后的源码,合并、拆分 |
afterCompile | 编译结束 |
emit | 生成资源到 output 目录之前 |
assetEmitted | 每个文件完成写入 |
afterEmit | 生成资源到 output 目录之后 |
done | 完成 |
Compilation
简化代码
class Compilation extends Tapable {
constructor(compiler) {
super();
...
//初始化配置
this.compiler = compiler;
this.resolverFactory = compiler.resolverFactory;
this.inputFileSystem = compiler.inputFileSystem;
this.requestShortener = compiler.requestShortener;
//初始化模版
this.mainTemplate = new MainTemplate(this.outputOptions);
this.chunkTemplate = new ChunkTemplate(this.outputOptions);
this.hotUpdateChunkTemplate = new HotUpdateChunkTemplate(
this.outputOptions
);
this.dependencyFactories = new Map(); // entryPlugin插件 会负责插入值
}
// SingleEntryPlugin 插件的 apply 回调用 addEntry 方法
addEntry(context, entry, name, callback) {
...
this._addModuleChain(context, entry,
module => {
this.entries.push(module);
},
(err, module) => { // 不能放到后面,因为中间某一步回调
...
}
);
}
_addModuleChain(context, dependency, onModule, callback) {
...
// 获取模块工厂
// entryPlugin时候Dep 是 SingleEntryDependency 实例
const moduleFactory = this.dependencyFactories.get(Dep);
// normalModuleFactory
...
//创建模块
let module = await moduleFactory.create({
contextInfo: {
issuer: "",
compiler: this.compiler.name
},
context: context,
dependencies: [dependency]
})
const addModuleResult = this.addModule(module);
module = addModuleResult.module;
onModule(module);
dependency.module = module;
module.addReason(null, dependency);
const afterBuild = () => {
if (addModuleResult.dependencies) {
// 递归处理依赖
this.processModuleDependencies(module, err => {
if (err) return callback(err);
callback(null, module);
});
} else {
return callback(null, module);
}
};
if (addModuleResult.build) {
// 构造模块
this.buildModule(module, false, null, null, err => {
this.semaphore.release();
afterBuild();
});
} else {
this.semaphore.release();
this.waitForBuildingFinished(module, afterBuild);
}
}
buildModule(module, optional, origin, dependencies, thisCallback) {
// _buildingModules是一个锁,如果一个module已经在构建中,则把callback添加到 _buildingModules 中,末尾统一执行
let callbackList = this._buildingModules.get(module);
if (callbackList) {
callbackList.push(thisCallback);
return;
}
this._buildingModules.set(module, (callbackList = [thisCallback]));
// buildModule钩子
this.hooks.buildModule.call(module);
// 构建模块
await module.build(
this.options,
this,
this.resolverFactory.get("normal", module.resolveOptions),
this.inputFileSystem
);
// 把 dependencies 进行代码中出现顺序排序
const originalMap = module.dependencies.reduce((map, v, i) => {
map.set(v, i);
return map;
}, new Map());
module.dependencies.sort((a, b) => {
const cmp = compareLocations(a.loc, b.loc); // 代码靠前
if (cmp) return cmp;
return originalMap.get(a) - originalMap.get(b);
});
this.hooks.succeedModule.call(module);
this._buildingModules.delete(module);
for (const cb of callbackList) {
cb();
}
}
}
![](https://img.haomeiwen.com/i8105934/eefeaab018b0cd07.png)
从addModuleDependencies到buildModule这条线是1:n,一对多的。将循环接收到的参数dependencies,循环每一个的时候,使用了semaphore.acquire,形成了并发执行。
NormalModuleFactory
生命周期
钩子 | 事件 |
---|---|
create | |
beforeResolve | |
factory | |
resolver | |
createParser | |
parser | 构造出了NormalModule将使用的解析器 |
afterResolve | |
createModule | |
module |
NormalModuleFactory
constructor(){
this.hooks.factory.tap("NormalModuleFactory",()=>(result,callback)=>{
let resolver = this.hooks.resolver.call(null);// 解析路径
resolver(result,(data)=>{
this.hooks.afterResulve.callAsync(data,()=>{
let createdModule = this.hooks.createModule.call(result);
createdModule = this.hooks.module.call(createdModule, result);
callback(createdModule);
})
})
});
this.hooks.resolver.tap("NormalModuleFactory",()=>{
})
}
create(data, callback){
this.hooks.beforeResolve.callAsync({
},()=>{
this.hooks.factory.call(null)(result, (err, module) => {
if(module && this.cachePredicate(module)) {
for (const d of dependencies) {
dependencyCache.set(d, module);
}
}
callback(null, module);
})
})
}
// 创建构造器 type的值可以是 json、javascript/auto、javascript/dynamic 等
getParser(type, parserOptions) {
/* 根据参数 type 和 parserOptions 进行缓存 */
return this.createParser(type, parserOptions);
}
createParser(type, parserOptions = {}) {
const parser = this.hooks.createParser.for(type).call(parserOptions);
this.hooks.parser.for(type).call(parser, parserOptions);
return parser;
}
getGenerator(type, generatorOptions) {
/* 根据参数 type 和 generatorOptions 进行缓存 */
return this.createGenerator(type,generatorOptions);
}
createGenerator(type, generatorOptions = {}) {
const generator = this.hooks.createGenerator
.for(type)
.call(generatorOptions);
this.hooks.generator.for(type).call(generator, generatorOptions);
return generator;
}
上面工厂函数代码中,有一个钩子createParser暴露给外部,不同类型的文件会在NormalModuleFactory的createParser返回对应的构造器。
例如jsonModulesPlugin会指定JsonParser构造器、会指定JsonGenerator
const JsonParser = require("./JsonParser");
const JsonGenerator = require("./JsonGenerator");
class JsonModulesPlugin {
apply(compiler) {
compiler.hooks.compilation.tap(
"JsonModulesPlugin",
(compilation, { normalModuleFactory }) => {
normalModuleFactory.hooks.createParser
.for("json")
.tap("JsonModulesPlugin", () => {
return new JsonParser();
});
normalModuleFactory.hooks.createGenerator
.for("json")
.tap("JsonModulesPlugin", () => {
return new JsonGenerator();
});
}
);
}
}
module.exports = JsonModulesPlugin;
又例如JavascriptModulesPlugin 也会指定NormalModuleFactory的createParser和createGenerator钩子,用于绑定构造器
"use strict";
const Parser = require("./Parser");
const Template = require("./Template");
const { ConcatSource } = require("webpack-sources");
const JavascriptGenerator = require("./JavascriptGenerator");
const createHash = require("./util/createHash");
class JavascriptModulesPlugin {
apply(compiler) {
compiler.hooks.compilation.tap(
"JavascriptModulesPlugin",
(compilation, { normalModuleFactory }) => {
normalModuleFactory.hooks.createParser
.for("javascript/auto")
.tap("JavascriptModulesPlugin", options => {
return new Parser(options, "auto");
});
normalModuleFactory.hooks.createParser
.for("javascript/dynamic")
.tap("JavascriptModulesPlugin", options => {
return new Parser(options, "script");
});
normalModuleFactory.hooks.createGenerator
.for("javascript/auto")
.tap("JavascriptModulesPlugin", () => {
return new JavascriptGenerator();
});
normalModuleFactory.hooks.createGenerator
.for("javascript/dynamic")
.tap("JavascriptModulesPlugin", () => {
return new JavascriptGenerator();
});
}
);
}
}
module.exports = JavascriptModulesPlugin;
NormalModuleFactory流程图,和参数流转过程
![](https://img.haomeiwen.com/i8105934/ef0f571fd2a0c34e.png)
重要插件
entryPlugin.js
每个插件一半都会修改若干个生命周期,比如入口解析插件,就修改了 compilation 和 make 。
class SingleEntryPlugin {
constructor(context, entry, name) {
this.context = context;
this.entry = entry;
this.name = name;
}
apply(compiler) {
compiler.hooks.compilation.tap(
"SingleEntryPlugin",
(compilation, { normalModuleFactory }) => {
// 在Compilation里配置,SingleEntryDependency 需要使用的 Factory 是 normalModuleFactory
compilation.dependencyFactories.set(
SingleEntryDependency,
normalModuleFactory
);
}
);
compiler.hooks.make.tapAsync(
"SingleEntryPlugin",
(compilation, callback) => {
const { entry, name, context } = this;
//返回SingleEntryDependency实例
const dep = SingleEntryPlugin.createDependency(entry, name);
compilation.addEntry(context, dep, name, callback);
// 间接调用 _addModuleChain,构造的出的Factory,从compilation.dependencyFactories取出,也就是normalModuleFactory
}
);
}
}
模块Module和Dependecy
Module 类往上还会继承于 DependenciesBlock,这个是所有模块的基类,它包含了处理依赖所需要的属性和方法。上面所说的 variables, dependencies, blocks 也是这个基类拥有的三个属性。它们分别是:
钩子 | 时间点 |
---|---|
variables | 对应需要对应的外部变量,比如 __filename, __dirname, process 等node环境下特有的变量 |
dependencies | 对应需要解析的其他普通模块,比如 require("./a") 中的 a 模块会先生成一个 CommonJSRequireDependency |
blocks | 对应需要解析的代码块(最终会对应成一个 chunk),比如 require.ensure("./b"),这里的 b 会生成一个 DependenciesBlock 对象 |
source
class Source {
source() {
throw new Error("Abstract");
}
size() {
if(Buffer.from.length === 1) return new Buffer(this.source()).length;
return Buffer.byteLength(this.source())
}
map(options) {
return null;
}
sourceAndMap(options) {
return {
source: this.source(),
map: this.map()
};
}
node() {
throw new Error("Abstract");
}
listNode() {
throw new Error("Abstract");
}
updateHash(hash) {
var source = this.source();
hash.update(source || "");
}
}
module.exports = Source;
NormalModule
parser
HarmonyModulesPlugin
class HarmonyModulesPlugin {
apply(compiler) {
compiler.hooks.compilation.tap(
"HarmonyModulesPlugin",
(compilation, { normalModuleFactory }) => {
// 省略Factory绑定
const handler = (parser, parserOptions) => {
// 此插件的作用,是在parser.hooks.program钩子 添加依赖
new HarmonyDetectionParserPlugin().apply(parser);
new HarmonyImportDependencyParserPlugin(this.options).apply(parser);
new HarmonyExportDependencyParserPlugin(this.options).apply(parser);
new HarmonyTopLevelThisParserPlugin().apply(parser);
};
normalModuleFactory.hooks.parser
.for("javascript/auto")
.tap("HarmonyModulesPlugin", handler);
normalModuleFactory.hooks.parser
.for("javascript/esm")
.tap("HarmonyModulesPlugin", handler);
}
);
}
}
HarmonyModulesPlugin插件,添加了normalModuleFactory各个事件节点应该做的主要事情
网友评论