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. Scrum冲刺阶段1

    各个成员在 Alpha 阶段认领的任务 人员 任务 何承华 美化设计 部分后端设计 陈宇 后端设计 丁培辉 美化设计 部分后端设计 温志铭 前端设计 杨宇潇 服务器搭建 张主强 前端设计 明日各个成员 ...

  2. 20175316 盛茂淞 实验一 Java开发环境的熟悉

    20175316 盛茂淞 实验一 Java开发环境的熟悉 实验目的 使用JDK编译.运行简单的Java程序 实验要求 1.建立"自己学号exp1"的目录 2.在"自己学号 ...

  3. 2019.02.12 bzoj5294: [Bjoi2018]二进制(线段树)

    传送门 题意简述: 给出一个长度为nnn的二进制串. 你需要支持如下操作: 修改每个位置:1变0,0变1 询问对于一个区间的子二进制串有多少满足重排之后转回十进制值为333的倍数(允许前导000). ...

  4. async/await让你的代码更加优雅

    一. 回调地狱:回调函数里面嵌套着回调函数嵌套着回调函数”,这就是被传说中的“回调地狱callbackHell () { const api = new Api() let user, friends ...

  5. springmvc 对日期的转换与处理

    一,背景 近期项目上需求还没有确定,难道清闲,对项目中不合理的地方进行一些升级改造.鉴于项目使用的技术框架比较老旧(spring 3.0+) ,一直没有对此做大的升级改造.由于之前项目入参,出参都是使 ...

  6. mysql windows 5.7 安装版下载地址

    https://dev.mysql.com/downloads/windows/installer/5.7.html

  7. 如何将uniurlframe中html调用delphi的函数

    uniGUI总群中台中cmj朋友为我们总结了如下内容,对于利用delphi+uniGUI开发应用,可以说是精品,必须掌握. 一句话,如何在html与delphi间交互代码,是最好的答案. [Clien ...

  8. MVC+EF CODE FIRST的使用

    1创建标准MVC项目 2通过NuGet安装EF 3在Models文件夹中编写实体类 4创建EFDB上下文类 5在webconfig中创建连接字符串,其中name=EFDB上下文类名 6通过管理控制台执 ...

  9. Java正则表达式API详解

    1. Pattern类 public class PatternExample { /** * public static String quote(String s) * 返回指定字符串的字面值模式 ...

  10. AndroidStudio环境安装与配置

    前言 大家好,给大家带来AndroidStudio环境安装与配置的概述,希望你们喜欢 AndroidStudio IDE下载 我们选择用Android Studio开发Android的App,Andr ...