Tips:寫到這裡,需要對當初的規則進行修改。在必要的地方,會在webpack.config.js中設置特殊的參數來跑源碼,例如本例會使用module:{rules:[...]}來測試,基本上測試參數均取自於vue腳手架(太複雜的刪掉)。 下麵兩節的主要流程圖如下: 在進入compile方法後,迎面 ...
Tips:寫到這裡,需要對當初的規則進行修改。在必要的地方,會在webpack.config.js中設置特殊的參數來跑源碼,例如本例會使用module:{rules:[...]}來測試,基本上測試參數均取自於vue腳手架(太複雜的刪掉)。
下麵兩節的主要流程圖如下:
在進入compile方法後,迎面而來的就是這麼一行代碼:
const params = this.newCompilationParams();
簡單看一下方法,源碼如下:
Compiler.prototype.newCompilationParams = () => { const params = { normalModuleFactory: this.createNormalModuleFactory(), contextModuleFactory: this.createContextModuleFactory(), compilationDependencies: [] }; return params; } // 工廠方法1 Compiler.prototype.createNormalModuleFactory = () => { const normalModuleFactory = new NormalModuleFactory(this.options.context, this.resolvers, this.options.module || {}); this.applyPlugins("normal-module-factory", normalModuleFactory); return normalModuleFactory; } // 工廠方法2 Compiler.prototype.createContextModuleFactory = () => { const contextModuleFactory = new ContextModuleFactory(this.resolvers, this.inputFileSystem); this.applyPlugins("context-module-factory", contextModuleFactory); return contextModuleFactory; }
該方法生成了一個對象參數,而對象中的值又分別調用了各自的工廠方法。
按順序,首先來看NormalModuleFactory工廠方法,首先是構造函數:
class NormalModuleFactory extends Tapable { // context預設為命令執行路徑 // resolvers => {...} // options => options.module constructor(context, resolvers, options) { super(); // 該參數在WebpackOptionsApply方法中被賦值 this.resolvers = resolvers; // 處理module.rules或module.loaders // 兩者意義一樣 this.ruleSet = new RuleSet(options.rules || options.loaders); // 誰會去設置這玩意???預設參數設置那會被置為true // 這裡會生成一個返回布爾值的函數 可以簡單理解為!! this.cachePredicate = typeof options.unsafeCache === "function" ? options.unsafeCache : Boolean.bind(null, options.unsafeCache); this.context = context || ""; // 解析緩存 this.parserCache = {}; // 這兩個方法超級長 this.plugin("factory", () => (result, callback) => { /**/ }); this.plugin("resolver", () => (data, callback) => { /**/ }) } }
也是一個繼承了Tapable框架的類,構造函數除去兩個事件流註入不看,剩餘的內容只有RuleSet那一塊,也是本節的主要內容。
從參數可以看出,這裡是對module.rules處理的地方,本節中測試配置添加了rules方便測試:
const path = require('path'); // resolve => path.join(__dirname,'..',path) module.exports = { entry: './input.js', output: { filename: 'output.js' }, module: { rules: [ { test: /\.js$/, loader: 'babel-loader', include: [resolve('src'), resolve('test')] }, { test: /\.css/, loader: 'css-loader!style-loader' } ] } };
只針對配置中有或者常用且官網有解釋的參數進行。
RuleSet
構造函數如下:
module.exports = class RuleSet { constructor(rules) { // 空對象 this.references = Object.create(null); // 格式化 this.rules = RuleSet.normalizeRules(rules, this.references, "ref-"); } }
生成了一個純凈對象並調用了本地靜態方法進行格式化:
class RuleSet { constructor(rules) { /**/ } // rules => 傳進來的配置數組 // refs => 純凈對象 // ident => 'ref-' static normalizeRules(rules, refs, ident) { // 分數組與非數組 if (Array.isArray(rules)) { return rules.map((rule, idx) => { return RuleSet.normalizeRule(rule, refs, `${ident}-${idx}`); }); } else if (rules) { return [RuleSet.normalizeRule(rules, refs, ident)]; } // 未配置rules直接返回空數組 else { return []; } } }
這裡也區分了數組參數與非數組參數,但是有個小bug。
看代碼可以很容易猜到,數組與非數組的情況理論上是這樣的:
// 數組 module.exports = { // ... module: { rules: [{ test: /\.vue$/, loader: 'vue-loader' }, /*...*/ ] } }; // 非數組 module.exports = { // ... module: { rules: { test: /\.vue$/, loader: 'vue-loader' } } };
因為傳非數組,會被包裝成一個數組,所以這種情況屬於單loader配置。
但是,這樣配置是會報錯的,因為過不了validateSchema的驗證,測試結果如圖:
這就很尷尬了,提供了非數組形式的處理方式,但是又不通過非數組的校驗,所以這基本上是永遠不會被執行的代碼。
管他的,估計源碼太大,開發者已經顧不上了。
下麵正式進行格式化階段,源碼整理如下:
class RuleSet { constructor(rules) { /**/ }; // ident => 'ref-${index}' static normalizeRule(rule, refs, ident) { // 傳入字元串 // 'css-loader' => use:[{loader:'css-loader'}] if (typeof rule === "string") return { use: [{ loader: rule }] }; // 非法參數 if (!rule) throw new Error("Unexcepted null when object was expected as rule"); if (typeof rule !== "object") throw new Error("Unexcepted " + typeof rule + " when object was expected as rule (" + rule + ")"); const newRule = {}; let useSource; let resourceSource; let condition; // test if (rule.test || rule.include || rule.exclude) { /**/ } if (rule.resource) { /**/ } // 官網doc都懶得解釋 估計是幾個很弱智的參數 if (rule.resourceQuery) { /**/ } if (rule.compiler) { /**/ } if (rule.issuer) { /**/ } // loader、loaders只能用一個 if (rule.loader && rule.loaders) throw new Error(RuleSet.buildErrorMessage(rule, new Error("Provided loader and loaders for rule (use only one of them)"))); const loader = rule.loaders || rule.loader; // 處理loader if (typeof loader === "string" && !rule.options && !rule.query) { checkUseSource("loader"); newRule.use = RuleSet.normalizeUse(loader.split("!"), ident); } else if (typeof loader === "string" && (rule.options || rule.query)) { checkUseSource("loader + options/query"); newRule.use = RuleSet.normalizeUse({ loader: loader, options: rule.options, query: rule.query }, ident); } else if (loader && (rule.options || rule.query)) { throw new Error(RuleSet.buildErrorMessage(rule, new Error("options/query cannot be used with loaders (use options for each array item)"))); } else if (loader) { checkUseSource("loaders"); newRule.use = RuleSet.normalizeUse(loader, ident); } else if (rule.options || rule.query) { throw new Error(RuleSet.buildErrorMessage(rule, new Error("options/query provided without loader (use loader + options)"))); } // 處理use if (rule.use) { checkUseSource("use"); newRule.use = RuleSet.normalizeUse(rule.use, ident); } // 遞歸處理內部rules if (rule.rules) newRule.rules = RuleSet.normalizeRules(rule.rules, refs, `${ident}-rules`); // 不知道是啥 if (rule.oneOf) newRule.oneOf = RuleSet.normalizeRules(rule.oneOf, refs, `${ident}-oneOf`); // const keys = Object.keys(rule).filter((key) => { return ["resource", "resourceQuery", "compiler", "test", "include", "exclude", "issuer", "loader", "options", "query", "loaders", "use", "rules", "oneOf"].indexOf(key) < 0; }); keys.forEach((key) => { newRule[key] = rule[key]; }); function checkUseSource(newSource) { /**/ } function checkResourceSource(newSource) { /**/ } if (Array.isArray(newRule.use)) { newRule.use.forEach((item) => { if (item.ident) { refs[item.ident] = item.options; } }); } return newRule; } }
總體來看源碼內容如下:
1、生成newRules對象保存轉換後的rules
2、處理單字元串rule
3、處理test、include、exclude參數
4、處理resource、resourseQuery、compiler、issuer參數
5、處理loader、loaders、options、query參數
6、處理use參數
7、遞歸處理rules參數
8、處理oneOf參數
內容比較多,還是分兩節講吧。