webpack 打包启动过程分析

247 阅读4分钟

webpack 启动做了什么

1.在使用webpack 的时候我们,我们一般会在'npm scripts' 配置好一些执行指令,在我们
执行这些指令的时候webpack做了什么?我们可以看一下不是简写的运行指令
'.\node_modules\.bin\webpack'
2.指令输入后进入'node\_modules\.bin' 目录 查找是否存在 webpack.sh 或者 webpack.cmd 文件,
如果存在,就执行,不 存在,就抛出错误
3.我是windows 系统来一下看一下' webpack.cmd'里面写了什么
    @IF EXIST "%~dp0\node.exe" (
      "%~dp0\node.exe"  "%~dp0\..\webpack\bin\webpack.js" %*
    ) ELSE (
      @SETLOCAL
      @SET PATHEXT=%PATHEXT:;.JS;=;%
      node  "%~dp0\..\webpack\bin\webpack.js" %*
    )
4.发现他还会接着去执行'node_modules\webpack\bin\webpack.js',因此这里才是文件的实际入口

webpack.js 文件做了什么

1.整个文件打开后发现整个代码可以分为六大块
// node_modules/webpack/bin/webpack.js
// 1.正常执行返回
process.exitCode = 0;
// 2.运行某个命令
const runCommand = (command, args) => {...}
// 3.判断某个包是否安装
const isInstalled = packageName => {...}
// 4.webpack可用的CLI:webpacl-cli和webpack-command
const CLIs = {...}
// 5.判断是否两个CLI是否安装了
const installedClis = CLIs.filter(cli=>cli.installed);
// 6.根据安装数量进行处理
if (installedClis.length === 0) {...} else if 
 (installedClis.length === 1) {...} else {...}
process.exitCode 分析
process.exitCode 为 0,代表程序正常运行;非 0,则代表程序报错。
runCommand 运行某个命令
1.通过源码发现整个'runCommand' 方法有两个参数,
  1.1.'command' 要运行的进程在这个入口文件源码里指的是用'yarn' 还是'npm',当我们看到
  '安装处置'这里的逻辑时候就会发现里面有一段参数'const packageManager = isYarn ? "yarn" : "npm";'
  1.2.'args' 这个参数是数组,当我们看到 '安装处置'这里的逻辑时候会发现实际他参数是
  'const installOptions = [isYarn ? "add" : "install", "-D"];'
2.其实通过这两个参数我们能发现,这个就是帮我们拼接安装包指令的方法,具体他会安装什么包还要往下看
3.现在这段代码中'require("child_process")' 是整个安装过程核心,这个方法是做什么的?
   这是一个提供了衍生子进程模块,下面代码中用到的'spawn'方法第一个 参数'要运行的命令',
   第二个参数'字符串参数的列表' ,第三个参数'是一些配置项',具体的以后研究

nodejs 文档对child_process说明 spawn方法的说明

/**
 * @param {string} command process to run
 * @param {string[]} args commandline arguments
 * @returns {Promise<void>} promise
 */
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();
			}
		});
	});
};
isInstalled 判断某个包是否安装
1.这里需要知道的api'require.resolve',这个方法主要做了?
  1.1.使用内部的 require() 机制查询模块的位置,此操作只返回解析后的文件名,不会加载该模块。
  如果找不到模块,则会抛出 MODULE_NOT_FOUND 错误。
这里要注意的是如果你写的只是一个名称,他是从'node_modules' 开始找的
2.因此这个方法将会帮我们判断某个包是否出现在'node_modules',也就是是否安装喽

node对这个api的解释 别人文章里的解释

  • 这里我写了一个小例子做说明

/**
 * @param {string} packageName name of the package
 * @returns {boolean} is the package installed?
 */
const isInstalled = packageName => {
	try {
		require.resolve(packageName);

		return true;
	} catch (err) {
		return false;
	}
};
CLIs webpack可用的CLI
1.这里其实就是调用'isInstalled' 来查到底安没安装webpack 脚手架
/**
 * @typedef {Object} CliOption
 * @property {string} name display name
 * @property {string} package npm package name
 * @property {string} binName name of the executable file
 * @property {string} alias shortcut for choice
 * @property {boolean} installed currently installed?
 * @property {boolean} recommended is recommended
 * @property {string} url homepage
 * @property {string} description description
 */

/** @type {CliOption[]} */
const CLIs = [
	{
		name: "webpack-cli",
		package: "webpack-cli",
		binName: "webpack-cli",
		alias: "cli",
		installed: isInstalled("webpack-cli"),
		recommended: true,
		url: "https://github.com/webpack/webpack-cli",
		description: "The original webpack full-featured CLI."
	},
	{
		name: "webpack-command",
		package: "webpack-command",
		binName: "webpack-command",
		alias: "command",
		installed: isInstalled("webpack-command"),
		recommended: false,
		url: "https://github.com/webpack-contrib/webpack-command",
		description: "A lightweight, opinionated webpack CLI."
	}
];
installedClis判断是否两个CLI是否安装了
1.这个就很简单一个filter 过滤方法
const installedClis = CLIs.filter(cli => cli.installed);
 
根据安装数量进行处理
1.整个代码的最后执行,如果你'webpack-command''webpack-cli'任意一个都没安装,
那么就会让你选择安装一个
2.如果安装其中一个,就正常运行
3.如果两个都安装了,那就提示删除一个才能运行
if (installedClis.length === 0) {
	const path = require("path");
	const fs = require("fs");
	const readLine = require("readline");

	let notify =
		"One CLI for webpack must be installed. These are recommended choices, delivered as separate packages:";

	for (const item of CLIs) {
		if (item.recommended) {
			notify += `\n - ${item.name} (${item.url})\n   ${item.description}`;
		}
	}

	console.error(notify);

	const isYarn = fs.existsSync(path.resolve(process.cwd(), "yarn.lock"));

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

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

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

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

		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."
			);
			process.exitCode = 1;

			return;
		}

		const packageName = "webpack-cli";

		console.log(
			`Installing '${packageName}' (running '${packageManager} ${installOptions.join(
				" "
			)} ${packageName}')...`
		);

		runCommand(packageManager, installOptions.concat(packageName))
			.then(() => {
				require(packageName); //eslint-disable-line
			})
			.catch(error => {
				console.error(error);
				process.exitCode = 1;
			});
	});
} else if (installedClis.length === 1) {
	const path = require("path");
	const pkgPath = require.resolve(`${installedClis[0].package}/package.json`);
	// eslint-disable-next-line node/no-missing-require
	const pkg = require(pkgPath);
	// eslint-disable-next-line node/no-missing-require
	require(path.resolve(
		path.dirname(pkgPath),
		pkg.bin[installedClis[0].binName]
	));
} else {
	console.warn(
		`You have installed ${installedClis
			.map(item => item.name)
			.join(
				" and "
			)} together. To work with the "webpack" command you need only one CLI package, please remove one of them or use them directly via their binary.`
	);

	// @ts-ignore
	process.exitCode = 1;
}

总结 图片来自下面的参考文章
1.webpack 最终找到 webpack-cli (webpack-command) 这个 npm 包,并且
执行 CLI

参考的文章

Webpack 进阶之源码分析(一)