.26-浅析webpack源码之事件流make(1)
compilation事件流中,依然只是针对细节步骤做事件流注入,代码流程如图:
// apply => this-compilation // apply => compilation // return compialtion const compilation = this.newCompilation(params); this.applyPluginsParallel("make", compilation, err => { // callback... });
触发完compilation事件流后,会直接返回一个compilation对象,然后触发下一个事件流make。
make的来源在EntryOptionPlugin插件中,无论entry参数是单入口字符串、单入口数组、多入口对象还是动态函数,都会在引入对应的入口插件后,注入一个make事件。
这里先以最简单的单入口字符串为例,开始跑make事件流:
// SingleEntryPlugin compiler.plugin("make", (compilation, callback) => { // 生成一个类型为single entry的依赖类 // dep.loc = name const dep = SingleEntryPlugin.createDependency(this.entry, this.name); compilation.addEntry(this.context, dep, this.name, callback); });
Compilation.addEntry
这里回到了compilation类中,调用原型函数addEntry。
class Compilation extends Tapable { // ... // context => 默认为process.cwd() // entry => dep => SingleEntryDependency // name => 单入口默认为main // callback => 后面的流程 addEntry(context, entry, name, callback) { const slot = { name: name, module: null }; // 初始为[] this.preparedChunks.push(slot); this._addModuleChain(context, entry, (module) => { /**/ }, (err, module) => { /**/ }); } }
Compilation._addModuleChain
没什么好讲的,直接进入_addModuleChain函数:
class Compilation extends Tapable { // ... _addModuleChain(context, dependency, onModule, callback) { // profile => options.profile // 不传则start为undefined const start = this.profile && Date.now(); // bail => options.bail const errorAndCallback = this.bail ? (err) => { callback(err); } : (err) => { err.dependencies = [dependency]; this.errors.push(err); callback(); }; if (typeof dependency !== "object" || dependency === null || !dependency.constructor) { throw new Error("Parameter 'dependency' must be a Dependency"); } // dependencyFactories包含了所有的依赖集合 const moduleFactory = this.dependencyFactories.get(dependency.constructor); if (!moduleFactory) { throw new Error(`No dependency factory available for this dependency type: ${dependency.constructor.name}`); } this.semaphore.acquire(() => { /**/ }); } }
profile和bail参数大概不会有人传吧,所有直接忽视。
接下来就是尝试从dependencyFactories中获取依赖类对应的值,这个Map对象所有值的设置都在compilation事件流中,详情可见24节中的流程图,传送门:http://www.cnblogs.com/QH-Jimmy/p/8183840.html
在这里,依赖类来源于SingleEntryDependency,键值对设置地点同样来源于SingleEntryPlugin:
// SingleEntryPlugin compiler.plugin("compilation", (compilation, params) => { const normalModuleFactory = params.normalModuleFactory; // 这里 compilation.dependencyFactories.set(SingleEntryDependency, normalModuleFactory); });
所以很简单,这里调用get后取出来的是normalModuleFactory。
而这个normalModuleFactory,在18节中有简单介绍并分析了其中RuleSet对module.rules的处理,传送门:http://www.cnblogs.com/QH-Jimmy/p/8109903.html
semaphore
获取对应的moduleFactory后,调用的this.semaphore其中是生成一个新类:
this.semaphore = new Semaphore(options.parallelism || 100);
(类的名字英文翻译是信号机)
内容比较简单,过一下源码:
class Semaphore { // 一个数字 默认为100 constructor(available) { this.available = available; this.waiters = []; }; // 当available大于0时执行callback并减1 // 否则将callback弹入waiters acquire(callback) { if (this.available > 0) { this.available--; callback(); } else { this.waiters.push(callback); } }; // 尝试取出最近弹入的callback并在事件流末尾执行 release() { if (this.waiters.length > 0) { const callback = this.waiters.pop(); process.nextTick(callback); } else { this.available++; } } }
设计看起来确实像个信号机,构造函数中有一个初始的使用次数以及一个待执行callback数组。
每次调用acquire时会传入一个callback,如果次数为正就执行callback并将使用次数减1,如果次数用完了,就把这个callback弹入waiters数组中。
每次调用release时,为尝试取出最新的callback并尽快执行,如果不存在待执行的callback,就将使用次数加1。
NormalModuleFactory.create
也就是说,以下代码可以理解成简单的函数调用:
this.semaphore.acquire(() => { moduleFactory.create({ contextInfo: { issuer: "", compiler: this.compiler.name }, context: context, dependencies: [dependency] }, (err, module) => { /* fn... */ }); });
这样,流程跑到了normalModuleFactory的原型方法create上。
class NormalModuleFactory extends Tapable { /* data => { contextInfo:{ issuer: '', compiler: this.compiler.name // undefined }, context: context, // process.cwd() dependencies: [SingleEntryDependency] } */ create(data, callback) { const dependencies = data.dependencies; // 尝试取缓存 const cacheEntry = dependencies[0].__NormalModuleFactoryCache; if (cacheEntry) return callback(null, cacheEntry); // 上下文 => process.cwd() const context = data.context || this.context; // 入口文件字符串 => ./input.js const request = dependencies[0].request; const contextInfo = data.contextInfo || {}; this.applyPluginsAsyncWaterfall("before-resolve", { contextInfo, context, request, dependencies }, (err, result) => { /**/ }); } }
这里将上下文、入口文件、入口模块依赖类整合,然后开始触发normalModuleFactory类上的事件流。
关于normalModuleFactory的事件流注入,全部都在24节中有介绍,再来一个传送门:http://www.cnblogs.com/QH-Jimmy/p/8183840.html
这里的事件流before-resolve是没有的,所以按照Tapable的中applyPluginsAsyncWaterfall的执行方式:
Tapable.prototype.applyPluginsAsyncWaterfall = function applyPluginsAsyncWaterfall(name, init, callback) { if (!this._plugins[name] || this._plugins[name].length === 0) return callback(null, init); // more... }
这里会直接调用callback,分别传入null与第二个参数,如下所示:
this.applyPluginsAsyncWaterfall("before-resolve", { contextInfo, context, request, dependencies }, // err => null // result => 上面的对象 (err, result) => { if (err) return callback(err); if (!result) return callback(); // 触发factory事件流 const factory = this.applyPluginsWaterfall0("factory", null); // Ignored if (!factory) return callback(); factory(result, (err, module) => { /**/ }); } );
这里会接着触发factory事件流,这个是在构造函数中直接plugin的。
class NormalModuleFactory extends Tapable { constructor(context, resolvers, options) { super(); // ...other property this.plugin("factory", () => (result, callback) => { let resolver = this.applyPluginsWaterfall0("resolver", null); if (!resolver) return callback(); resolver(result, (err, data) => { /**/ }); }); } }
这里又触发了resolver事件流,同样是构造函数中另外一个plugin的事件。
这节先到这里。