細說webpack源碼之compile流程-rules參數(shù)處理技巧(1)
上篇文章給大家介紹了細說webpack源碼之compile流程-rules參數(shù)處理技巧(2), 細說webpack源碼之compile流程-入口函數(shù)run
大家可以點擊查看。
Tips:寫到這里,需要對當(dāng)初的規(guī)則進行修改。在必要的地方,會在webpack.config.js中設(shè)置特殊的參數(shù)來跑源碼,例如本例會使用module:{rules:[...]}來測試,基本上測試參數(shù)均取自于vue腳手架(太復(fù)雜的刪掉)。
下面兩節(jié)的主要流程圖如下:

在進入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;
}
該方法生成了一個對象參數(shù),而對象中的值又分別調(diào)用了各自的工廠方法。
按順序,首先來看NormalModuleFactory工廠方法,首先是構(gòu)造函數(shù):
class NormalModuleFactory extends Tapable {
// context默認(rèn)為命令執(zhí)行路徑
// resolvers => {...}
// options => options.module
constructor(context, resolvers, options) {
super();
// 該參數(shù)在WebpackOptionsApply方法中被賦值
this.resolvers = resolvers;
// 處理module.rules或module.loaders
// 兩者意義一樣
this.ruleSet = new RuleSet(options.rules || options.loaders);
// 誰會去設(shè)置這玩意???默認(rèn)參數(shù)設(shè)置那會被置為true
// 這里會生成一個返回布爾值的函數(shù) 可以簡單理解為!!
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框架的類,構(gòu)造函數(shù)除去兩個事件流注入不看,剩余的內(nèi)容只有RuleSet那一塊,也是本節(jié)的主要內(nèi)容。
從參數(shù)可以看出,這里是對module.rules處理的地方,本節(jié)中測試配置添加了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'
}
]
}
};
只針對配置中有或者常用且官網(wǎng)有解釋的參數(shù)進行。
RuleSet
構(gòu)造函數(shù)如下:
module.exports = class RuleSet {
constructor(rules) {
// 空對象
this.references = Object.create(null);
// 格式化
this.rules = RuleSet.normalizeRules(rules, this.references, "ref-");
}
}
生成了一個純凈對象并調(diào)用了本地靜態(tài)方法進行格式化:
class RuleSet {
constructor(rules) { /**/ }
// rules => 傳進來的配置數(shù)組
// refs => 純凈對象
// ident => 'ref-'
static normalizeRules(rules, refs, ident) {
// 分?jǐn)?shù)組與非數(shù)組
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直接返回空數(shù)組
else {
return [];
}
}
}
這里也區(qū)分了數(shù)組參數(shù)與非數(shù)組參數(shù),但是有個小bug。
看代碼可以很容易猜到,數(shù)組與非數(shù)組的情況理論上是這樣的:
// 數(shù)組
module.exports = {
// ...
module: {
rules: [{ test: /\.vue$/, loader: 'vue-loader' }, /*...*/ ]
}
};
// 非數(shù)組
module.exports = {
// ...
module: {
rules: { test: /\.vue$/, loader: 'vue-loader' }
}
};
因為傳非數(shù)組,會被包裝成一個數(shù)組,所以這種情況屬于單loader配置。
但是,這樣配置是會報錯的,因為過不了validateSchema的驗證,測試結(jié)果如圖:


這就很尷尬了,提供了非數(shù)組形式的處理方式,但是又不通過非數(shù)組的校驗,所以這基本上是永遠不會被執(zhí)行的代碼。
管他的,估計源碼太大,開發(fā)者已經(jīng)顧不上了。
下面正式進行格式化階段,源碼整理如下:
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
}]
};
// 非法參數(shù)
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) { /**/ }
// 官網(wǎng)doc都懶得解釋 估計是幾個很弱智的參數(shù)
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);
}
// 遞歸處理內(nèi)部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;
}
}
總體來看源碼內(nèi)容如下:
1、生成newRules對象保存轉(zhuǎn)換后的rules
2、處理單字符串rule
3、處理test、include、exclude參數(shù)
4、處理resource、resourseQuery、compiler、issuer參數(shù)
5、處理loader、loaders、options、query參數(shù)
6、處理use參數(shù)
7、遞歸處理rules參數(shù)
8、處理oneOf參數(shù)
總結(jié)
以上所述是小編給大家介紹的細說webpack源碼之compile流程-rules參數(shù)處理技巧(1),希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
由JavaScript技術(shù)實現(xiàn)的web小游戲(不含網(wǎng)游)
伴隨Ajax與網(wǎng)頁游戲的崛起,曾幾何時JavaScript也成了游戲開發(fā)時可供選擇的技術(shù)之一,文本 僅列舉數(shù)項由JavaScript技術(shù)實現(xiàn)的web小游戲(不含網(wǎng)游),聊作參考之用。2010-06-06
javascript fullscreen全屏實現(xiàn)代碼
用了實現(xiàn)打開一個滿屏的代碼2009-04-04
Bootstrap下拉菜單Dropdowns的實現(xiàn)代碼
這篇文章主要為大家詳細介紹了Bootstrap下拉菜單Dropdowns的實現(xiàn)代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-03-03

