细说webpack源码之compile流程

网友投稿 322 2023-03-03


细说webpack源码之compile流程

上篇文章给大家介绍了细说webpack源码之compile流程-rules参数处理技巧(1),    细说webpack源码之compile流程-入口函数run

大家可以点击查看。

第一步处理rule为字符串,直接返回一个包装类,很简单看注释就好了。

test

然后处理test、include、exclude,如下:

if (rule.test || rule.include || rule.exclude) {

// 标记使用参数

checkResourceSource("test + include + exclude");

// 没有就是undefined

condition = {

test: rule.test,

include: rule.iMpyvvqXBSMnclude,

exclude: rule.exclude

};

// 处理常规参数

try {

newRule.resource = RuleSet.normalizeCondition(condition);

} catch (error) {

throw new Error(RuleSet.buildErrorMessage(condition, error));

}

}

checkResourceSource直接看源码:

let resourceSource;

// ...

function checkResourceSource(newSource) {

// 第一次直接跳到后面赋值

if (resourceSource && resourceSource !== newSource)

throw new Error(RuleSet.buildErrorMessage(rule, new Error("Rule can only have one resource source (provided " + newSource + " and " + resourceSource + ")")));

resourceSource = newSource;

}

这个用于检测配置来源的唯一性,后面会能看到作用,同样作用的还有checkUseSource方法。

随后将三个参数包装成一个对象传入normalizeCondition方法,该方法对常规参数进行函数包装:

class RuleSet {

constructor(rules) { /**/ };

static normalizeCondition(condition) {

// 假值报错

if (!condition) throw new Error("Expected condition but got falsy value");

// 检测给定字符串是否以这个开头

if (typeof condition === "string") { return str => str.indexOf(condition) === 0; }

// 函数直接返回

if (typeof condition === "function") { return condition; }

// 正则表达式返回一个正则的test函数

if (condition instanceof RegExp) { return condition.test.bind(condition); }

// 数组map递归处理 有一个满足返回true

if (Array.isArray(condition)) {

const items = condition.map(c => RuleSet.normalizeCondition(c));

return orMatcher(items);

}

if (typeof condition !== "object") throw Error("Unexcepted " + typeof condition + " when condition was expected (" + condition + ")");

const matchers = [];

// 对象会对每个值进行函数包装弹入matchers中

Object.keys(condition).forEach(key => {

const value = condition[key];

switch (key) {

case "or":

case "include":

case "test":

if (value)

matchers.push(RuleSet.normalizeCondition(value));

break;

case "and":

if (value) {

const items = value.map(c => RuleSet.normalizeCondition(c));

matchers.push(andMatcher(items));

}

break;

case "not":

case "exclude":

if (value) {

const matcher = RuleSet.normalizeCondition(value);

matchers.push(notMatcher(matcher));

}

break;

default:

throw new Error("Unexcepted property " + key + " in condition");

}

});

if (matchers.length === 0)

throw new Error("Excepted condition but got " + condition);

if (matchers.length === 1)

return matchers[0];

return andMatcher(matchers);

}

}

这里用js的rules做案例,看这个方法的返回:

class RuleSet {

constructor(rules) { /**/ };

/*

Example:

{

test: /\.js$/,

loader: 'babel-loader',

include: [resolve('src'), resolve('test')]

}

*/

/*

condition:

{

test: /\.js$/,

include: ['d:\\workspace\\src', 'd:\\workspace\\test'],

exclude: undefined

}

*/

static normalizeCondition(condition) {

// include返回类似于 [(str) => str.indexOf('d:\\workspace\\src') === 0,...] 的函数

if (typeof condition === "string") { return str => str.indexOf(condition) === 0; }

// test参数返回了 /\.js$/.test 函数

if (condition instanceof RegExp) { return condition.test.bind(condition); }

// include为数组

if (Array.isArray(condition)) {

const items = condition.map(c => RuleSet.normalizeCondition(c));

return orMatcher(items);

}

const matchers = [];

// 解析出['test','include','exclude']

Object.keys(condition).forEach(key => {

const value = condition[key];

switch (key) {

// 此value为一个数组

case "include":

case "test":

if (value)

matchers.push(RuleSet.normalizeCondition(value));

break;

// undefined跳过

case "exclude":

if (value) { /**/ }

break;

default:

throw new Error("Unexcepted property " + key + " in condition");

}

});

return andMatcher(matchers);

}

}

