>[success] # 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',因此这里才是文件的实际入口 ~~~ >[info] ## webpack.js 文件做了什么 ~~~ 1.整个文件打开后发现整个代码可以分为六大块 ~~~ ~~~ js // 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 {...} ~~~ >[danger] ##### 第一块process.exitCode 分析 ~~~ 1.process.exitCode 为 0,代表程序正常运行;非 0,则代表程序报错。 注:Node API 文档中明确写到,process.exit() 方法以退出状态 code 指示 Node.js 同步地终止进程。 如果省略 code,则使用成功代码 0 或 process.exitCode 的值 (如果已设置)退出。 ~~~ >[danger] ##### 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'方法第一个 参数'要运行的命令', 第二个参数'字符串参数的列表' ,第三个参数'是一些配置项',具体的以后研究 注:相当于会拼成 一个'npm install -D 报名' ~~~ [nodejs 文档对child_process说明](http://nodejs.cn/api/child_process.html) [spawn方法的说明](http://nodejs.cn/api/child_process.html#child_process_child_process_spawn_command_args_options) ~~~ /** * @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(); } }); }); }; ~~~ >[danger] ##### isInstalled 判断某个包是否安装 ~~~ 1.这里需要知道的api'require.resolve',这个方法主要做了? 1.1.使用内部的 require() 机制查询模块的位置,此操作只返回解析后的文件名,不会加载该模块。 如果找不到模块,则会抛出 MODULE_NOT_FOUND 错误。 这里要注意的是如果你写的只是一个名称,他是从'node_modules' 开始找的 2.因此这个方法将会帮我们判断某个包是否出现在'node_modules',也就是是否安装喽 ~~~ [node对这个api的解释](http://nodejs.cn/api/modules.html#modules_require_resolve_request_options) [别人文章里的解释](https://www.jb51.net/article/111869.htm) * 这里我写了一个小例子做说明 ![](https://img.kancloud.cn/6d/8e/6d8ecd0dcc6156d7b60e847f13d53150_876x322.png) ~~~ /** * @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; } }; ~~~ >[danger] ##### 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." } ]; ~~~ >[danger] ##### installedClis判断是否两个CLI是否安装了 ~~~ 1.这个就很简单一个filter 过滤方法 ~~~ ~~~ const installedClis = CLIs.filter(cli => cli.installed); ~~~ >[danger] ##### 根据安装数量进行处理 ~~~ 1.整个代码的最后执行,如果你'webpack-command'和'webpack-cli'任意一个都没安装, 那么就会让你选择安装一个 2.如果安装其中一个,就正常运行 3.如果两个都安装了,那就提示删除一个才能运行 4.path.resolve总是返回一个以相对于当前的工作目录(working directory)的绝对路径。 关于这个api'https://blog.csdn.net/iconhot/article/details/89257576' 5.fs.existsSync 判断文件是否存在 ~~~ ~~~ if (installedClis.length === 0) { // 如果你'webpack-command'和'webpack-cli'任意一个都没安装 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) { // 循环带有'webpack-command'和'webpack-cli' 这配置项的数组 if (item.recommended) {// 可以发现默认让你安装的其实是'webpack-cli',他的这个属性才是true notify += `\n - ${item.name} (${item.url})\n ${item.description}`; } } console.error(notify); // 查询你根目录是否有yarn.lock 来决定是用yarn 还是 npm // path.resolve总是返回一个以相对于当前的工作目录的绝对路径。 // fs.existsSync 判断文件是否存在 // process.cwd() 方法会返回 Node.js 进程的当前工作目录 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; } ~~~ >[danger] ##### 总结 图片来自下面的参考文章 ~~~ 1.webpack 最终找到 webpack-cli (webpack-command) 这个 npm 包,并且 执行 CLI ~~~ ![](https://img.kancloud.cn/51/4d/514d3a6394d12f79d5a5ecd1d27b9a8d_634x305.png) >[danger] ##### 参考的文章 [Webpack 进阶之源码分析(一)](https://segmentfault.com/a/1190000021469703)