webpack 执行文件

/bin/webpack 这个文件相对简单,其实就是判断一下webpack-cli 是否已经安装,没有安装就调用命令安装,要是已经安装则使用webpack-cli

1. 声明 webpack-cli 信息对象

const cli = {
    name: "webpack-cli",
    package: "webpack-cli",
    binName: "webpack-cli",
    installed: isInstalled("webpack-cli"),
    url: "https://github.com/webpack/webpack-cli"
};

2. 判断 webpack-cli 是否有安装, 只要判断目录下node_module 是有webpack-cli 这个文件夹即可

const isInstalled = packageName => {
    if (process.versions.pnp) {
        return true;
    }

    const path = require("path");
    const fs = require("graceful-fs");

    let dir = __dirname;

    do {
        try {
            if (fs.statSync(path.join(dir, "node_modules", packageName)).isDirectory()) {
                return true;
            }
        } catch (_error) {
            // Nothing
        }
    } while (dir !== (dir = path.dirname(dir)));

    return false;
};

3. 判断 webpack-cli 是否有安装,如果有则调用runCli, 没有则 执行runCommand

if (!cli.installed) {
    const path = require("path");
    const fs = require("graceful-fs");
    const readLine = require("readline");

    const notify =
        "CLI for webpack must be installed.\n" + `  ${cli.name} (${cli.url})\n`;

    console.error(notify);

    /** 判断是使用yarn 还是node 还是其他的包管理工具 */
    let packageManager;

    if (fs.existsSync(path.resolve(process.cwd(), "yarn.lock"))) {
        packageManager = "yarn";
    } else if (fs.existsSync(path.resolve(process.cwd(), "pnpm-lock.yaml"))) {
        packageManager = "pnpm";
    } else {
        packageManager = "npm";
    }

    const installOptions = [packageManager === "yarn" ? "add" : "install", "-D"];

    console.error(
        `We will use "${packageManager}" to install the CLI via "${packageManager} ${installOptions.join(
            " "
        )} ${cli.package}".`
    );

    const question = `Do you want to install 'webpack-cli' (yes/no): `;

    const questionInterface = readLine.createInterface({
        input: process.stdin,
        output: process.stderr
    });

    process.exitCode = 1;
    questionInterface.question(question, answer => {
        questionInterface.close();
        /** 命令行输入y 时,安装`webpack-cli`, 否则提示需要安装webpack-cli */
        const normalizedAnswer = answer.toLowerCase().startsWith("y");

        if (!normalizedAnswer) {
            console.error(
                "You need to install 'webpack-cli' to use webpack via CLI.\n" +
                    "You can also install the CLI manually."
            );

            return;
        }
        process.exitCode = 0;

        runCommand(packageManager, installOptions.concat(cli.package))
            .then(() => {
                runCli(cli);
            })
            .catch(error => {
                console.error(error);
                process.exitCode = 1;
            });
    });
} else {
    runCli(cli);
}

4. runCammand 执行命令

const runCommand = (command, args) => {
    const cp = require("child_process");
    return new Promise((resolve, reject) => {
        const executedCommand = cp.spawn(command, args, {
            stdio: "inherit",
            shell: true
        });

        executedCommand.on("error", error => {
            reject(error);
        });

        executedCommand.on("exit", code => {
            if (code === 0) {
                resolve();
            } else {
                reject();
            }
        });
    });
};

5. 最后 runCli, 执行webpack-cli

const runCli = cli => {
    const path = require("path");
    /** webpack-cli, 这里require.resolve 是获取webpack-cli.package.json 的目录路径 */
    const pkgPath = require.resolve(`${cli.package}/package.json`);
    const pkg = require(pkgPath);
    require(path.resolve(path.dirname(pkgPath), pkg.bin[cli.binName]));
};

webpack-cli

  1. 上面所说运行webpack-cli 中的package.json 下的 bin, 即 ./bin/cli.js
  2. ./bin/cli.js 执行了 runCli()../lib/bootstrap 中加载啊
  3. 然后执行了cli = new WebpackCli(), 然后调用了cli.run()
  4. new WebpackCli() 的构造函数中使用了 commander
  5. cli.run 调用了 this.program.action 回调 调用 await loadCommandByName(commandToRun, true);,然后在调用了 makeCommand, makeCommand 调用了 loadWebpack, 并且 getBuiltInOptions 创建了webpackoption 也就是 webpack.config.js, 最后在makeCommand 调用了command.action 触发回调,调用了await this.runWebpack(options, isWatchCommandUsed);
    总结一下流程 this.program.action -> loadCommandByName -> makeCommad( loadWebpack, runWebpack )
  6. runWebpack 执行了 createCompiler 然后返回 this.webpack(option)
const webpack = require('webpack');
const config = require('./webpack.config');
compiler = webpack(config)
compiler.run((err) => {
    console.log(err)
})

webpack 源码

主入口

// webpack.js
// webpack 函数简易结构
cosnt webpack = (option, callback) => {
    /** 先忽略掉watch */
    const create = () => {
        let compiler = createCompiler(option);
        return { compiler };
    }
    /** 判断有没有回调,最终都是返回compiler */
    if (callback) {
        const { compiler } = create()
        compiler.run(() => {
            callback()
        })
        return compiler
    } else {
        const { compiler } = create();
        return compiler
    }

    return compiler
}

createCompiler

// lib/webpack.js
const createCompiler = rawOptions => {
    /** 序列化一下option */
    const options = getNormalizedWebpackOptions(rawOptions);
    // 设置一下默认值比如context 设为当前运行目录
    applyWebpackOptionsBaseDefaults(options);
    /** compiler 最主要的函数在于run 函数 */
    const compiler = new Compiler(options.context);
    compiler.options = options;

    // 加载plugins , 先加载我们配置文件下的plugin
    if (Array.isArray(options.plugins)) {
        for (const plugin of options.plugins) {
            if (typeof plugin === "function") {
                plugin.call(compiler, compiler);
            } else {
                plugin.apply(compiler);
            }
        }
    }
    // 给option默认值
    applyWebpackOptionsDefaults(options);
    /** 触发环境设置hooks */
    compiler.hooks.environment.call();
    compiler.hooks.afterEnvironment.call();
    // 给config 的 key/value 转换成插件 plugin
    new WebpackOptionsApply().process(options, compiler);
    compiler.hooks.initialize.call();
    return compiler;
};

流程图