这里继续看orMatcher、andMatcher函数的处理:

function orMatcher(items) {

// 当一个函数被满足条件时返回true

return function(str) {

for (let i = 0; i < items.length; i++) {

if (items[i](str))

return true;

}

return false;

};

}

function andMatcher(items) {

// 当一个条件不满足时返回false

return function(str) {

for (let i = 0; i < items.length; i++) {

if (!items[i](str))

return false;

}

return true;

};

}

从字面意思也可以理解函数作用,比如说这里的include被包装成一个orMatcher函数,传入的字符串无论是以数组中任何一个元素开头都会返回true,andMatcher以及未出现的notMatcher同理。

resource

下面是对resource参数的处理,源码如下:

if (rule.resource) {

// 如果前面检测到了test || include || exclude参数 这里会报错

checkResourceSource("resource");

try {

newRule.resource = RuleSet.normalizeCondition(rule.resource);

} catch (error) {

throw new Error(RuleSet.buildErrorMessage(rule.resource, error));

}

}

可以看出这个参数与前面那个是互斥的,应该是老版API,下面两种方式实现是一样的:

/*

方式1:

rules:[

{

test: /\.js$/,

loader: 'babel-loader',

include: [resolve('src'), resolve('test')]

}

]

*/

/*

方式2:

rules:[

{

resource:{

test: /\.js$/,

include: [resolve('src'), resolve('test')]

exclude: undefined

}

}

]

*/

接下来的resourceQuery、compiler、issuer先跳过,脚手架没有,不知道怎么写。

loader

下面是另一块主要配置loader(s),这里loader与loaders作用一样的,当初还头疼啥区别:

const loader = rule.loaders || rule.loader;

// 单loader情况

if (typeof loader === "string" && !rule.options && !rule.query) {

checkUseSource("loader");

newRule.use = RuleSet.normalizeUse(loader.split("!"), ident);

}

// loader配合options或query出现

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);

}

// options与query同时出现报错

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)")));

}

/*

处理这种愚蠢用法时:

{

test: /\.css$/,

loader: [{ loader: 'less-loader' }, { loader: 'css-loader' }]

}

*/

else if (loader) {

checkUseSource("loaders");

newRule.use = RuleSet.normalizeUse(loader, ident);

}

// 单独出现options或者query报错

else if (rule.options || rule.query) {

throw new Error(RuleSet.buildErrorMessage(rule, new Error("options/query provided without loader (use loader + options)")));

}

之前举例的babel-loader就是第一种单loader配置,这里使用vue-loader嵌套的css配置作为示例。

首先normalizeUse方法如下:

static normalizeUse(use, ident) {

// 单loader字符串

if (Array.isArray(use)) {

return use

// 如果是单loader情况这里会返回[[loader1...],[loader2...]]

.map((item, idx) => RuleSet.normalizeUse(item, `${ident}-${idx}`))

// 扁平化后变成[loader1,loader2]

.reduce((arr, items) => arr.concat(items), []);

}

// 对象或字符串

return [RuleSet.normalizeUseItem(use, ident)];

};

先讲解有options或者query的模式,这里会把参数包装一个对象传入normalizeUse方法:

loader && (options || query)

// indet => 'ref-'

