打开webpeck-cli下的convert-argv.js文件

// 定义options为空数组
 const options = [];
 // webpack -d 检查 -d指令
  if (argv.d) { 
      //...
  }
  // webpack -p
  if (argv.p) {
      //...
  }
  if (argv.output) {
      //...
  }
   //...
   /*如果有 --config   --config webpack.config.js   config就是webpack.config.js
    可以这样理解 
    "dev": "webpack-dev-server --inline --progress --hot --config webpack.config.js",当我们npm run dev的时候执行这段
    package.json的内容 此时有config读取webpack.config.js的内容 当我们npm run build时 执行 "webpack" 此时没有config走else分支*/
    if (argv.config) {
        // ... 获取文件
    }else{
        /*读取默认配置 ts co 等后缀类
         defaultConfigFiles是 数组[{ path:
         '/Users/orion/Desktop/react-beauty-highcharts/webpack.config.js',
         ext: '.js'
         },{path:'/Users/orion/Desktop/react-beauty-highcharts/webpack.config.ts', ext: '.ts'},{},...] */
        for (i = 0; i < defaultConfigFiles.length; i++) {
            const webpackConfig = defaultConfigFiles[i].path;
            // 读取文件,如果有的话push推进去
            if (fs.existsSync(webpackConfig)) {
                configFiles.push({
                    path: webpackConfig,
                    ext: defaultConfigFiles[i].ext
                });
                //     最终结果configFiles is the Array [ { path:'/Users/orion/Desktop/react-beauty-highcharts/webpack.config.js',
                //  ext: '.js' } ]
                break;
            }
        
        }
    }

process.cwd() 是node.js里读取文件路径的一个API

//configFiles长度大于0时
if (configFiles.length > 0) {
    // ...
    const requireConfig = function requireConfig(configPath) {
        // 这是局部options不要和全局的options数组混淆
        let options = (function WEBPACK_OPTIONS() {
            if (argv.configRegister && argv.configRegister.length) {
                module.paths.unshift(
                    path.resolve(process.cwd(), "node_modules"),
                    process.cwd()
                );
                argv.configRegister.forEach(dep => {
                    require(dep);
                });
                return require(configPath);
            } else {
                // 读取路径下的文件内容返回
                return require(configPath);
            }
        })();
        // 预处理options,options若是数组的话,处理成对象之类的
        options = prepareOptions(options, argv);
        return options;
    };
    configFiles.forEach(function(file) {
            /// interpret.extensions[.js]为null
            // 这里直接跳出
            registerCompiler(interpret.extensions[file.ext]);
            // options这里是全局options空数组 
            options.push(requireConfig(file.path));
        });
        // configFileLoaded 加载完毕
        configFileLoaded = true;
     }
     // 如果没有加载完毕,调用函数传递空数组
    if (!configFileLoaded) {
        return processConfiguredOptions({});
    } else if (options.length === 1) {
       // 如果只有一个,把仅有的传进去 
        return processConfiguredOptions(options[0]);
    } else {
        // 传options
        return processConfiguredOptions(options);
    }

注意了,这里有一个return 也就是这个convert-argv模块的最终返回结果,函数到这里就结束了。接下来我看看一下processConfiguredOptions函数

我们先按照npm run build分支走options.length为1,读取options[0]是webpack.config.js里的module.exports ={} 对象,饶了这大的一个圈子,那么接下来一起来看一看对你的输入配置做了怎么样的处理吧😁

