.7-浅析webpack源码之WebpackOptionsDefaulter模块
WebpackOptionsDefaulter模块
通过参数检测后,会根据单/多配置进行处理,本文基于单配置,所以会进行到如下代码:
if (Array.isArray(options)) {
compiler = new MultiCompiler(options.map(options => webpack(options)));
} else if (typeof options === "object") {
// TODO webpack 4: process returns options
// 这里的处理有部分是为了webpack4.0做准备
new WebpackOptionsDefaulter().process(options);
// ...
}
模块的作用是进行默认值的设置,流程图如下:
进入该模块:
"use strict"; const OptionsDefaulter = require("./OptionsDefaulter");
const Template = require("./Template"); class WebpackOptionsDefaulter extends OptionsDefaulter {
constructor() {
super();
this.set("devtool", false);
// 大量的this.set...
}
}
module.exports = WebpackOptionsDefaulter;
可以看到,这个模块的内容是用ES6的新语法写的,很好理解,因为这个模块是只是针对webpack的默认设置,所以主要功能内容应该都在原型上面,直接进入OptionsDefaulter模块:
"use strict"; function getProperty(obj, name) { /**/ } function setProperty(obj, name, value) { /**/ } class OptionsDefaulter {
constructor() {
this.defaults = {};
this.config = {};
} process(options) {
// TODO: change this for webpack 4: options = Object.assign({}, options);
for (let name in this.defaults) {
switch (this.config[name]) {
// case...
}
}
} set(name, config, def) {
if (arguments.length === 3) {
this.defaults[name] = def;
this.config[name] = config;
} else {
this.defaults[name] = config;
delete this.config[name];
}
}
}
module.exports = OptionsDefaulter;
这个模块也是用ES6写的,内容比较简单,主要内容如下:
1、构造函数定义两个对象defaults,config
2、两个原型方法process,set
3、两个工具方法getProperty,setProperty
构造函数创建了两个对象,defaults对象保存了参数的默认值,而config对象保存了某些参数的特殊处理方式。
由于原型方法依赖于工具方法,所以从工具方法开始讲解:
getProperty
// obj就是options
function getProperty(obj, name) {
// 切割name
name = name.split(".");
// 注意这里是length-1
for (let i = 0; i < name.length - 1; i++) {
// 层层赋值
obj = obj[name[i]];
// 若obj非对象返回直接返回undefined
if (typeof obj !== "object" || !obj) return;
}
// 返回最后一层的值
return obj[name.pop()];
}
这个函数有意思,直接看比较懵逼,需要来个案例:
// obj => {entry:'./inpuit.js',output:{filename:'output.js'}}
// name => output.filename
function getProperty(obj, name) {
// 切割后得到[output,filename]
name = name.split(".");
// 第二次跳出循环
for (let i = 0; i < name.length - 1; i++) {
// obj => {filename:'output.js'}
obj = obj[name[i]];
// 返回了对象这里就不返回了
if (typeof obj !== "object" || !obj) return;
}
// 注意这里obj是options.output
// 所以返回的是options.output.filename
return obj[name.pop()];
}
可以看出,这个函数是尝试获取对象的某个键,键的递进用点来连接,如果获取失败返回undefined。
精妙的函数!避免了多次判断 obj[key] 是否为undefined,直接用字符串的方式解决了此问题。
setProperty
function setProperty(obj, name, value) {
name = name.split(".");
for (let i = 0; i < name.length - 1; i++) {
// 非对象直接返回undefined
if (typeof obj[name[i]] !== "object" && typeof obj[name[i]] !== "undefined") return;
// 设置为对象
if (!obj[name[i]]) obj[name[i]] = {};
obj = obj[name[i]];
}
// 设置对应的值
obj[name.pop()] = value;
}
有了前面的getProperty,这个就比较好懂了。
下面就是原型方法:
set
set(name, config, def) {
if (arguments.length === 3) {
this.defaults[name] = def;
this.config[name] = config;
}else {
this.defaults[name] = config;
delete this.config[name];
}
}
没什么好讲的,根据传参数量对构造函数生成的对象进行复制。
至于process方法会在后面调用,所以这里暂时不讲,回到WebpackOptionsDefaulter中的大量set,抽取两个情况的进行讲解:
this.set("output.chunkFilename", "make", (options) => {
const filename = options.output.filename;
return filename.indexOf("[name]") >= 0 ? filename.replace("[name]", "[id]") : "[id]." + filename;
});
this.set("resolve.extensions", [".js", ".json"]);
调用这两个方法后,defaults与config对象如下:
defaults = {
"resolve.extensions": [".js", ".json"],
"output.chunkFilename": (options) => {
const filename = options.output.filename;
return filename.indexOf("[name]") >= 0 ? filename.replace("[name]", "[id]") : "[id]." + filename;
})
}
config = {
"output.chunkFilename": "make"
}
函数中,由于output.filename是必传参数,所以能取到值。
chunkFilename的函数会对字符串中的 [name] 置换成 [id] ,如果没有就加上 [id.] 前缀。
例如多输出中经常会取 [name].js 作为输出文件名,在这里chunkFilename的默认值就是 [id].js 。
大量大量的set后,可以来看看process函数了,为方便展示,写成function形式:
// 传进来的options
function process(options) {
// 遍历defaults对象
for (let name in this.defaults) {
// 匹配config参数
switch (this.config[name]) {
// 默认情况直接进行赋值
case undefined:
if (getProperty(options, name) === undefined)
setProperty(options, name, this.defaults[name]);
break;
// 用来保证根键为对象
case "call":
setProperty(options, name, this.defaults[name].call(this, getProperty(options, name), options), options);
break;
// 默认值通过调用函数注入
// 传入一个options参数
case "make":
if (getProperty(options, name) === undefined)
setProperty(options, name, this.defaults[name].call(this, options), options);
break;
// 将默认值添加进已有的数组中
case "append":
{
let oldValue = getProperty(options, name);
if (!Array.isArray(oldValue)) oldValue = [];
oldValue.push.apply(oldValue, this.defaults[name]);
setProperty(options, name, oldValue);
break;
}
default:
throw new Error("OptionsDefaulter cannot process " + this.config[name]);
}
}
}
根据config的情况有4种默认值注入方式,其中函数调用方式可以更加灵活的进行配置。
为求完整,在某些set中有调用 Template.toIdentifier 方法,看一眼其内部实现:
// 匹配所有非大小写字母$_
const IDENTIFIER_NAME_REPLACE_REGEX = /^[^a-zA-Z$_]/;
// 匹配所有非大小写字母数字$_
const IDENTIFIER_ALPHA_NUMERIC_NAME_REPLACE_REGEX = /[^a-zA-Z0-9$_]/g;
module.exports = class Template extends Tapable {
constructor(outputOptions) { /**/ } //静态方法 直接调用
static toIdentifier(str) {
if (typeof str !== "string") return "";
// 特殊符号全部置换为_
return str.replace(IDENTIFIER_NAME_REPLACE_REGEX, "_").replace(IDENTIFIER_ALPHA_NUMERIC_NAME_REPLACE_REGEX, "_");
} // 其余方法...
}
只是一个普通的字符替换函数而已。
一句话总结:WebpackOptionsDefaulter模块对options配置对象添加了大量的默认参数。
完事~
.7-浅析webpack源码之WebpackOptionsDefaulter模块的更多相关文章
- .15-浅析webpack源码之WebpackOptionsApply模块-plugin事件流总览
总体过了一下后面的流程,发现Compiler模块确实不适合单独讲解,这里继续讲解后面的代码: compiler.options = new WebpackOptionsApply().process( ...
- .6-浅析webpack源码之validateSchema模块
validateSchema模块 首先来看错误检测: const webpackOptionsValidationErrors = validateSchema(webpackOptionsSchem ...
- .4-浅析webpack源码之convert-argv模块
上一节看了一眼预编译的总体代码,这一节分析convert-argv模块. 这个模块主要是对命令参数的解析,也是yargs框架的核心用处. 生成默认配置文件名数组 module.exports = fu ...
- .9-浅析webpack源码之NodeEnvironmentPlugin模块总览
介绍Compiler的构造比较无趣,不如先过后面的,在用到compiler的时候再做讲解. 这一节主要讲这行代码: // 不管这里 compiler = new Compiler(); compile ...
- .14-浅析webpack源码之Watchpack模块
解决掉了最头疼的DirectoryWatcher内部实现,这一节可以结束NodeWatchFileSystem模块. 关于watch的应用场景,仔细思考了下,这不就是热重载的核心嘛. 首先是监视文件, ...
- .13-浅析webpack源码之WatcherManager模块
从模块流可以看出,这个NodeWatchFileSystem模块非常深,这里暂时不会深入到chokidar模块,有点太偏离本系列文章了,从WatcherManager开始讲解. 流程如图: 源码非常简 ...
- .12-浅析webpack源码之NodeWatchFileSystem模块总览
剩下一个watch模块,这个模块比较深,先大概过一下整体涉及内容再分部讲解. 流程图如下: NodeWatchFileSystem const Watchpack = require("wa ...
- .11-浅析webpack源码之Storage模块
至此已完成NodeJsInputFileSysten模块的讲解,下一步就是实际实用的模块: compiler.inputFileSystem = new CachedInputFileSystem(n ...
- .10-浅析webpack源码之graceful-fs模块
在cachedInput.output.watch三大文件系统中,output非常简单,没有必要讲,其余两个模块依赖于input模块,而input主要是引用了graceful-fs的部分API,所以这 ...
随机推荐
- LOJ-10105(欧拉回路模板,套圈法,递归)
题目链接:传送门 思路: (1)用邻接表存储有向图和无向图,有向图和无向图的每条边均站两个单元,无向图有正向边和反向边的区分. (2)有向图有欧拉回路:所有点的入度=出度: 无向图有欧拉回路:所有点的 ...
- TensorFlow基本--张量
在TensorFlow中所有的数据都通过张量的形式表示,从功能上看张量可以被简单的理解为多维数据,其中零阶张量表示标量(一个数),第一阶张量为向量(一个一维数组),第n阶向量可以理解为一个n维数组. ...
- 《mysql必知必会》学习_第15章_20180806_欢
第15章:联结表 P98 外键:外键为某个表的一列A,同时这一列包含另一个表的主键值B(B属于A,等于或者小于的关系) P99 select vend_name,prod_name,prod_pric ...
- 收藏的blog
https://www.cnblogs.com/xifengxiaoma/tag/vue/ https://www.cnblogs.com/xifengxiaoma/p/9400200.html
- springmvc接受及响应ajax请求。 以及@RequestBody 和@ResponseBody注解的使用
1.发送ajax请求 $.ajax({ url:"user/testAjax", contentType:"application/json;charset=UTF-8& ...
- 如何用impress.js写有逼格的ppt
概述 这是我学习课程impress让你的内容"舞"起来而做的总结和练手. 你可以点这里在线预览我做的ppt 注意:等加载完了之后,点击空格键翻页! 简化模板 下面是一个简化的模板 ...
- Servlet案例2:文件下载
首先,解决两个小问题 1.输入中文乱码问题 一个小Demo即可解决: package demo; import java.io.IOException; import java.io.PrintWri ...
- python中的变量和算数运算
先说下变量的作用: 用来保存数据,为什么要保存? 后面要使用. 变量的概念: 变量就是用来存储一些信息,供程序以后调用或者操作修改.变量为标记数据提供了一种描述性的名字,以便我们的程序可以被程序的阅读 ...
- spring boot 上传文件
spring.servlet.multipart.max-file-size=23KBspring.servlet.multipart.maxRequestSize=23KB <form act ...
- JSTL-简介
JSTL全称为 JSP Standard Tag Library 即JSP标准标签库. JSTL作为最基本的标签库,提供了一系列的JSP标签,实现了基本的功能:集合的遍历.数据的输出.字符串的处理.数 ...