static normalizeUseItem(item, ident) {

if (typeof item === "function")

return item;

if (typeof item === "string") {

return RuleSet.normalizeUseItemString(item);

}

const newItem = {};

if (item.options && item.query) throw new Error("Provided options and query in use");

if (!item.loader) throw new Error("No loader specified");

newItem.options = item.options || item.query;

// 防止options:null的情况

if (typeof newItem.options === "object" && newItem.options) {

// 这里只是为了处理ident参数

if (newItem.options.ident)

newItem.ident = newItem.options.ident;

else

newItem.ident = ident;

}

// 取出loader参数

const keys = Object.keys(item).filter(function(key) {

return ["options", "query"].indexOf(key) < 0;

});

keys.forEach(function(key) {

newItem[key] = item[key];

});

/*

newItem =

{

loader:'原字符串',

ident:'ref-', (或自定义)

options:{...}

}

*/

return newItem;

}

比起对test的处理,这里就简单的多,简述如下:

1、尝试取出options(query)中的ident参数,赋值给newItem的ident,没有就赋值为默认的ref-

2、取出对象中不是options、query的键,赋值给newItem,这里传进来的键只有三个,剩下的就是loader,这个filter是逗我???

3、返回newItem对象

总之,不知道为什么防止什么意外情况而写出来的垃圾代码,这段代码其实十分简单。

单loader

第二种情况是单字符串,会对'!'进行切割调用map方法处理,再调用reduce方法扁平化为一个数组,直接看normalizeUseItemString方法:

/*

Example:

{

test: /\.css$/,

loader: 'css-loader?{opt:1}!style-loader'

}

返回:

[{loader:'css-loader',options:{opt:1}},

{loader:'style-loader'}]

*/

static normalizeUseItemString(useItemString) {

// 根据'?'切割获取loader的参数

const idx = useItemString.indexOf("?");

if (idx >= 0) {

return {

loader: useItemString.substr(0, idx),

// 后面的作为options返回

options: useItemString.substr(idx + 1)

};

}

return {

loader: useItemString

};

}

这种就是串行调用loader的处理方式,代码很简单。

Tips

这里有一点要注意,一旦loader使用了串行调用方式,不要传options或者query参数,不然loader不会被切割解析!!!

这两种方式只是不同的使用方法,最后的结果都是一样的。

use

下面是use参数的解析,估计因为这是老版的API,所以格式要求严格,处理比较随便:

if (rule.use) {

checkUseSource("use");

newRule.use = RuleSet.normalizeUse(rule.use, ident);

}

如果不用loader(s),可以用use替代,但是需要按照格式写,比如说上述串行简写的loader,在use中就需要这样写:

/*

{

test:/\.css$/,

user:['css-loader','style-loader]

}

*/

而对应options或query,需要这样写:

/*

{

test:/\.css$/,

user:{

loader:'css-loader',

options:'1'

}

}

*/

因为基本上不用了,所以这里简单看一下。

rules、oneOf

if (rule.rules)

newRule.rules = RuleSet.normalizeRules(rule.rules, refs, `${ident}-rules`);

if (rule.oneOf)

newRule.oneOf = RuleSet.normalizeRules(rule.oneOf, refs, `${ident}-oneOf`);

这两个用得少,也没啥难理解的。

下一步是过滤出没有处理的参数,添加到newRuls上:

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];

});

基本上用到都是test、loader、options,暂时不知道有啥额外参数。

ident

// 防止rules:[]的情况

if (Array.isArray(newRule.use)) {

newRule.use.forEach((item) => {

// ident来源于options/query的ident参数

if (item.ident) {

refs[item.ident] = item.options;

}

});

}

最后这个地方是终于用到了传进来的纯净对象refs。

如果在options中传了ident参数,会填充这个对象,key为ident值,value为对应的options。

至此,所有rules的规则已经解析完毕,真是配置简单处理复杂。

总结

以上所述是给大家介绍的细说webpack源码之compile流程-rules参数处理技巧(2),希望对大家有所帮助,如果大家有任何疑问请给我留言,会及时回复大家的。在此也非常感谢大家对我们网站的支持!


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:自建api管理(如何搭建api服务器)
下一篇:java接口设计(java接口设计实验)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~