1、下载需要分析的项目
分别是核心、cli、本地开发服务器
2、看webpack-4.43.0
package.json
"main": "lib/webpack.js",
"web": "lib/webpack.web.js",
"bin": "./bin/webpack.js",
命令行调用,执行bin对应js文件;nodejs代码调用执行main对应文件。
3、./bin/webpack.js
#!/usr/bin/env node
// @ts-ignore
process.exitCode = 0;
/**
* @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();
}
});
});
};
process.exitCode,设置退出状态,0成功,1错误。
runCommand,使用child_process开启子进程,执行命令,返回promise。inherit:继承父进程相关的stdio。
/**
* @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;
}
};
require.resolve执行成功,说明包存在,出现错误,则捕获说明不存在。
/**
* @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."
}
];
两个包的信息,webpack-cli和webpack-command,下面会用到。
const installedClis = CLIs.filter(cli => cli.installed);
过滤这两包,获取安装过的包组成的数组。
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;
});
});
}
如果两个包,一个也没安装,就提示必须安装,这是推荐的包。
使用 fs.existsSync,在当前工作目录,判断是否存在yarn.lock文件。
存在,设置yarn add;否则npm install -D 。
提示询问是否同意安装webpack-cli? yes/no
使用readLine.createInterface,监控用户输入。
questionInterface.question监控到输入,就关闭监控输入,转小写判断输入是否y开头,不是,就提示你得安装,你可以人工手动安装。
并设置错误process.exitCode = 1;返回。
如果输入的开头是y,就提示安装webpack-cli,并且执行安装命令,注意runCommand参数,第二个参数是数组,表示命令行命令的参数,child_process子进程的.spawn函数,也支持这个数组形式参数。
前面runCommand返回promise,子进程执行退出时,如果code为0,表示成功执行命令,也就是成功安装,执行resolve。
然后就到了then,为了确保成功安装了webpack-cli,就require(packageName)下试下。 如果异常,也是catch,提示错误,设置process.exitCode = 1。 子进程如果失败,非0,会走reject();这时也会到catch逻辑。 promise的错误传递体现了。
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]
));
}
俩个如果有一个安装了,我们这时并不知道安装了哪儿个,不过无所谓,我们就取这个安装过的包的package.json文件路径,然后引用它。
path.dirname(pkgPath),找到文件路径的目录路径。
找到package.json内bin对象,进而根据安装过的cli的binName,找到对应bin对应的js路径。
然后引用它。
这里引用它,其实就是执行它,相当于执行命令行命令。
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。