webpack笔记(7)-原理分析一

545 阅读3分钟

webpack启动过程分析

两种启动方式
(1)通过npm scripts运行:npm run prod
(2)通过webpack命令直接执行:webpack entry.js

在命令行运行上述命令后,npm会让命令行工具进入到node_modules/.bin目录下,查找是否存在webpack.sh(linux)或者webpack.cmd(windows)。 image.png 那么这个文件是webpack还是webpack-cli来做的呢?可以看下这两个包下面的package.json文件,其中的bin的指向:
webpack:

"bin": "./bin/webpack.js",

webapck-cli

"bin": {
    "webpack-cli": "./bin/cli.js"
},

那么,其实真实的位置是node_modules\webpack\bin\webpack.js,也可以对比下node_modules/.bin目录的webpack文件,两者内容是一样的。

image.png

最后,webpack最终找到 webpack-cli (webpack-command) 这个npm包,并且执行 CLI。

webpack-cli

webpack-cli做的事情:

  • 引入yargs,对命令行进行定制
  • 分析命令行参数,对参数进行转换,组成编译配置项
  • 引入webpack,根据配置项进行编译和构建

NON_COMPILATION_ARGS

在分析yargs之前,先明确一个概念:并不是所有的webpack命令都会实例化一个webpack,比如webpack init,webpack-cli的cli文件中,就定义了NON_COMPILATION_ARGS用来筛选出不需要编译的命令。

const { NON_COMPILATION_ARGS } = require("./utils/constants");
const NON_COMPILATION_CMD = process.argv.find(arg => {
    if (arg === "serve") {
        global.process.argv = global.process.argv.filter(a => a !== "serve");
        process.argv = global.process.argv;
    }
    return NON_COMPILATION_ARGS.find(a => a === arg);
});

if (NON_COMPILATION_CMD) {
    return require("./utils/prompt-command")(NON_COMPILATION_CMD, ...process.argv);
}
// /utils/constants文件中
const NON_COMPILATION_ARGS = [
    "init",  // 创建一份 webpack 配置文件
    "migrate",  // 进行 webpack 版本迁移
    "serve",  // 运行 webpack-serve
    "generate-loader",  // 生成 webpack loader 代码
    "generate-plugin",  // 生成 webpack plugin 代码
    "info"  // 返回与本地环境相关的一些信息
];

命令行工具包yargs

提供命令和分组参数 当我们输入help命令,可以看到所有支持的命令和其分组 image.png

image.png

// cli.js
const yargs = require("yargs").usage(`webpack-cli ${require("../package.json").version}

Usage: webpack-cli [options]
       webpack-cli [options] --entry <entry> --output <output>
       webpack-cli [options] <entries...> --output <output>
       webpack-cli <command> [options]

For more information, see https://webpack.js.org/api/cli/.`);

// 将yargs对象传递给config-yargs文件
require("./config/config-yargs")(yargs);
// config.yargs.js
const { GROUPS } = require("../utils/constants");

const {
    CONFIG_GROUP,  // 配置相关参数(文件名称、运行环境等)
    BASIC_GROUP,  // 基础参数(entry设置、debug模式设置、watch监听设置、devtool设置)
    MODULE_GROUP,  // 模块参数,给 loader 设置扩展
    OUTPUT_GROUP,  // 输出参数(输出路径、输出文件名称)
    ADVANCED_GROUP,  // 高级用法(记录设置、缓存设置、监听频率、bail等)
    RESOLVE_GROUP,  // 解析参数(alias 和 解析的文件后缀设置)
    OPTIMIZE_GROUP,  // 优化参数
    DISPLAY_GROUP  // 展示相关参数
} = GROUPS;
// 还有一个options,即最后面的选项,是通用参数(帮助命令、版本信息等)

module.exports = function(yargs) {
    yargs
        .help("help")
        .alias("help", "h")
        .version()
        .alias("version", "v")
        .options({
            // options就是我们输入的各种命令
            config: {
                type: "string",
                describe: "Path to the config file",
                // group就是这个配置项在哪个分组里面
                group: CONFIG_GROUP,
                defaultDescription: "webpack.config.js or webpackfile.js",
                requiresArg: true
            },
            env: {
                describe: "Environment passed to the config, when it is a function",
                group: CONFIG_GROUP
            },
            ...

所以webpack一共有9个命令组

分析命令行参数

// cli.js
// webpack会从两个地方修改参数,一个是webpack.config.js,一个是命令行
// options经过下面的一系列转换,会将命令行中的参数转换成webpack可识别的配置参数
let options;
try {
    // 所以convert-argv的作用就是根据输入的命令,给options增加参数和插件
    options = require("./utils/convert-argv")(argv);
} catch (err) {
...
}

// 判断某个值在一个对象上面是否存在
function ifArg(name, fn, init) {
    if (Array.isArray(argv[name])) {
        if (init) init();
        argv[name].forEach(fn);
    } else if (typeof argv[name] !== "undefined") {
        if (init) init();
        fn(argv[name], -1);
    }
}

// 将组装好的options传递进来
function processOptions(options) {

    // 定义一个输出参数
    let outputOptions = options.stats;

    // 判断该参数是否配置,然后进行对应的参数处理
    ifArg("display", function(preset) {
        outputOptions = statsPresetToOptions(preset);
    });

    outputOptions = Object.create(outputOptions);
    
    ...

    // 最终会实例化一个webpack对象
    const webpack = require("webpack");
    ...
    // 进行编译
    compiler.run((err, stats) => {
        if (compiler.close) {
            compiler.close(err2 => {
                compilerCallback(err || err2, stats);
            });
        } else {
            compilerCallback(err, stats);
        }
    });
...

总结:webpack-cli对配置文件和命令行参数进行转化后,最终生成配置选项参数options和ouputOptions,最终会根据配置参数实例化webpack对象,然后执行构建流程。

极客时间-玩转webpack