细说webpack源码之compile流程-rules参数处理技巧(1)


Posted in Javascript onDecember 26, 2017

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

大家可以点击查看。

Tips:写到这里,需要对当初的规则进行修改。在必要的地方,会在webpack.config.js中设置特殊的参数来跑源码,例如本例会使用module:{rules:[...]}来测试,基本上测试参数均取自于vue脚手架(太复杂的删掉)。

下面两节的主要流程图如下:

细说webpack源码之compile流程-rules参数处理技巧(1)

在进入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的验证,测试结果如图:

细说webpack源码之compile流程-rules参数处理技巧(1)

细说webpack源码之compile流程-rules参数处理技巧(1)

这就很尴尬了,提供了非数组形式的处理方式,但是又不通过非数组的校验,所以这基本上是永远不会被执行的代码。

管他的,估计源码太大,开发者已经顾不上了。

下面正式进行格式化阶段,源码整理如下:

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

总结

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

Javascript 相关文章推荐
jquery中append()与appendto()用法分析
Nov 14 Javascript
JS组件Bootstrap Table表格多行拖拽效果实现代码
Dec 08 Javascript
全面详细的jQuery常见开发技巧手册
Feb 21 Javascript
微信小程序 获取相册照片实例详解
Nov 16 Javascript
jquery 实现复选框的全选操作实例代码
Jan 24 Javascript
JavaScript中的遍历详解(多种遍历)
Apr 07 Javascript
利用vue.js实现被选中状态的改变方法
Feb 08 Javascript
动态内存分配导致影响Javascript性能的问题
Dec 18 Javascript
koa2+vue实现登陆及登录状态判断
Aug 15 Javascript
微信小程序缓存支持二次开发封装实现解析
Dec 16 Javascript
Vue组件间的通信pubsub-js实现步骤解析
Mar 11 Javascript
vue中v-for循环选中点击的元素并对该元素添加样式操作
Jul 17 Javascript
Bootstrap 模态框多次显示后台提交多次BUG的解决方法
Dec 26 #Javascript
细说webpack源码之compile流程-入口函数run
Dec 26 #Javascript
Vue 进入/离开动画效果
Dec 26 #Javascript
node.js中路由,中间件,ge请求和post请求的参数详解
Dec 26 #Javascript
Angular实现可删除并计算总金额的购物车功能示例
Dec 26 #Javascript
浅谈React深度编程之受控组件与非受控组件
Dec 26 #Javascript
使用vue实现简单键盘的示例(支持移动端和pc端)
Dec 25 #Javascript
You might like
PHP EOT定界符的使用详解
2008/09/30 PHP
一个php Mysql类 可以参考学习熟悉下
2009/06/21 PHP
PHP 文件缓存的性能测试
2010/04/25 PHP
PHP 缓存实现代码及详细注释
2010/05/16 PHP
如何用PHP实现插入排序?
2013/04/10 PHP
PHP中让curl支持sock5的代码实例
2015/01/21 PHP
php定时执行任务设置详解
2015/02/06 PHP
Javascript 各浏览器的 Javascript 效率对比
2008/01/23 Javascript
浅谈javascript的分号的使用
2015/05/12 Javascript
jquery实现滑动特效代码
2015/08/10 Javascript
jQuery ajax应用总结
2016/06/02 Javascript
jQuery事件绑定用法详解
2016/09/08 Javascript
JS只能输入正整数的简单实例
2016/10/07 Javascript
jQuery 禁止表单用户名、密码自动填充功能
2017/10/30 jQuery
vue全局组件与局部组件使用方法详解
2018/03/29 Javascript
vue2实现搜索结果中的搜索关键字高亮的代码
2018/08/29 Javascript
详解react内联样式使用webpack将px转rem
2018/09/13 Javascript
Bootstrap Paginator+PageHelper实现分页效果
2018/12/29 Javascript
javascript自定义日期比较函数用法示例
2019/07/22 Javascript
Python中一些自然语言工具的使用的入门教程
2015/04/13 Python
python获取元素在数组中索引号的方法
2015/07/15 Python
python爬取网易云音乐评论
2018/11/16 Python
在pycharm中设置显示行数的方法
2019/01/16 Python
Python 用三行代码提取PDF表格数据
2019/10/13 Python
如何用Python 实现全连接神经网络(Multi-layer Perceptron)
2020/10/15 Python
python中的测试框架
2020/11/13 Python
HTML5实时语音通话聊天MP3压缩传输3KB每秒
2019/08/28 HTML / CSS
打造经典复古风格的品牌:Alice + Olivia(爱丽丝+奥利维亚)
2016/09/07 全球购物
英国网上购买肉类网站:Great British Meat
2018/10/17 全球购物
婚礼主持词
2014/03/13 职场文书
职业生涯规划书前言
2014/04/15 职场文书
暑假安全保证书
2015/02/28 职场文书
2015年团队工作总结范文
2015/05/04 职场文书
通讯稿格式及范文
2015/07/22 职场文书
python3读取文件指定行的三种方法
2021/05/24 Python
MyBatis自定义SQL拦截器示例详解
2021/10/24 Java/Android