function processConfiguredOptions(options) {
        // 非法输出类型检测
        const webpackConfigurationValidationErrors = validateSchema(
            webpackConfigurationSchema,
            options
        );
        if (webpackConfigurationValidationErrors.length) {
            const error = new WebpackOptionsValidationError(
                webpackConfigurationValidationErrors
            );
            // 报错处理,具体什么是非法,不影响主流程先过😁
            console.error(
                error.message,
                `\nReceived: ${typeof options} : ${JSON.stringify(options, null, 2)}`
            );
            process.exit(-1); // eslint-disable-line
        }
        
        // 如果options是Promise😱,以后待查,按照我们的npm run build 流程走不是Promise
        if (typeof options.then === "function") { 
            return options.then(processConfiguredOptions);
        }
        
        /* process ES6 default 检测,虽说咱们的webpack.congfig.js 的内容是用的 ES6 module.exports 写的,但是options是对象已经被读出来了,所以不走这个分支,
        可是为什么要再写一遍呢?看来还是有这种情况的,继续往下读*/
        if (typeof options === "object" && typeof options.default === "object") {
            return processConfiguredOptions(options.default);
        }
        
        // filter multi-config by name,对于多配置的处理,我们先不走这个分支
        if (Array.isArray(options) && argv["config-name"]) {
          //...
        }
        if (Array.isArray(options)) {
            options.forEach(processOptions);
        } else {
            /* 看了这个多判断终于有没有找到家的感觉,回家的路程好艰辛,接下来我们看看processOptions函数对options做了什么
            それは何をしましたか?🤔 */
            processOptions(options);
        }
        // ... 
         // 这个return 正好对应上个return,是最终的模块返回值
        return options;
function processOptions(options) {
        // 基本处理函数
        function ifArg(name, fn, init, finalize) {
            //... 
        }
        function ifArgPair(name, fn, init, finalize){
            //... 
        }
        function ifBooleanArg(name, fn) {
            //... 
        }
}

我们看到processOptions先定义了几个函数
ifArg,ifArgPair,ifBooleanArg,loadPlugin 根据名字可以翻译
如果是参数,如果是键值对形式参数 如果是布尔参数,如果是插件
提起插件,是不是就想起了我们如下写的 plugins,对这个配置的处理,读源码联想大法和通过名称翻译的方法还是很重要的,自己写的时候,尽量起好懂的名称,不要用中文拼音😑

{
plugins: [
    new HtmlWebpackPlugin({ // 实例化生成html插件
      title: 'title',
      template: './src/index.html', 
      filename: 'index.html', 
      inlineSource:  '.(js|css))$',  
      minify: {
          removeComments: true,
          collapseWhitespace: true
      },
      chunks: ["index"]
   }),        
    new HtmlWebpackPlugin()
  ],
  }

接下来是真正的函数调用

ifArg("mode", function(value) {
    options.mode = value;
});

function ifArg(name, fn, init, finalize) {
    const isArray = Array.isArray(argv[name]);
    const isSet = typeof argv[name] !== "undefined" && argv[name] !== null;
    // isArray为false 直接返回
    if (!isArray && !isSet) return;
    

    init && init();
    if (isArray) argv[name].forEach(fn);
    else if (isSet) fn(argv[name], -1);
    finalize && finalize();
}
  • 说一下argv目前argv是这么个对象
  • webpakck -p 有- p的时候有p=true
  • 有 -d的时候有d=true
  • '$0'是webpack的目前地址
  • 其他的自己悟,哈哈哈哈哈🤣 听说90后喜欢用这个表情
{ 
    _: [],
    cache: null,
    bail: null,
    profile: null,
    color: [Function: getSupportLevel],
    colors: [Function: getSupportLevel],
    d: true,
    p: true,
    'info-verbosity': 'info',
    infoVerbosity: 'info',
    '$0':
    '/Users/orion/Desktop/react-beauty-highcharts/node_modules/.bin/webpack',
    debug: true,
    'output-pathinfo': true,
    devtool: 'eval-cheap-module-source-map',
    mode: 'development',
    'optimize-minimize': true,
    define: [ 'process.env.NODE_ENV="production"' ] 
}

我们接着读这个函数

function ifArg(name, fn, init, finalize) {
    //如果argv[name]是数组 
    const isArray = Array.isArray(argv[name]);
    //如果argv[name]存在
    const isSet = typeof argv[name] !== "undefined" && argv[name] !== null;
    // isArray和isSet同时为false的时候返回
    if (!isArray && !isSet) return;
    // 按照目前的流程走,能过来的name有define,output-pathinfo,debug,devtool,optimize-minimize
    //如果init函数存在就执行init函数,目前流程只有define有init,执行函数defineObject = {};
    init && init();
    //如果是数组,执行传入的fn
    if (isArray) argv[name].forEach(fn);
    else if (isSet) fn(argv[name], -1);
    finalize && finalize();
}
这里只有一个是数组define,我们追踪define
   ifArgPair(
            "define",
            function(name, value) {
                if (name === null) {
                    name = value;
                    value = true;
                }
                defineObject[name] = value;
            },
            function() {
                defineObject = {};
            },
            function() {
                const DefinePlugin = require("webpack").DefinePlugin;
                addPlugin(options, new DefinePlugin(defineObject));
            }
        );
    }
    
        function ifArgPair(name, fn, init, finalize) {
            ifArg(
                name,
                function(content, idx) {
                    const i = content.indexOf("=");
                    if (i < 0) {
                        return fn(null, content, idx);
                    } else {
                        // 根据
                        return fn(content.substr(0, i), content.substr(i + 1), idx);
                    }
                },
                init,
                finalize
            );
        }
      // 接着,ifArgPair会调用ifArg对fn进行了处理
      从fn到
      function(content, idx) {
                    const i = content.indexOf("=");
                    if (i < 0) {
                        return fn(null, content, idx);
                    } else {
                        // 根据
                        return fn(content.substr(0, i), content.substr(i + 1), idx);
                    }
                }
     
        // 目前define是数组,define =[ 'process.env.NODE_ENV="production"' ]
         if (isArray) argv[name].forEach(fn);
         // 执行fn 的参数把 等号分割,执行
         function(name, value) {
              // name = process.env.NODE_ENV
              // value = production
            
                if (name === null) {
                    name = value;
                    value = true;
                }
                defineObject[name] = value;
                
            }
        // 函数执行结果就是
         defineObject.process.env.NODE_ENV =  production
    
         // 最后返回的是options,此后还有执行第三个参数出入了defineObject最终影响了options配置

            

