jyzwf / blog

在Issues里记录技术得点滴
17 stars 3 forks source link

Webpack源码系列之处理options #57

Open jyzwf opened 6 years ago

jyzwf commented 6 years ago

众所周知,webpack4 中添加了各个选项的默认值,那么我们来看看它内部是怎么设置的

进入 lib/webpack.js即可找到我们的 webpack 函数。

首先 webpack 会先校验传入的options 是否合法,如果不合法就会直接给出报错。接着是定义一个 compiler 变量。如果传入的options是数组,意味着有多个配置对象,如下:

module.exports = [{
  output: {
    filename: './dist-amd.js',
    libraryTarget: 'amd'
  },
  entry: './app.js',
  mode: 'production',
}, {
  output: {
    filename: './dist-commonjs.js',
    libraryTarget: 'commonjs'
  },
  entry: './app.js',
  mode: 'production',
}]

具体部分代码如下:

if (Array.isArray(options)) {
        compiler = new MultiCompiler(options.map(options => webpack(options)));
    } else if (typeof options === "object") {
        // 为配置设置初始值
        options = new WebpackOptionsDefaulter().process(options);

        compiler = new Compiler(options.context);
        compiler.options = options;
        // 这里引入compiler.inputFileSystem
        new NodeEnvironmentPlugin().apply(compiler);
        if (options.plugins && Array.isArray(options.plugins)) {
            for (const plugin of options.plugins) {
                plugin.apply(compiler);
            }
        }
        // 执行environment,afterEnvironment两个hook
        compiler.hooks.environment.call();
        compiler.hooks.afterEnvironment.call();
        compiler.options = new WebpackOptionsApply().process(options, compiler);
    } else {
        throw new Error("Invalid argument: options");
    }

这里只先讨论单对象的情况: 遂来看下 WebpackOptionsDefaulter 类,它继承自 OptionsDefaulter ,并且在里面设置默认值的方法主要就是调用父类 set 方法,并且上面的 process 方法也同样是调用父类。下面是具体的 set 方法:

set(name, config, def) {
        if (def !== undefined) {
            this.defaults[name] = def;
            this.config[name] = config;
        } else {
            this.defaults[name] = config;
            delete this.config[name];
        }
    }

它主要是搜集各个选项的默认值和配置保存到 this.defaultsthis.config 字段中,详情如下: WebpackOptionsDefaulter

这里列举下config的枚举值:

config = [
    make,
    call,    // ['module','output','node','performance','optimization','optimization.runtimeChunk','resolve','resolveLoader']   共9个
    append,    // 在 WebpackOptionsDefaulter 构造函数中未找到有该配置,???
] 

下面是全部收集完但不包含结合用户传进来的options产生的默认值收到的 this.defaultthis.config:

// this.default
{
    "entry": "./src",
    "context": "当前项目的目录",
    "target": "web",
    "module.unknownContextRequest": ".",
    "module.unknownContextRegExp": false,
    "module.unknownContextRecursive": true,
    "module.unknownContextCritical": true,
    "module.exprContextRequest": ".",
    "module.exprContextRegExp": false,
    "module.exprContextRecursive": true,
    "module.exprContextCritical": true,
    "module.wrappedContextRegExp": {},
    "module.wrappedContextRecursive": true,
    "module.wrappedContextCritical": false,
    "module.strictExportPresence": false,
    "module.strictThisContextOnImports": false,
    "module.rules": [],
    "output.filename": "[name].js",
    "output.webassemblyModuleFilename": "[modulehash].module.wasm",
    "output.library": "",
    "output.libraryTarget": "var",
    "output.path": "/Users/bill/Documents/_webpack/learning/demo1/dist",
    "output.sourceMapFilename": "[file].map[query]",
    "output.hotUpdateChunkFilename": "[id].[hash].hot-update.js",
    "output.hotUpdateMainFilename": "[hash].hot-update.json",
    "output.crossOriginLoading": false,
    "output.jsonpScriptType": false,
    "output.chunkLoadTimeout": 120000,
    "output.hashFunction": "md4",
    "output.hashDigest": "hex",
    "output.hashDigestLength": 20,
    "output.devtoolLineToLine": false,
    "output.strictModuleExceptionHandling": false,
    "node.console": false,
    "node.process": true,
    "node.global": true,
    "node.Buffer": true,
    "node.setImmediate": true,
    "node.__filename": "mock",
    "node.__dirname": "mock",
    "performance.maxAssetSize": 250000,
    "performance.maxEntrypointSize": 250000,
    "optimization.removeAvailableModules": true,
    "optimization.removeEmptyChunks": true,
    "optimization.mergeDuplicateChunks": true,
    "optimization.providedExports": true,
    "optimization.splitChunks": {},
    "optimization.splitChunks.chunks": "async",
    "optimization.splitChunks.minChunks": 1,
    "optimization.splitChunks.automaticNameDelimiter": "~",
    "optimization.splitChunks.name": true,
    "optimization.splitChunks.cacheGroups": {},
    "optimization.splitChunks.cacheGroups.default": {
        "automaticNamePrefix": "",
        "reuseExistingChunk": true,
        "minChunks": 2,
        "priority": -20
    },
    "optimization.splitChunks.cacheGroups.vendors": {
        "automaticNamePrefix": "vendors",
        "test": {},
        "priority": -10
    },
    "optimization.mangleWasmImports": false,
    "optimization.hashedModuleIds": false,
    "resolve.unsafeCache": true,
    "resolve.modules": [
        "node_modules"
    ],
    "resolve.extensions": [
        ".wasm",
        ".mjs",
        ".js",
        ".json"
    ],
    "resolve.mainFiles": [
        "index"
    ],
    "resolveLoader.unsafeCache": true,
    "resolveLoader.mainFields": [
        "loader",
        "main"
    ],
    "resolveLoader.extensions": [
        ".js",
        ".json"
    ],
    "resolveLoader.mainFiles": [
        "index"
    ]
}

