调试
在vscode 中创建调试文件,进行debug, 点击debug,自动创建即可
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "debugWebpack",
"program": "${workspaceFolder}/bin/webpack.js",
"console": "integratedTerminal",
"cwd": "${workspaceFolder}",
"args": [
// "${workspaceFolder}/examples/examples.js",
// webpack example 目录,我们选其中一个进行调试即可, 详细 example/README.md
"${workspaceFolder}/examples/commonjs/example.js",
]
}
]
}
接之前一篇文章, 创建compiler
- 接下来可以配合官方文档进行阅读compiler hook按照官方顺序就是compiler整个生命周期
梳理 compiler 生命周期
- 首先进行环境初始化, 即
compiler.hook.environment.call()
, 但全局搜索没有做订阅 - 环境初始化后会到
compiler.hook.afterEnvironment.call()
, 说明环境初始化完成,IgnoringWatchFileSystem
, 应该是做忽略某些文件的监听 - 从
lib/webpack
中的new WebpackOptionsApply().process(options, compiler);
这里其实是对option
的配置进行plugin 的加载,且对compiler 对应周期进行注册监听事件。
所以这里会经历一个entryOption
的周期,
例如:entry 入口
,
// 处理入口文件, SyncBailHook , 当返回为非undefined时停止往下执行
new EntryOptionPlugin().apply(compiler);
// 执行入口文件回调
compiler.hooks.entryOption.call(options.context, options.entry);
// EntryOptionPlugin
class EntryOptionPlugin {
apply(compiler) {
compiler.hooks.entryOption.tap("EntryOptionPlugin", (context, entry) => {
EntryOptionPlugin.applyEntryOption(compiler, context, entry);
return true;
});
}
/** 省略其余code */
}
其余配置也差不多, 然后会执行compiler.hooks.afterPlugins.call(compiler);
说明所有的插件已经加载完毕。
最后调用compiler.hooks.afterResolvers.call(compiler);
表示compiler 中3种类型的解析器已经全部设置完成,new WebpackOptionsApply().process(options, compiler);
执行完毕
- 然后 执行
compiler.hook.initialize.call()
- 最后回到
lib/webpack
执行compiler.run()
Compiler
上面已经通过 new Compiler(options.context)
创建compiler, 然后调用compiler.run()
进入lib/compiler.js
可以看到创建了多个hooks, 关于tapable, 我觉得这篇文章挺好
run
方法执行, 经历了两个hooks (hooks.beforeRun, hooks.run)
, 最后执行this.compiler()
compile(callback) {
// 创建 normalFactory, 跟 contextFactory, 创建完成之后调用 hooks.normalModuleFactory.call(); hooks.contextModuleFactory.call()
const params = this.newCompilationParams();
this.hooks.beforeCompile.callAsync(params, err => {
if (err) return callback(err);
this.hooks.compile.call(params);
const compilation = this.newCompilation(params);
const logger = compilation.getLogger("webpack.Compiler");
logger.time("make hook");
this.hooks.make.callAsync(compilation, err => {
logger.timeEnd("make hook");
if (err) return callback(err);
logger.time("finish make hook");
this.hooks.finishMake.callAsync(compilation, err => {
logger.timeEnd("finish make hook");
if (err) return callback(err);
process.nextTick(() => {
logger.time("finish compilation");
compilation.finish(err => {
logger.timeEnd("finish compilation");
if (err) return callback(err);
logger.time("seal compilation");
compilation.seal(err => {
logger.timeEnd("seal compilation");
if (err) return callback(err);
logger.time("afterCompile hook");
this.hooks.afterCompile.callAsync(compilation, err => {
logger.timeEnd("afterCompile hook");
if (err) return callback(err);
return callback(null, compilation);
});
});
});
});
});
});
});
}
2.创建 NormalModuleFactory
, 跟 contextModuleFactory
, 创建完成之后调用 hooks.normalModuleFactory.call()
,hooks.contextModuleFactory.call()
NormalModuleFactory
用来生成模块的
之后调用
hooks.beforeCompile
,beforeCompile
在LazyCompilationPlugin 下注册了, 这里跟hmr 热更新相关。 同时在DllReferencePlugin, 它是用来拆分bundle,提升构建速度的 具体可以看这然后调用
hook.compile
, 在这DllReferencePlugin, ExternalsPlugin, DelegatedPlugin三个文件中注册了方法创建
compilation
实例,该实例可以用于factorizeModule, buildModule, addModule, 以及对module的依赖收集, 执行compiler.hooks.thisCompilation.call
以及compiler.hooks.compilation.call
执行
compiler.hooks.make
正式进入到编译阶段找到
tapAsync
函数, 通过调用addEntry
添加入口文件compiler.hooks.make.tapAsync("EntryPlugin", (compilation, callback) => { compilation.addEntry(context, dep, options, err => { callback(err); }); });
addEntry(context, entry, optionsOrName, callback) { // TODO webpack 6 remove const options = typeof optionsOrName === "object" ? optionsOrName : { name: optionsOrName }; this._addEntryItem(context, entry, "dependencies", options, callback); }
然后
_addEntryItem
, 会判断是否是多入口等等,然后执行compilation.hooks.addEntry.call
表示添加入口文件完毕, 最后执行addModuleTree
this.addModuleTree( { context, dependency: entry, contextInfo: entryData.options.layer ? { issuerLayer: entryData.options.layer } : undefined }, (err, module) => { if (err) { this.hooks.failedEntry.call(entry, options, err); return callback(err); } this.hooks.succeedEntry.call(entry, options, module); return callback(null, module); } );
来看看
addModuleTree
函数,addModuleTree
里面会执行一个handleModuleCreation
const Dep = /** @type {DepConstructor} */ (dependency.constructor); // 这里会找到直接找到normalModuleFactory const moduleFactory = this.dependencyFactories.get(Dep); this.handleModuleCreation( { factory: moduleFactory, dependencies: [dependency], originModule: null, contextInfo, context }, (err, result) => { if (err && this.bail) { // error } else if (!err && result) { callback(null, result); } else { callback(); } } );
- 然后
handleModuleCreation
会执行factorizeModule
, 先不用管回调
this.factorizeModule( { currentProfile, factory, dependencies, factoryResult: true, originModule, contextInfo, context }, (err, factoryResult) => { /** 省略部分代码, 只需要知道后续会进行 this.addModule(newModule, () => {}) */ const newModule = factoryResult.module; this.addModule(newModule, () => { // 下面会执行模块创建以及 依赖收集 this._handleModuleBuildAndDependencies( originModule, module, recursive, callback ); }) } )
this.factorizeModule
其实就是简单的将当前模块 添加到一个异步队列中// this.factorizeModule 通过new AsyncQueue 创建 this.factorizeQueue.add(options, callback);
factorizeQueue.add
它是一个异步执行 队列,当有一个任务加入到队列中,最后会执行setImmediate(root._ensureProcessing);
,
但值得注意的是root
这个参数设计的有点巧妙,是一种父子关系,this.processDependenciesQueue 的children 包含 this.addModuleQueue, this.factorizeQueue , this.buildQueue
最后会执行setImmediate(root._ensureProcessing);
当前root 是this.processDependenciesQueue
。 setImmediate 会在下一次事件循环中调用/** @type {AsyncQueue<Module, Module, Module>} */ this.processDependenciesQueue = new AsyncQueue({ name: "processDependencies", parallelism: options.parallelism || 100, processor: this._processModuleDependencies.bind(this) }); /** @type {AsyncQueue<Module, string, Module>} */ this.addModuleQueue = new AsyncQueue({ name: "addModule", parent: this.processDependenciesQueue, getKey: module => module.identifier(), processor: this._addModule.bind(this) }); /** @type {AsyncQueue<FactorizeModuleOptions, string, Module | ModuleFactoryResult>} */ this.factorizeQueue = new AsyncQueue({ name: "factorize", parent: this.addModuleQueue, processor: this._factorizeModule.bind(this) }); /** @type {AsyncQueue<Module, Module, Module>} */ this.buildQueue = new AsyncQueue({ name: "build", parent: this.factorizeQueue, processor: this._buildModule.bind(this) });
ensureProcessing 会遍历当前实例的children,简单说就是遍历 addModuleQueue, factorizeQueue, buildQueue 三个AsyncQueue 然后从这三个实例的队列中找任务去执行
_ensureProcessing() { // this = this.processDependenciesQueue, // children 包含 this.addModuleQueue, this.factorizeQueue, this.buildQueue this._willEnsureProcessing = false; if (this._queued.length > 0) return; if (this._children !== undefined) { for (const child of this._children) { while (this._activeTasks < this._parallelism) { const entry = child._queued.dequeue(); if (entry === undefined) break; this._activeTasks++; entry.state = PROCESSING_STATE; child._startProcessing(entry); } if (child._queued.length > 0) return; } } if (!this._willEnsureProcessing) this._needProcessing = false; }
child._startProcessing, 因为一直从入口过来, 此时的child 是factorize
_startProcessing(entry) { this.hooks.beforeStart.callAsync(entry.item, err => { if (err) { // error 处理 } let inCallback = false; try { // compliation._addModule 或者 compliation._factorizeModule 或者 compliation._buildModule // 此时调用的是 compliation._factorizeModule this._processor(entry.item, (e, r) => { inCallback = true; this._handleResult(entry, e, r); }); } catch (err) { if (inCallback) throw err; this._handleResult(entry, err, null); } this.hooks.started.call(entry.item); }); }
_factorizeModule
调用了 NormalModuleFactory.create
调用了NormalModuleFactory.hooks.beforeResolve.callAsync
,NormalModuleFactory.hooks.factorize.callAsync
在NormalModuleFactory.hooks.factorize.tapAsync
中调用了resolve.callAsync
, 在resolve.tapAsync
主要目的是resolve 模块找到模块对应的loader
以及loader的路径,描述文件等,这里会执行enhance-loader
, 并且创建parse
和generator
赋值到resolveData.createData
Object.assign(data.createData, { layer: layer === undefined ? contextInfo.issuerLayer || null : layer, request: stringifyLoadersAndResource( allLoaders, resourceData.resource ), userRequest, rawRequest: request, loaders: allLoaders, resource: resourceData.resource, context: resourceData.context || getContext(resourceData.resource), matchResource: matchResourceData ? matchResourceData.resource : undefined, resourceResolveData: resourceData.data, settings, type, parser: this.getParser(type, settings.parser), parserOptions: settings.parser, generator: this.getGenerator(type, settings.generator), generatorOptions: settings.generator, resolveOptions });
- 继续执行
nmf.hooks.afterResolve.callAsync
和nmf.hooks.createModule.callAsync
this.hooks.afterResolve.callAsync(resolveData, (err, result) => { const createData = resolveData.createData; this.hooks.createModule.callAsync(createData, resolveData, (err, createdModule) => { if(!createModule) { createdModule = new NormalModule(createData); } // SideEffectsFlagPlugin 这里做sideEffect createdModule = this.hooks.module.call( createdModule, createData, resolveData ); // 执行完成后调用 hooks.factorize.callAsync 回调 传入当前module 信息, 也就是factory.create 的callback 再callback 中执行AsyncQueue.handleResult return callback(null, createdModule); }) })
AsyncQueue.handleResult 执行了 加进异步队列的 callback 即 6.5 的回调 addModule, 此时又做了一次6.6 之后的循环 只是改成了
addModule
addModule
经过上面一轮 后执行this._handleModuleBuildAndDependencies( originModule, module, recursive, callback );
在
_handleModuleBuildAndDependencies
会执行this.buildModule
再回调中会构建依赖