webpack4 源码分析(一) 入口及webpack.js
1.命令行入口
一般来说,我们通过 npm run webpack --config xx.js ...
命令来启动webpack。
当我们在命令行运行以上命令后,npm会让命令行工具进入node_modules\.bin
目录查找是否存在 webpack.sh
或者 webpack.cmd
文件,如果存在,就执行,不存在,就抛出错误。
2. webpack.cmd
我们来看下 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" %*
)
总体逻辑就是先判断当前目录下是否存在node执行文件,无论存不存在,最后都是使用node去执行 node_modules\webpack\bin\webpack.js
。
linux下的启动脚本逻辑也基本上一致。
从这里我们看到,实际入口文件就是 node_modules\webpack\bin\webpack.js
。
3. webpack.js
process.exitCode = 0; //1
const runCommand = (command, args) =>{...};//2
const isInstalled = packageName =>{...};//3
const CLIs =[...];//4
const installedClis = CLIs.filter(cli => cli.installed); //5
if (installedClis.length === 0){...}else if (installedClis.length === 1){...}else{...}//6
这个文件里代码可以分为6步,分别说明如下:
- 1:nodejs中表示执行成功,回调函数的err 将为null;
- 2:一个用来运行命令行命令的函数;
- 3:一个用来判断某个包是否安装的函数,利用了
require.resolve()
函数来检查包的路径是否存在 - 4:一个用来存储webpack的cli工具信息的数组,实际上只包含两个:
webpack-cli
与webpack-conmand
; - 5:一个用来存储已安装的webpack的cli工具信息的数组;
- 6.:根据已安装的webpack命令行工具数量做不同的逻辑处理;
我们按照实际执行顺序看,首先要执行第6步的分支逻辑判断,首先要得到第5步的数组。
第5步则是利用了数组的filter()
方法,对第4步中定义的数组中的每一个元素进行了处理。我们先看下第4步中定义的数组内容:
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
两个cli工具包的详细信息。
这两个对象都有一个名为installed
的属性,使用了第3步所定义的方法检查了该依赖包是否已安装,如果检查结果为已安装,则会被filter()
方法放到新生成的installedClis
数组中。
现在,我们有了一个installedClis
数组,那么如果以上两个cli工具包都没有安装,它的长度就为 0 ,就会进入第6步的第一个分支处理,基本就是提示用户安装推荐的工具包(是否推荐以上面两个对象的recommended
属性决定),并给出安装命令。如下:
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:";
//遍历CLIS数组,如果该cli工具是被推荐的,则输出相关信息
for (const item of CLIs) {
if (item.recommended) {
notify += `\n - ${item.name} (${item.url})\n ${item.description}`;
}
}
console.error(notify);
//判断包管理工具,yarn or npm?
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(
" "
)}".`
);
//询问是否安装
let question = `Do you want to install 'webpack-cli' (yes/no): `;
const questionInterface = readLine.createInterface({
input: process.stdin,
output: process.stderr
});
// 根据用户回答结果,如果是yes,则自动安装,如果为no,则提示错误信息,并退出执行;如果安装出错,输出错误信息并退出执
//行;如果安装成功,则引入webpack-cli 包并执行它
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;
});
});
}
如果安装了其中一个cli工具,则进入第二个分支,引入该工具并执行,如下:
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]
));
}
如果是其他情况,则进入第三个分支,输出警告信息(只允许安装一种cli工具)并退出执行。如下:
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;
}