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就是optionsfunction 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.filenamefunction 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形式:
// 传进来的optionsfunction 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模块
原文地址:http://www.cnblogs.com/QH-Jimmy/p/8034891.html