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参数

  内容比较多,还是分两节讲吧。

.18-浅析webpack源码之compile流程-rules参数处理(1)的更多相关文章

  1. .19-浅析webpack源码之compile流程-rules参数处理(2)

    第一步处理rule为字符串,直接返回一个包装类,很简单看注释就好了. test/include/exclude 然后处理test.include.exclude,如下: if (rule.test | ...

  2. .17-浅析webpack源码之compile流程-入口函数run

    本节流程如图: 现在正式进入打包流程,起步方法为run: Compiler.prototype.run = (callback) => { const startTime = Date.now( ...

  3. .20-浅析webpack源码之compile流程-Template模块

    这里的编译前指的是开始触发主要的事件流this-compilaiton.compilation之前,由于还有一些准备代码,这一节全部弄出来. 模块基本上只走构造函数,具体的方法调用的时候再具体讲解. ...

  4. 从Webpack源码探究打包流程,萌新也能看懂~

    简介 上一篇讲述了如何理解tapable这个钩子机制,因为这个是webpack程序的灵魂.虽然钩子机制很灵活,而然却变成了我们读懂webpack道路上的阻碍.每当webpack运行起来的时候,我的心态 ...

  5. .29-浅析webpack源码之Resolver.prototype.resolve

    在上一节中,最后返回了一个resolver,本质上就是一个Resolver对象: resolver = new Resolver(fileSystem); 这个对象的构造函数非常简单,只是简单的继承了 ...

  6. .29-浅析webpack源码之doResolve事件流(1)

    在上一节中,最后返回了一个resolver,本质上就是一个Resolver对象: resolver = new Resolver(fileSystem); 这个对象的构造函数非常简单,只是简单的继承了 ...

  7. .30-浅析webpack源码之doResolve事件流(1)

    这里所有的插件都对应着一个小功能,画个图整理下目前流程: 上节是从ParsePlugin中出来,对'./input.js'入口文件的路径做了处理,返回如下: ParsePlugin.prototype ...

  8. .34-浅析webpack源码之事件流make(3)

    新年好呀~过个年光打游戏,function都写不顺溜了. 上一节的代码到这里了: // NormalModuleFactory的resolver事件流 this.plugin("resolv ...

  9. .30-浅析webpack源码之doResolve事件流(2)

    这里所有的插件都对应着一个小功能,画个图整理下目前流程: 上节是从ParsePlugin中出来,对'./input.js'入口文件的路径做了处理,返回如下: ParsePlugin.prototype ...

随机推荐

  1. FastReport二维码打印存在的问题

    FastReport二维码打印存在的问题 (2018-05-21 09:28:38) 转载▼ 标签: delphi 分类: Delphi10.2 FastReport本身支持二维码,实际应用中遇到这样 ...

  2. CxGrid 改变某行或单元格的颜色

    CxGrid 改变某行或单元格的颜色   一个表(T)的结构结构如下. ID Test 1 20012 14443 17885 26456 4568 cxGrid成功连接到该表, 如果要实现单元格特效 ...

  3. SWFUpload 在ie9上出现的bug

    SWFUpload 在ie9下会出现js错误 参考以下几个网址,备忘: http://code.google.com/p/swfupload/issues/detail?id=348 http://c ...

  4. Win7的“以管理员身份运行”

    如果HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\EnableLUA被设置为0,则"以管理员身份运行" ...

  5. Strust2总结

    1. JavaEE软件三层结构和MVC的区别? JavaEE软件三层机构是由sun公司提供JavaEE开发规范的:Web层(表现层).业务逻辑层.数据持久层.[其中WEB层会使用前端控制器模式] MV ...

  6. python3 os.path.realpath(__file__) 和 os.path.cwd() 方法的区别

    python3 os.path.realpath(__file__) 和 os.path.cwd() 方法的区别 os.path.realpath 获取当前执行脚本的绝对路径. os.path.rea ...

  7. Django(ORM常用字段)

    day68 参考:http://www.cnblogs.com/liwenzhou/p/8688919.html 1. Django ORM常用字段:             1. AutoField ...

  8. Keras学习笔记——Hello Keras

    最近几年,随着AlphaGo的崛起,深度学习开始出现在各个领域,比如无人车.图像识别.物体检测.推荐系统.语音识别.聊天问答等等.因此具备深度学习的知识并能应用实践,已经成为很多开发者包括博主本人的下 ...

  9. wcf返回值报错解析

    问题来源 最近在项目中使用wcf,因为是一个新手,对新的东西总是比较敬畏,不过一切都是进行得很顺利,运行的时候,突然报了错,编译器提示的错误大概是:“InvalidOperationException ...

  10. select2插件使用小记2 - 多选联动 - 笔记

    这是select2插件使用的第二篇,可参考第一篇 select2插件使用小记.上一篇主要是关于基本的使用,这篇主要是关于多选,及联动的.侧重点不同. 效果图如下: 遵从W3C标准:结构.样式.行为.以 ...