compilation事件流中,依然只是針對細節步驟做事件流註入,代碼流程如圖: 觸發完compilation事件流後,會直接返回一個compilation對象,然後觸發下一個事件流make。 make的來源在EntryOptionPlugin插件中,無論entry參數是單入口字元串、單入口數組、多 ...
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的事件。
這節先到這裡。