webpack原理概述

247 阅读2分钟

当一个项目从npm run build或者npm run dev的过程中经历了什么?

1.寻找启动文件

在命令行输入npm run build或者npm run dev时,npm会让命令行工具进入node_modules.bin目录查找是否有wepack.sh或wepack.cmd文件,如果存在就执行,不存在就抛出错误。
最终的执行文件入口是:node_modules\webpack\bin\webpack.js

2.webpack.js文件

  • 定义runCommand文件,目的是让执行的实现通过启动文件来实现
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();
			}
		});
	});
};

其中,通过require("child_process")引入子进程,然后,通过子进程的spawn方式,启动子进程,command是传入的命令,比如npm;args是启动参数;最后一个是选项用于配置在父进程和子进程之间建立的管道,stdio:"inherit"指的是相当于 ['inherit', 'inherit', 'inherit']。

  • 定义runCli函数,目的是引入安装好的包
const runCli = cli => {
	const path = require("path");
	const pkgPath = require.resolve(`${cli.package}/package.json`);
	const pkg = require(pkgPath);
	require(path.resolve(path.dirname(pkgPath), pkg.bin[cli.binName]));
};

const cli = {
	name: "webpack-cli",
	package: "webpack-cli",
	binName: "webpack-cli",
	installed: isInstalled("webpack-cli"),
	url: "https://github.com/webpack/webpack-cli"
};
  • webpack-cli是否安装的判断和提示安装
// 是否安装了某个包的判断函数
const isInstalled = packageName => {
	try {
		require.resolve(packageName);

		return true;
	} catch (err) {
		return false;
	}
};
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);

	let packageManager;
        // 通过调用fs.existsSync做判断,判断采用哪个命令
	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): `;
        // 通过调用包readLine构建交互
	const questionInterface = readLine.createInterface({
		input: process.stdin,
		output: process.stderr
	});
	process.exitCode = 1;
	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."
			);
			return;
		}
		process.exitCode = 0;
                // 调用以上定义的方法,对webpack-cli进行安装
		runCommand(packageManager, installOptions.concat(cli.package))
			.then(() => {
				runCli(cli);
			})
			.catch(error => {
				console.error(error);
				process.exitCode = 1;
			});
	});
} else {
	runCli(cli);
}

以上首先通过cli.installed进行判断,如果未安装wepack-cli,则引入path、graceful-fs(fs的扩展版本)和readline(构建交互)模块。通过readline.createInterface({ input: process.stdin, output: process.stdout })构建和用户的交互,通过回调函数获得answer.toLowerCase().startsWith("y")返回的值判断用户是否输入的是y开头的,如果不是结束执行;如果是,则runCommand(packageManager, installOptions.concat(cli.package))执行当前的安装命令。

注:后续的笔记会持续更新补充