// this.config
{
    "devtool": "make",
    "cache": "make",
    "module": "call",
    "module.unsafeCache": "make",
    "module.defaultRules": "make",
    "output": "call",
    "output.chunkFilename": "make",
    "output.hotUpdateFunction": "make",
    "output.jsonpFunction": "make",
    "output.chunkCallbackName": "make",
    "output.globalObject": "make",
    "output.devtoolNamespace": "make",
    "output.pathinfo": "make",
    "node": "call",
    "performance": "call",
    "performance.hints": "make",
    "optimization": "call",
    "optimization.flagIncludedChunks": "make",
    "optimization.occurrenceOrder": "make",
    "optimization.sideEffects": "make",
    "optimization.usedExports": "make",
    "optimization.concatenateModules": "make",
    "optimization.splitChunks.hidePathInfo": "make",
    "optimization.splitChunks.minSize": "make",
    "optimization.splitChunks.maxAsyncRequests": "make",
    "optimization.splitChunks.maxInitialRequests": "make",
    "optimization.runtimeChunk": "call",
    "optimization.noEmitOnErrors": "make",
    "optimization.checkWasmTypes": "make",
    "optimization.namedModules": "make",
    "optimization.namedChunks": "make",
    "optimization.portableRecords": "make",
    "optimization.minimize": "make",
    "optimization.minimizer": "make",
    "optimization.nodeEnv": "make",
    "resolve": "call",
    "resolve.aliasFields": "make",
    "resolve.mainFields": "make",
    "resolve.cacheWithContext": "make",
    "resolveLoader": "call",
    "resolveLoader.cacheWithContext": "make"
}

实例化完 WebpackOptionsDefaulter 后,就是结合用户传进来的 options,调用 process 方法来 生成一个完整的 optionsprocess 中会根据之前各个 set 中所对应的 config 值来处理各个配置项。

// process
options = Object.assign({}, options);
for (let name in this.defaults) {
            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)
                    );
                    break;
                case "make":
                    if (getProperty(options, name) === undefined) {
                        setProperty(options, name, this.defaults[name].call(this, options));
                    }
                    break;
                case "append": {
                    let oldValue = getProperty(options, name);
                    if (!Array.isArray(oldValue)) {
                        oldValue = [];
                    }
                    oldValue.push(...this.defaults[name]);
                    setProperty(options, name, oldValue);
                    break;
                }
                default:
                    throw new Error(
                        "OptionsDefaulter cannot process " + this.config[name]
                    );
            }
        }
return options
  1. 当没有配置项的时 先检验用户传进来的options是否有该选项,如果没有,就直接将默认值赋予该选项

  2. 当配置项值为 call 时: 该配置项的set时值均是 function ,并且 webpack 本身也为该选项对象的某些字段提供了默认值。它先将存在于用户配置中的选项先进行设置,如果没有设置的,才会利用默认值,且该函数的第一个参数是该选项的用户配置值,第二个为传入的options

  3. 当配置项值为 make 时: 该配置项的set时值均是 function ,默认第一个参数是传入的options

  4. 当配置项值为 append 时: 暂未见该使用场景,见到时再看

至此,webpack 设置最终的options就完成,接下来就是 Compiler 阶段