01 => 调用NormalModule.build方法,开始编译
- 设置一些属性以便后续使用,如_ast 、 _source 等
- 返回doBuild方法
class NormalModule extends Module {...// option => this.option// compilation => Compilation对象// resolver =>// fs => 修改文件权限// callback => 回调函数build(options, compilation, resolver, fs, callback) {// 设置属性this.built = true;this._source = null; // 某个模块对应的源代码this._sourceSize = null;this._ast = null; // 源代码对应的ast树this._buildHash = "";this.error = null; // error信息this.errors.length = 0;this.warnings.length = 0;this.buildMeta = {};this.buildInfo = { // 打包后信息cacheable: false,fileDependencies: new Set(),contextDependencies: new Set(),assets: undefined,assetsInfo: undefined};// 返回doBuild方法并传入对应参数// 具体代码在03中,优先看doBuild执行了什么return this.doBuild(options, compilation, resolver, fs, err => {...})}}
02 => 处理loader,返回文件对应的源代码
最终触发callback回调,回到doBuild的回调中doBuild(options, compilation, resolver, fs, callback) {// 根据传入的loader信息,创建loaderContextconst loaderContext = this.createLoaderContext(resolver,options,compilation,fs);// 调用runLoaders函数// resource => 读取的绝对路径// loaders => 传入的loader,为数组// context => loaderContext上下文对象// readResource => 读文件函数runLoaders({resource: this.resource,loaders: this.loaders,context: loaderContext,readResource: fs.readFile.bind(fs)},// result 中有一个result属性,为Buffer流(err, result) => {// 设置缓存,并设置文件依赖,传入的入口有几个需要build的文件if (result) {this.buildInfo.cacheable = result.cacheable;this.buildInfo.fileDependencies = new Set(result.fileDependencies);this.buildInfo.contextDependencies = new Set(result.contextDependencies);}// 设置一些属性,与流程无关.../** 返回一个对象* {* _name: 文件对应的绝对路径* _value: 文件的源代码* }*/this._source = this.createSource(// asBuffer => 将Buffer转化成utf-8形式,此时返回的就是我们的源代码// asBuffer => return Buffer.from(source, "utf-8")this.binary ? asBuffer(source) : asString(source),resourceBuffer,sourceMap);// 调用回调函数return callback();}))}
03 => doBuild内部代码
执行回调后,走到05的位置
this.doBuild(options, compilation, resolver, fs, err => {
// 清空缓存中对应的
this._cachedSources.clear();
...
// handleParseResult 函数
// 执行callback函数,走到了05中
const handleParseResult = result => {
this._lastSuccessfulBuildMeta = this.buildMeta;
this._initBuildHash(compilation);
return callback();
};
// 返回处理后的结果
// this.parser => 之前绑定的Parser类
const result = this.parser.parse(
this._ast || this._source.source(),
{
current: this,
module: this,
compilation: compilation,
options: options
},
(err, result) => {
if (err) {
handleParseError(err);
} else {
handleParseResult(result);
}
}
);
if (result !== undefined) {
// parse is sync
handleParseResult(result);
}
})
04 => 处理AST
class Parser extends Tapable {
// source => 文件源代码
// initialState => 03中传入的属性
parse(source, initialState) {
let ast;
let comments;
// false
if (typeof source === "object" && source !== null) {
ast = source;
comments = source.comments;
// true
} else {
comments = [];
//
ast = Parser.parse(source, {
sourceType: this.sourceType,
onComment: comments
});
}
const state = (this.state = initialState || {});
// 与流程无关,暂不关心
...
return state
}
// 返回语法树
static parse(code, options) {
// "auto"
const type = options ? options.sourceType : "module";
// 设置parserOptions
const parserOptions = Object.assign(
Object.create(null),
defaultParserOptions,
options
);
// type === "auto",true
if (type === "auto") {
parserOptions.sourceType = "module";
} else if (parserOptions.sourceType === "script") {
parserOptions.allowReturnOutsideFunction = true;
}
let ast;
// 引入 acorn 包,解析出对应的ast树
// const acorn = require("acorn");
// const acornParser = acorn.Parser;
ast = acornParser.parse(code, parserOptions);
// 错误处理,暂不考虑
...
return ast;
}
}
05 => module.build回调
模块构建成功后,调用回调函数,进入到buildModule的回调中
module.build(
this.options,
this,
this.resolverFactory.get("normal", module.resolveOptions),
this.inputFileSystem,
error => {
// error相关处理,与流程无关,暂不关心
...
// 调用succeedModule钩子,说明模块打包已经成功
this.hooks.succeedModule.call(module);
// 调用回调函数
return callback();
}
)
06 => this.buildModule回调
通过回调函数,判断当前模块是否有依赖模块,如果需要的话递归加载
const afterBuild = () => {
// 判断当前是否存在依赖项,如果存在需要递归加载
if (addModuleResult.dependencies) {
this.processModuleDependencies(module, err => {
if (err) return callback(err);
callback(null, module);
});
} else {
return callback(null, module);
}
};
this.buildModule(module, false, null, null, err => {
// 错误处理,暂不关心
...
afterBuild();
});
07 => 递归加载对应的子模块
- 重新执行factory.create()操作,和 webpack源码02 中的 05调用compiler.hooks.make,开始编译 大体逻辑是一致的,此处就不详细描述了
- 执行callback
- 最终会返回到compilation.seal函数上
```javascript
// this.processModuleDependencies
processModuleDependencies(module, callback) {
…
// 进入addModuleDependencies方法
this.addModuleDependencies(
); }module, sortedDependencies, this.bail, null, true, callback
// this.addModuleDependencies addModuleDependencies( module, dependencies, bail, cacheGroup, recursive, callback ) { // asyncLib => neo-async库 // const asyncLib = require(“neo-async”); asyncLib.forEach( dependencies, (item, callback) => { const dependencies = item.dependencies; … const factory = item.factory; // 重新创建模块,进行递归处理 factory.create( … ) }, err => { if (err) { … } // 执行callback回调函数 return process.nextTick(callback); } ) }
<a name="yzwTk"></a>
### 08 => 处理Chunk
- 内部会执行许多的 Tapable钩子
- 处理Chunk,核心是创建模块并加载已有模块,同时保存模块信息
- 会进入创建优化后的文件
```javascript
seal(callback) {
// 执行对应钩子
this.hooks.seal.call();
this.hooks.afterOptimizeDependencies.call(this.modules);
this.hooks.beforeChunks.call();
...
// 因为我们是单入口,所以this._preparedEntrypoints只会存在一项
for (const preparedEntrypoint of this._preparedEntrypoints) {
const module = preparedEntrypoint.module;
const name = preparedEntrypoint.name;
// 返回的是new Chunk(name)
const chunk = this.addChunk(name);
const entrypoint = new Entrypoint(name);
...
// 设置一些属性
this.namedChunkGroups.set(name, entrypoint);
this.entrypoints.set(name, entrypoint);
this.chunkGroups.push(entrypoint);
GraphHelpers.connectChunkGroupAndChunk(entrypoint, chunk);
GraphHelpers.connectChunkAndModule(chunk, module);
chunk.entryModule = module;
chunk.name = name;
this.assignDepth(module);
}
// 构建Chunk
// 核心:创建模块加载已有模块的内容,同时记录模块信息
buildChunkGraph(
this,
/** @type {Entrypoint[]} */ (this.chunkGroups.slice())
);
// 调用Tapable钩子
this.hooks.afterChunks.call(this.chunks);
this.hooks.optimize.call();
this.hooks.afterOptimizeModules.call(this.modules);
this.hooks.afterOptimizeChunks.call(this.chunks, this.chunkGroups);
// 到此处,整个流程基本都完成,就剩构建文件了
// 会调用许多的Tapable钩子
this.hooks.optimizeTree.callAsync(this.chunks, this.modules, err => {
this.hooks.afterOptimizeTree.call(this.chunks, this.modules);
this.hooks.afterOptimizeChunkModules.call(this.chunks, this.modules);
this.hooks.reviveModules.call(this.modules, this.records);
this.hooks.optimizeModuleOrder.call(this.modules);
this.hooks.advancedOptimizeModuleOrder.call(this.modules);
this.hooks.beforeModuleIds.call(this.modules);
this.hooks.moduleIds.call(this.modules);
// 会调用许多Tapable钩子
...
if (this.hooks.shouldGenerateChunkAssets.call() !== false) {
this.hooks.beforeChunkAssets.call();
// 创建优化后的文件
this.createChunkAssets();
}
...
})
}
09 => 处理资源,将其转化成webpack输出后的代码
class Compilation extends Tapable {
createChunkAssets() {
// 获取传入的output路径
const outputOptions = this.outputOptions;
// 缓存source
const cachedSourceMap = new Map();
// 因目前都是同步,所以chunks只有一项
for (let i = 0; i < this.chunks.length; i++) {
const chunk = this.chunks[i];
chunk.files = [];
let source; // 打包后的资源
let file;
let filenameTemplate;
// this.mainTemplate => 实例化后的MainTemplate类
const template = chunk.hasRuntime()
? this.mainTemplate
: this.chunkTemplate;
// 最终返回[{ render(), filenameTemplate, pathOptions, identifier, hash }]
const manifest = template.getRenderManifest({
chunk,
hash: this.hash,
fullHash: this.fullHash,
outputOptions,
moduleTemplates: this.moduleTemplates,
dependencyTemplates: this.dependencyTemplates
});
// 此时无缓存
// false
if (
this.cache &&
this.cache[cacheName] &&
this.cache[cacheName].hash === usedHash
) {
source = this.cache[cacheName].source;
} else {
// 设置source
// 会调用MainTemplate类中的renderBootstrap方法
source = fileManifest.render();
...
}
...
// 将source保存到内存中暂存
this.emitAsset(file, source, assetInfo);
// 添加文件对应的缓存
chunk.files.push(file);
this.hooks.chunkAsset.call(chunk, file);
alreadyWrittenFiles.set(file, {
hash: usedHash,
source,
chunk
});
}
emitAsset(file, source, assetInfo = {}) {
...
this.assets[file] = source;
this.assetsInfo.set(file, assetInfo);
}
}
}
// 转换源代码为webpack输出的代码
class MainTemplate extends Tapable {
// 用字符串拼出来对应的source
// 返回优化后的source
renderBootstrap(hash, chunk, moduleTemplate, dependencyTemplates) {
const buf = [];
buf.push(
this.hooks.bootstrap.call(
"",
chunk,
hash,
moduleTemplate,
dependencyTemplates
)
);
buf.push(this.hooks.localVars.call("", chunk, hash));
buf.push("");
buf.push("// The require function");
buf.push(`function ${this.requireFn}(moduleId) {`);
buf.push(Template.indent(this.hooks.require.call("", chunk, hash)));
buf.push("}");
buf.push("");
buf.push(
Template.asString(this.hooks.requireExtensions.call("", chunk, hash))
);
buf.push("");
buf.push(Template.asString(this.hooks.beforeStartup.call("", chunk, hash)));
const afterStartupCode = Template.asString(
this.hooks.afterStartup.call("", chunk, hash)
);
...
return buf;
}
}
10 => 根据优化后的代码,产出对应的文件
- seal 函数执行完毕后,会进行到对应的回调函数,最终会返回到一开始的 run函数(兜兜转转又回到了起点)
- 通过this.outputFileSystem.writeFile创建文件
- 至此,webpack流程告一段落了~
class Compiler extends Tapable { run(callback) { ... // onCompiled就是编译完成后的回调函数 const onCompiled = (err, compilation) => { // 异常直接返回 if (err) return finalCallback(err); // 通过 this.emitAssets ,产出dist目录 this.emitAssets(compilation, err => { // 内部做的是一些记录的事情,与流程无关,暂不关心 ... }) } ... } // 产出文件 emitAssets(compilation, callback) { let outputPath; // 目录创建成功后的回调 const emitFiles = err => { if (err) return callback(err); ... // 通过路径,创建对应的文件 this.outputFileSystem.writeFile(targetPath, content, err => { ... }) } // 调用Tapable的emit钩子 this.hooks.emit.callAsync(compilation, err => { if (err) return callback(err); // 获取输出的目录,为绝对路径 outputPath = compilation.getPath(this.outputPath); // 在一开始的时候绑定的,赋予创建文件的功能 this.outputFileSystem.mkdirp(outputPath, emitFiles); }); } }