我们接着往下读

//如果不为数组,而且存在执行fn
else if (isSet) fn(argv[name], -1);

我们追踪output-pathinfo

    ifBooleanArg("output-pathinfo", function() {
        ensureObject(options, "output");
        options.output.pathinfo = true;
    });
    
    function ifBooleanArg(name, fn) {
            ifArg(name, function(bool) {
                if (bool) {
                   // 如果配置是true那么执行函数
                    fn();
                }
            });
        }
    // 函数执行结果就是
    options.output.pathinfo = true;

再往下读

finalize && finalize();
对于defin字段春入了finalize函数,即第三个函数
function() {
    // 从webpack引入DefinePlugin对象
    const DefinePlugin = require("webpack").DefinePlugin;
    // 调用addPlugin
    addPlugin(options, new DefinePlugin(defineObject));
}

function addPlugin(options, plugin) {
        ensureArray(options, "plugins");
        options.plugins.unshift(plugin);
}
// 最终结果对options做了处理

其他同理
😵这个模块终于写完了

我们总结一下

这个模块做的就是从webpack配置文件读取配置赋值给options,然后根据配置,对options做了附加的对象处理比如options.output.pathinfo = true;然后返回options,国外小哥哥好能绕
我们在bin/cli文件里打印一下最后的config

{ entry: './src/index.js',
  output:
   { filename: 'index.js',
     path: '/Users/orion/Desktop/react-beauty-highcharts/dist',
     libraryTarget: 'commonjs2',
     pathinfo: true },
  module: { rules: [ [Object], [Object] ] },
  devServer: { openPage: './src/index.html', open: true, hot: true },
  externals: [ [Function] ],
  mode: 'development',
  plugins:
   [ LoaderOptionsPlugin { options: [Object] },
     LoaderOptionsPlugin { options: [Object] },
     DefinePlugin { definitions: [Object] } ],
  devtool: 'eval-cheap-module-source-map',
  context: '/Users/orion/Desktop/react-beauty-highcharts' }

鼓掌👏给自己一朵小红花🌹


织雪纱奈
86 声望17 粉丝

一个想成为演员的前端er