细说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 Tips(4) 一些关于提高JQuery性能的Tips
Dec 19 Javascript
JQuery.ajax传递中文参数的解决方法 推荐
Mar 28 Javascript
html dom节点操作(获取/修改/添加或删除)
Jan 23 Javascript
jQuery手机浏览器中拖拽动作的艰难性分析
Feb 04 Javascript
使用纯javascript实现经典扫雷游戏
Apr 23 Javascript
小巧强大的jquery layer弹窗弹层插件
Dec 06 Javascript
如何用js判断dom是否有存在某class的值
Feb 13 Javascript
基于构造函数的五种继承方法小结
Jul 27 Javascript
微信小程序的生命周期的详解
Oct 19 Javascript
实例分析JS与Node.js中的事件循环
Dec 12 Javascript
详解vue-cli 构建项目 vue-cli请求后台接口 vue-cli使用axios、sass、swiper
May 28 Javascript
详解在React中跨组件分发状态的三种方法
Aug 09 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静态变量当缓存的方法
2013/11/13 PHP
XAMPP安装与使用方法详细解析
2013/11/27 PHP
php序列化函数serialize() 和 unserialize() 与原生函数对比
2015/05/08 PHP
PHP实现微信提现功能(微信商城)
2019/11/21 PHP
收藏一些不常用,但是有用的代码
2007/03/12 Javascript
JQuery toggle使用分析
2009/11/16 Javascript
JavaScript类和继承 constructor属性
2010/03/04 Javascript
jQuery中[attribute*=value]选择器用法实例
2014/12/31 Javascript
jquery实现简单手风琴菜单效果实例
2015/06/13 Javascript
jQuery和JavaScript节点插入元素的方法对比
2016/11/18 Javascript
jQuery+ajax的资源回收处理机制分析
2017/01/07 Javascript
谈谈Vue.js——vue-resource全攻略
2017/01/16 Javascript
利用javascript实现的三种图片放大镜效果实例(附源码)
2017/01/23 Javascript
简单实现js无缝滚动效果
2017/02/05 Javascript
jQuery 开发之EasyUI 添加数据的实例
2017/09/26 jQuery
利用three.js画一个3D立体的正方体示例代码
2017/11/19 Javascript
vue-cli webpack配置文件分析
2019/05/20 Javascript
vue实现随机验证码功能(完整代码)
2019/12/10 Javascript
在vue中实现某一些路由页面隐藏导航栏的功能操作
2020/09/21 Javascript
JS实现页面侧边栏效果探究
2021/01/08 Javascript
使用Python的Twisted框架编写简单的网络客户端
2015/04/16 Python
详解Python的Django框架中的Cookie相关处理
2015/07/22 Python
windows下ipython的安装与使用详解
2016/10/20 Python
解决Django的request.POST获取不到内容的问题
2018/05/28 Python
解决pycharm 远程调试 上传 helpers 卡住的问题
2019/06/27 Python
Python使用plt.boxplot() 参数绘制箱线图
2020/06/04 Python
websocket+sockjs+stompjs详解及实例代码
2018/11/30 HTML / CSS
英国最大的香水商店:The Fragrance Shop
2018/07/06 全球购物
Athleta官网:购买女士瑜伽服、技术运动服和休闲运动服
2020/11/12 全球购物
如何在.net Winform里面显示PDF文档
2012/09/11 面试题
邮政员工辞职信
2014/01/16 职场文书
幼教求职信
2014/03/12 职场文书
电子专业自荐信
2014/07/01 职场文书
民主生活会对照检查材料
2014/09/22 职场文书
小鞋子观后感
2015/06/05 职场文书
读书笔记怎么写
2015/07/01 职场文书