趕緊完結這個系列咯,webpack4都已經出正式版了。 之前的代碼搜索到js文件的對應loader,並添加到了對象中返回,流程如下: 這個對象的request將入口文件的路徑與loader拼接起來並用!分割,所有的屬性基本上都與路徑相關。 after-resolve事件流 這裡會觸發after-re ...
趕緊完結這個系列咯,webpack4都已經出正式版了。
之前的代碼搜索到js文件的對應loader,並添加到了對象中返回,流程如下:
this.plugin("factory", () => (result, callback) => { let resolver = this.applyPluginsWaterfall0("resolver", null); // Ignored if (!resolver) return callback(); // 這裡的data就是返回的大對象 /* data => { context: 'd:\\workspace\\doc', request: 'd:\\workspace\\node_modules\\babel-loader\\lib\\index.js!d:\\workspace\\doc\\input.js', dependencies: [SingleEntryDependency],, userRequest: 'd:\\workspace\\doc\\input.js', rawRequest: './input.js', loaders: [ { loader: 'd:\\workspace\\node_modules\\babel-loader\\lib\\index.js' } ], resource: 'd:\\workspace\\doc\\input.js', 還有package.json與parser相關的屬性 } */ resolver(result, (err, data) => { if (err) return callback(err); // Ignored if (!data) return callback(); // direct module if (typeof data.source === "function") return callback(null, data); this.applyPluginsAsyncWaterfall("after-resolve", data, (err, result) => { // ... }) }) })
這個對象的request將入口文件的路徑與loader拼接起來並用!分割,所有的屬性基本上都與路徑相關。
after-resolve事件流
這裡會觸發after-resolve事件流,註入地點如下:
const matchJson = /\.json$/i; params.normalModuleFactory.plugin("after-resolve", (data, done) => { // if this is a json file and there are no loaders active, we use the json-loader in order to avoid parse errors // @see https://github.com/webpack/webpack/issues/3363 if (matchJson.test(data.request) && data.loaders.length === 0) { data.loaders.push({ loader: jsonLoaderPath }); } done(null, data); });
註釋已經寫的很明白了,這裡是檢測待處理文件類型是否是json文件,如果是並且沒有對應的loader,就將內置的json-loader作為loader。
callback
接下來看回調函數內容:
this.applyPluginsAsyncWaterfall("after-resolve", data, (err, result) => { if (err) return callback(err); // Ignored if (!result) return callback(); // 無此事件流 返回undefined let createdModule = this.applyPluginsBailResult("create-module", result); if (!createdModule) { if (!result.request) { return callback(new Error("Empty dependency (no request)")); } // 創建 createdModule = new NormalModule( result.request, result.userRequest, result.rawRequest, result.loaders, result.resource, result.parser ); } // 無此事件流 createdModule = this.applyPluginsWaterfall0("module", createdModule); return callback(null, createdModule); });
這裡的兩個事件流都是沒有的,所以只需要看那個創建類的過程,然後就直接返回了。
只看下構造函數:
class NormalModule extends Module { /* request => 'd:\\workspace\\node_modules\\babel-loader\\lib\\index.js!d:\\workspace\\doc\\input.js' userRequest => 'd:\\workspace\\doc\\input.js' rawRequest => './input.js' loaders => [ { loader: 'd:\\workspace\\node_modules\\babel-loader\\lib\\index.js' } ] resource => 'd:\\workspace\\doc\\input.js' parser => [Parser] */ constructor(request, userRequest, rawRequest, loaders, resource, parser) { super(); this.request = request; this.userRequest = userRequest; this.rawRequest = rawRequest; this.parser = parser; this.resource = resource; this.context = getContext(resource); this.loaders = loaders; this.fileDependencies = []; this.contextDependencies = []; this.warnings = []; this.errors = []; this.error = null; this._source = null; this.assets = {}; this.built = false; this._cachedSource = null; } // ...原型方法 }
只有一個getContext方法是執行的,這個方法比較簡單,過一下就行:
exports.getContext = function getContext(resource) { var splitted = splitQuery(resource); return dirname(splitted[0]); }; // 切割參數 function splitQuery(req) { var i = req.indexOf("?"); if (i < 0) return [req, ""]; return [req.substr(0, i), req.substr(i)]; } // 返回目錄 // d:\\workspace\\doc\\input.js => d:\\workspace\\doc(反斜杠這裡有轉義) function dirname(path) { if (path === "/") return "/"; var i = path.lastIndexOf("/"); var j = path.lastIndexOf("\\"); var i2 = path.indexOf("/"); var j2 = path.indexOf("\\"); var idx = i > j ? i : j; var idx2 = i > j ? i2 : j2; if (idx < 0) return path; if (idx === idx2) return path.substr(0, idx + 1); return path.substr(0, idx); }
返回了入口文件路徑的目錄,也就是上下文。
這樣一路回調返回,回到了最初的原型方法create:
// NormalModuleFactory const factory = this.applyPluginsWaterfall0("factory", null); // Ignored if (!factory) return callback(); factory(result, (err, module) => { // 這裡的module就是new出來的NormalModule對象 if (err) return callback(err); // this.cachePredicate = typeof options.unsafeCache === "function" ? options.unsafeCache : Boolean.bind(null, options.unsafeCache); // 由於預設情況下unsafeCache為true 所以這個函數預設一直返回true if (module && this.cachePredicate(module)) { dependencies.forEach(d => d.__NormalModuleFactoryCache = module); } callback(null, module); });
這裡對之前處理的入口文件對象做了緩存。
返回又返回,回到了Compilation對象中:
_addModuleChain(context, dependency, onModule, callback) { // ... this.semaphore.acquire(() => { // 從這裡出來 moduleFactory.create({ contextInfo: { issuer: "", compiler: this.compiler.name }, context: context, dependencies: [dependency] }, (err, module) => { if (err) { this.semaphore.release(); return errorAndCallback(new EntryModuleNotFoundError(err)); } let afterFactory; // 不存在的屬性 if (this.profile) { if (!module.profile) { module.profile = {}; } afterFactory = Date.now(); module.profile.factory = afterFactory - start; } const result = this.addModule(module); // ... }); }); }
常規的錯誤處理,然後判斷了下profile屬性,這個屬性大概是用計算前面factory整個事件流的觸發時間。
總之,接著調用了addModule原型方法
addModule
// 第二個參數未傳 addModule(module, cacheGroup) { // 這裡調用的是原型方法 /* identifier() { return this.request; } */ // 愚蠢的方法 const identifier = module.identifier(); // 空對象沒有的 if (this._modules[identifier]) { return false; } // 緩存名是'm' + request const cacheName = (cacheGroup || "m") + identifier; // 如果有緩存的情況下 if (this.cache && this.cache[cacheName]) { // ... } // 這個方法是在父類上面 // 作用是清空屬性 // 然而並沒有什麼屬性可以清 跳過 module.unbuild(); // 將類分別加入各個對象/數組 this._modules[identifier] = module; if (this.cache) { this.cache[cacheName] = module; } this.modules.push(module); return true; }
這個方法其實並沒有做什麼實際的東西,簡單來講只是添加了緩存。
接下來看下麵的代碼:
_addModuleChain(context, dependency, onModule, callback) { // ... this.semaphore.acquire(() => { moduleFactory.create({ contextInfo: { issuer: "", compiler: this.compiler.name }, context: context, dependencies: [dependency] }, (err, module) => { // ... // 返回一個true const result = this.addModule(module); // 跳過下麵兩步 if (!result) { // ... } if (result instanceof Module) { // ... } // 進入這裡 /* (module) => { entry.module = module; this.entries.push(module); module.issuer = null; } */ onModule(module); this.buildModule(module, false, null, null, (err) => { if (err) { this.semaphore.release(); return errorAndCallback(err); } if (this.profile) { const afterBuilding = Date.now(); module.profile.building = afterBuilding - afterFactory; } moduleReady.call(this); }); function moduleReady() { this.semaphore.release(); this.processModuleDependencies(module, err => { if (err) { return callback(err); } return callback(null, module); }); } }); }); }
這個onModule來源於方法的第三個參數,比較簡單,沒做什麼事,然後繼續跑調用了原型方法buildModule。
buildModule
// this.buildModule(module, false, null, null, (err) => {}) buildModule(module, optional, origin, dependencies, thisCallback) { // 無此事件流 this.applyPlugins1("build-module", module); if (module.building) return module.building.push(thisCallback); const building = module.building = [thisCallback]; function callback(err) { module.building = undefined; building.forEach(cb => cb(err)); } module.build(this.options, this, this.resolvers.normal, this.inputFileSystem, (error) => { // ... }); }
總的來說並沒有做什麼事,傳進來的callback被封裝並帶入build方法的回調中。
module.build
// options為添加了預設參數的配置對象webpack.config.js build(options, compilation, resolver, fs, callback) { this.buildTimestamp = Date.now(); this.built = true; this._source = null; this.error = null; this.errors.length = 0; this.warnings.length = 0; this.meta = {}; return this.doBuild(options, compilation, resolver, fs, (err) => { // ... }); }
一串串的賦值,又是doBuild方法。
module.doBuild
doBuild(options, compilation, resolver, fs, callback) { this.cacheable = false; const loaderContext = this.createLoaderContext(resolver, options, compilation, fs); runLoaders({ resource: this.resource, loaders: this.loaders, context: loaderContext, readResource: fs.readFile.bind(fs) }, (err, result) => { // ... }); }
呃,已經連續調用好多個函數了。
createLoaderContext
createLoaderContext(resolver, options, compilation, fs) { const loaderContext = { // .. }; compilation.applyPlugins("normal-module-loader", loaderContext, this); // 無此值 if (options.loader) Object.assign(loaderContext, options.loader); return loaderContext; }
這裡先不關心這個loaderContext裡面到底有什麼,因為後面會調用的,直接看事件流'normal-module-loader'。
// LoaderTargetPlugin.js compilation.plugin("normal-module-loader", (loaderContext) => loaderContext.target = this.target);
這裡由於配置對象沒有target參數,所以這個沒有用。
// LoaderPlugin.js compilation.plugin("normal-module-loader", (loaderContext, module) => { loaderContext.loadModule = function loadModule(request, callback) { // ... }; });
這個僅僅是加了個屬性方法,跳過。
最後返回了一個包含很多方法的對象loaderContext。
第二步是調用runLoaders方法,將之前生成的對象傳進去,這裡終於要調用loader對文件進行處理了!!!!
下節可以有乾貨,這節流水賬先這樣了。