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模块的更多相关文章

  1. .15-浅析webpack源码之WebpackOptionsApply模块-plugin事件流总览

    总体过了一下后面的流程,发现Compiler模块确实不适合单独讲解,这里继续讲解后面的代码: compiler.options = new WebpackOptionsApply().process( ...

  2. .6-浅析webpack源码之validateSchema模块

    validateSchema模块 首先来看错误检测: const webpackOptionsValidationErrors = validateSchema(webpackOptionsSchem ...

  3. .4-浅析webpack源码之convert-argv模块

    上一节看了一眼预编译的总体代码,这一节分析convert-argv模块. 这个模块主要是对命令参数的解析,也是yargs框架的核心用处. 生成默认配置文件名数组 module.exports = fu ...

  4. .9-浅析webpack源码之NodeEnvironmentPlugin模块总览

    介绍Compiler的构造比较无趣,不如先过后面的,在用到compiler的时候再做讲解. 这一节主要讲这行代码: // 不管这里 compiler = new Compiler(); compile ...

  5. .14-浅析webpack源码之Watchpack模块

    解决掉了最头疼的DirectoryWatcher内部实现,这一节可以结束NodeWatchFileSystem模块. 关于watch的应用场景,仔细思考了下,这不就是热重载的核心嘛. 首先是监视文件, ...

  6. .13-浅析webpack源码之WatcherManager模块

    从模块流可以看出,这个NodeWatchFileSystem模块非常深,这里暂时不会深入到chokidar模块,有点太偏离本系列文章了,从WatcherManager开始讲解. 流程如图: 源码非常简 ...

  7. .12-浅析webpack源码之NodeWatchFileSystem模块总览

    剩下一个watch模块,这个模块比较深,先大概过一下整体涉及内容再分部讲解. 流程图如下: NodeWatchFileSystem const Watchpack = require("wa ...

  8. .11-浅析webpack源码之Storage模块

    至此已完成NodeJsInputFileSysten模块的讲解,下一步就是实际实用的模块: compiler.inputFileSystem = new CachedInputFileSystem(n ...

  9. .10-浅析webpack源码之graceful-fs模块

    在cachedInput.output.watch三大文件系统中,output非常简单,没有必要讲,其余两个模块依赖于input模块,而input主要是引用了graceful-fs的部分API,所以这 ...

随机推荐

  1. Samba简介与配置(匿名&本地用户验证)

    Samba简介 Samba是在Linux和UNIX系统上实现SMB协议的一个免费软件,由服务器及客户端程序构成. 在此之前我们已经了解了NFS,NFS与samba一样,也是在网络中实现文件共享的一种实 ...

  2. [MACHINE LEARNING] Can we predict voting outcomes?

    1. CART Tree library(rpart)library(rpart.plot)CTree = rpart(Party ~ . -USER_ID, data = train, method ...

  3. SSM_CRUD新手练习(8)搭建BootStrap分页页面

      经过Spring单元测试模拟请求,我们能够成功的取出数据,接下来,我们就开始写用来显示查询数据的分页页面吧. 我们使用Bootstrap来帮助我们快速开发漂亮的页面,具体怎么用可以查看Bootst ...

  4. uniGUI中Cookies使用中文汉字的方法(使用EncodeString函数编码)

    uniGUI中Cookies使用中文汉字的方法(使用EncodeString函数编码)   在目前的UniGUI(ver:0.88)中使用UniApplication.Cookies.SetCooki ...

  5. nlog 的手动配置

    使用nlog的时候,有时候需要手动配置.比如数据库链接和密码不配在文件里,或者统计配置在一个位置之类的. var config = new NLog.Config.LoggingConfigurati ...

  6. FFmpeg原始帧处理-滤镜API用法详解

    本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10429145.html 在FFmpeg中,滤镜(filter)处理的是未压缩的原始音视频 ...

  7. 788. Rotated Digits

    X is a good number if after rotating each digit individually by 180 degrees, we get a valid number t ...

  8. python 使用进程池Pool进行并发编程

    进程池Pool 当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态成生多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到mu ...

  9. 十进制转化为二进制Java实现

    提取2的幂 这个方法用代码实现貌似有点麻烦,需要探测大小,我只实现了整数十进制到二进制的转化 /* * 提取2的幂 */ public static String TenToBin1(int ten) ...

  10. Eclipse 中 Debug 时鼠标悬停无法查看变量值

    问题描述:Eclipse在Debug模式下,当鼠标移动到某个变量上面时不自动显示该变量对应的值. 解决方法:在Eclipse中点击 Window->Preferences->Java-&g ...