- 运行
npx webpack
命令进行打包,会去当前项目的node_module/.bin
目录下查找webpack.js
文件进行执行。 - 在
webpack.js
文件中:首先会去判断是否有安装Webpack CLI
,如果没有安装的话,会在命令行中给出提示并引导安装;一直判断到安装了Webpack CLI
之后,就会去导入并执行webpack-cli/bin/cli.js
文件。 - 在
webpack-cli/bin/cli.js
文件及其导入的文件中:会创建 WebpackCLI 的实例对象,并执行其run()
方法进行编译打包。
在 WebpackCLI 类中,做的最重要的一步就是会把打包命令中的参数和配置文件中的配置进行合并,然后把合并后的配置信息作为第一个参数传递给引入的 webpack 函数,就会生成编译器 compiler,就可以利用这个编译器去编译了。
总结来说,执行
npx webpack
时,会先去执行Webpack CLI
,目的就是为了把打包命令中的参数和配置文件中的配置进行合并;然后再真正执行 Webpoack。
不同的版本可能会略有差异,此处是
webpack5.88.2
和webpack-cli5.1.4
。
mac 和 windows 中文件的位置不同,此处是 mac 中的文件路径。
运行 webpack 命令的详细启动流程:
-
运行
npx webpack
命令进行打包,会去当前项目的node_module/.bin
目录下查找本地安装的 webpack 进行执行。
// node_module/.bin/webpack.js #!/usr/bin/env node // 安装 webpack-cli 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(); } }); }); }; // 判断是否安装了 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) {} } while (dir !== (dir = path.dirname(dir))); for (const internalPath of require("module").globalPaths) { try { if (fs.statSync(path.join(internalPath, packageName)).isDirectory()) { return true; } } catch (_error) {} } return false; }; // 执行 webpack-cli const runCli = cli => { const path = require("path"); // 获取到 webpack-cli/package.json 文件的路径 const pkgPath = require.resolve(`${cli.package}/package.json`); const pkg = require(pkgPath); // 通过拼接 webpack-cli/package.json 文件路径和其中 bin[webpack-cli]的属性值,获取到 webpack-cli/bin/cli.js 文件的路径,对其进行导入并执行 if (pkg.type === "module" || /\.mjs/i.test(pkg.bin[cli.binName])) { import(path.resolve(path.dirname(pkgPath), pkg.bin[cli.binName])).catch( error => { console.error(error); process.exitCode = 1; } ); } else { require(path.resolve(path.dirname(pkgPath), pkg.bin[cli.binName])); } }; // 1. 定义 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 if (!cli.installed) { const path = require("path"); const fs = require("graceful-fs"); const readLine = require("readline"); // 2.1 报错提示 CLI for webpack must be installed const notify = "CLI for webpack must be installed.\n" + ` ${cli.name} (${cli.url})\n`; console.error(notify); // 2.2 检查当前使用的是哪个包管理工具,并且报错提示使用哪个包管理工具来安装 webpack-cli 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}".` ); // 2.3 提问,并且接收在命令行中的输入和输出 const question = `Do you want to install 'webpack-cli' (yes/no): `; const questionInterface = readLine.createInterface({ input: process.stdin, output: process.stderr }); // 2.4 接收到答案 process.exitCode = 1; questionInterface.question(question, answer => { questionInterface.close(); // 2.4.1 如果回答的不是 y,那么报错提示 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; } // 2.4.2 否则的话,安装 webpack-cli,并且在安装成功之后,运行 webpack-cli process.exitCode = 0; console.log( `Installing '${ cli.package }' (running '${packageManager} ${installOptions.join(" ")} ${ cli.package }')...` ); runCommand(packageManager, installOptions.concat(cli.package)) .then(() => { runCli(cli); }) .catch(error => { console.error(error); process.exitCode = 1; }); }); } else { // 3. 如果安装了 webpack-cli,运行 webpack-cli runCli(cli); }
-
在
node_module/.bin/webpack.js
中运行了runCli()
函数,会导入并执行webpack-cli/bin/cli.js
文件。
// webpack-cli/bin/cli.js #!/usr/bin/env node "use strict"; const importLocal = require("import-local"); const runCLI = require("../lib/bootstrap"); if (!process.env.WEBPACK_CLI_SKIP_IMPORT_LOCAL) { if (importLocal(__filename)) { return; } } process.title = "webpack"; // 1. 运行 runCLI() 函数 runCLI(process.argv);
// webpack-cli/lib/bootstrap.js "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const WebpackCLI = require("./webpack-cli"); const runCLI = async (args) => { // 1. 创建 WebpackCLI 的实例对象 const cli = new WebpackCLI(); try { // 2. 执行 WebpackCLI 实例对象的 run 方法 // webpack-cli/lib/webpack-cli.js 中创建并导出了 WebpackCLI 类,WebpackCLI 类的实例方法 run() 会执行引入的 webpack 函数 await cli.run(args); } catch (error) { cli.logger.error(error); process.exit(2); } }; module.exports = runCLI;
-
在
webpack-cli/lib/webpack-cli.js
中中创建并导出了 WebpackCLI 类。会把打包命令中的参数和配置文件中的配置进行合并;然后把合并后的配置信息作为第一个参数传递给引入的 webpack 函数;然后就会生成编译器 compiler。Webpack 本质上导出的就是一个函数。
不使用 Webpack CLI
,手动使用 Webpack 进行打包:
Webpack CLI
做的最重要的事就是把打包命令中的参数和配置文件中的配置进行合并。
如果打包命令中根本没有什么核心的东西,不需要 Webpack CLI
来对其进行合并配置的话,完全可以不使用 Webpack CLI
,开发者手动利用 Webpack 进行打包。例如 Vue CLI
、React CLI
都没有使用 Webpack CLI
。
-
新建
build.js
文件并编写代码,用来直接利用 Webpack 进行打包。// 引入 webpack。由于要使用 node 来执行这个文件,因此此处使用 CommonJS const webpack = require('webpack') // 引入 webpack 的配置文件 const config = require('./webpack.config') // 执行 webpack 函数并传入配置参数,生成 compiler 编译器 const compiler = webpack(config) // 执行 compiler 编译器的 run 方法来进行编译打包 compiler.run((err,stats) => { if (err) { console.log(err) } else { console.log(stats) } })
-
在命令行中运行
node build.js
来执行该文件,会发现,成功利用 Webpack 进行了编译打包。