概览
为最新的webpack5代码体量过于庞大,本文将结合1.0版本的代码阐释webppack构建的基本流程
基本概念
从make钩子讲起:
compiler.plugin("make", function(compilation, callback) {
compilation.addEntry(this.context, new SingleEntryDependency(this.entry), this.name, callback);
}.bind(this));
sequenceDiagram
Compilation->>Compilation: _addModuleChain
Compilation->>NormalModuleFactory: create
NormalModuleFactory->>Compilation: module
Compilation->>Compilation: addModule(添加module到modules数组且设置_modules对象)
Compilation->>Compilation: onModule callback(addentry就是添加到entries数组中)
Compilation->>Compilation: buildModule(回调执行moduleReady,即processModuleDependencies)
Compilation->>NormalModule: build
NormalModule->>NormalModuleMixin: doBuild(应用一系列的loader后读取文件返回normalModule继续执行)
NormalModule->>NormalModule.parser: parse(此时的模块文件已经为应用loader之后的代码,parse方法拿到这个字符串形式的代码)
Compilation->>Compilation: processModuleDependencies(buildModule结束后处理模块引入的依赖,module也是一个block)
Compilation->>Compilation: addModuleDependencies(对于每个dependency,找到对应的工厂函数生成module)
Compilation->>NormalModuleFactory: create
SEAL
结束make钩子,进入seal方法,做chunks处理
this.applyPluginsParallel("make", compilation, function(err) {
if (err) return callback(err);
compilation.seal(function(err) {
if (err) return callback(err);
this.applyPluginsAsync("after-compile", compilation, function(err) {
if (err) return callback(err);
return callback(null, compilation);
});
}.bind(this));
}.bind(this));
function seal(callback) {
this.applyPlugins("seal");
this.preparedChunks.forEach(function(preparedChunk) {
var module = preparedChunk.module;
var chunk = this.addChunk(preparedChunk.name, module);
chunk.initial = chunk.entry = true;
chunk.addModule(module);
module.addChunk(chunk);
this.processDependenciesBlockForChunk(module, chunk);
}, this);
}
compilation.addChunks
- 一个
compilation
有许多chunks
- 一个
chunk
包含多个modules
chunk
和module
相互引用- 一个
module
有许多blocks
(继承自DependenciesBlock
) processDependenciesBlockForChunk
将module
的dependencies module
都加入到这一个chunk中
Compilation.prototype.processDependenciesBlockForChunk = function processDependenciesBlockForChunk(block, chunk) {
block.blocks.forEach(function(b) {
var c;
if (!b.chunk) {
c = this.addChunk(b.chunkName, b.module, b.loc);
b.chunk = c;
c.addBlock(b);
} else {
c = b.chunk;
}
chunk.addChunk(c);
c.addParent(chunk);
this.processDependenciesBlockForChunk(b, c);
}, this);
function iteratorDependency(d) {
if (!d.module) return;
if (d.module.error) {
d.module = null;
return;
}
if (chunk.addModule(d.module)) {
d.module.addChunk(chunk);
this.processDependenciesBlockForChunk(d.module, chunk);
}
}
block.dependencies.forEach(iteratorDependency, this);
block.variables.forEach(function(v) {
v.dependencies.forEach(iteratorDependency, this);
}, this);
};
添加module到chunk完之后,进入seal的下一阶段:
this.applyPlugins("optimize");
this.applyPlugins("optimize-modules", this.modules);
this.applyPlugins("after-optimize-modules", this.modules);
// RemoveParentModulesPlugin -> RemoveEmptyChunksPlugin -> MergeDuplicateChunksPlugin
this.applyPlugins("optimize-chunks", this.chunks);
this.applyPlugins("after-optimize-chunks", this.chunks);
进入下一阶段
this.applyPluginsAsyncSeries("optimize-tree", this.chunks, this.modules, function(err) {
if (err) return callback(err);
//RecordIdsPlugin
this.applyPlugins("revive-modules", this.modules, this.records);
this.applyPlugins("optimize-module-order", this.modules);
// 给每个module赋予一个ID,取自nextFreeModuleId++
this.applyModuleIds();
this.applyPlugins("optimize-module-ids", this.modules);
this.applyPlugins("after-optimize-module-ids", this.modules);
// RecordIdsPlugin
// 给compilation的records赋值records.modules.byIdentifier[identifier] = module.id;
// 通过request路径取到module id
this.applyPlugins("record-modules", this.modules, this.records);
// RecordIdsPlugin
this.applyPlugins("revive-chunks", this.chunks, this.records);
this.applyPlugins("optimize-chunk-order", this.chunks);
// 给每个module赋予一个ID,取自nextFreeChunkId++,维护一个ids数组,包含所有的chunkId
this.applyChunkIds();
// FlagIncludedChunksPlugin
this.applyPlugins("optimize-chunk-ids", this.chunks);
this.applyPlugins("after-optimize-chunk-ids", this.chunks);
// 修改records
// if (!records.chunks) records.chunks = {};
// if (!records.chunks.byName) records.chunks.byName = {};
// if (!records.chunks.byBlocks) records.chunks.byBlocks = {};
this.applyPlugins("record-chunks", this.chunks, this.records);
// 根据ID大小对modules和chunks排序,包括module里的chunks,和chunk里面的module,小id在前
this.sortItems();
this.applyPlugins("before-hash");
// 给每个chunk计算一个hash,最后为compilation计算一个fullHash,根据hashDigestLength裁剪前多少位hash值
this.createHash();
this.applyPlugins("after-hash");
this.applyPlugins("before-chunk-assets");
// 用template渲染成字符串保存在this.assets中,键为生成的文件名,值为ConcatSource类型,包含了即将生成代码的source map信息。
this.createChunkAssets();
this.applyPlugins("additional-chunk-assets", this.chunks);
// 填充this.fileDependencies
// 筛除重复的fileDependencies
/** this.fileDependencies.sort();
this.fileDependencies = filterDups(this.fileDependencies);
this.contextDependencies.sort();
this.contextDependencies = filterDups(this.contextDependencies);**/
this.summarizeDependencies();
this.applyPlugins("record", this, this.records);
this.applyPluginsAsync("optimize-chunk-assets", this.chunks, function(err) {
if (err) return callback(err);
this.applyPlugins("after-optimize-chunk-assets", this.chunks);
this.applyPluginsAsync("optimize-assets", this.assets, function(err) {
if (err) return callback(err);
this.applyPlugins("after-optimize-assets", this.assets);
return callback();
}.bind(this));
}.bind(this));
}.bind(this));
Compilation.prototype.applyChunkIds = function applyChunkIds() {
this.chunks.forEach(function(chunk) {
if (chunk.id === null) {
if (chunk.id === null)
chunk.id = this.nextFreeChunkId++;
}
if (!chunk.ids)
chunk.ids = [chunk.id];
}, this);
};
Compilation.prototype.sortItems = function sortItems() {
function byId(a, b) {
return a.id - b.id;
}
this.chunks.sort(byId);
this.modules.sort(byId);
this.modules.forEach(function(module) {
module.chunks.sort(byId);
module.reasons.sort(function(a, b) {
return byId(a.module, b.module)
});
});
this.chunks.forEach(function(chunk) {
chunk.modules.sort(byId);
});
};
records结构
record-modules之后
record-chunks之后
结束seal,emitAssets,emitRecords
this.compile(function(err, compilation) {
if (err) return callback(err);
// 输出文件
this.emitAssets(compilation, function(err) {
if (err) return callback(err);
this.emitRecords(function(err) {
if (err) return callback(err);
var stats = compilation.getStats();
stats.startTime = startTime;
stats.endTime = new Date().getTime();
this.applyPlugins("done", stats);
return callback(null, stats);
}.bind(this));
}.bind(this));
}.bind(this));
最后返回至起始调用点webpack函数
var compiler = webpack(options, function(err, stats) {
if (!options.watch) {
// Do not keep cache anymore
var ifs = compiler.inputFileSystem;
if (ifs && ifs.purge) ifs.purge();
}
if (err) {
console.error(err.stack || err);
if (err.details) console.error(err.details);
if (!options.watch) {
process.on("exit", function() {
process.exit(1);
});
}
return;
}
if (outputOptions.json)
process.stdout.write(JSON.stringify(stats.toJson(outputOptions), null, 2) + "\n");
else {
// 终端打印统计信息
process.stdout.write(stats.toString(outputOptions) + "\n");
}
});
一些类层次结构
classDiagram
DependenciesBlock <|-- Module
Module <|-- NormalModule
NormalModuleMixin <|-- NormalModule
DependenciesBlock: blocks
DependenciesBlock: dependencies
DependenciesBlock: variables
DependenciesBlock: addDependency()
classDiagram
NullDependency <|-- RequireHeaderDependency
Dependency <|-- NullDependency
Dependency <|-- ModuleDependency
ModuleDependency <|-- SingleEntryDependency
classDiagram
Template <|-- MainTemplate
Template <|-- ChunkTemplate
MainTemplate <|-- JsonpMainTemplate
ChunkTemplate <|-- JsonpChunkTemplate
classDiagram
Source <|-- SourceMapNodeSource
SourceMapNodeSource <|-- ConcatSource
总结
可以看到,webpack依赖tapable实现的插件架构,在构建过程中的hooks绑定许多处理方法,进而影响后续的构建过程。从入口文件开始,解析文件地址,初始化module对象,在这个过程中通过loader对文件内容进行转换,利用loader构建完毕之后,使用js parser解析成ast,然后递归处理依赖,将依赖模块添加到不同的module对象中。
graph TD
webpack --> compiler.run --> compiler.compile --> id1(make hook ) --> addEntry --> addModlueChain --> moduleFactory.create --> id(module对象) --> buildModule --> module.build --> loaders(module.doBuild//应用loaders) --> onModuleBuild(onModuleBuild//模块构建完毕) --> parse[parser.parse//利用ast改变, 影响module.dependencies] --> moduleReady --> processModuleDependencies --> add(addModuleDependencies//递归添加依赖构建模块) --> compilation.seal --> rest(// 添加module到chunk//生成chunk和moduleid//利用template文件结合source-map生成最终的bundle)