前情回顾
在上篇 npm run build(上) 中, 介绍了
- 从
npm run build,找执行命令webpack - 通过
package.json中bin字段获取到webpack命令映射的执行文件webpack.js。 webpack.js文件中执行runCli()方法,用来载入webpack-cli命令的映射文件cli.js。cli.js执行runCLI()方法,实例化new WebpackCli(),并执行run()方法。- 实例化
this.color = createColors()返回一个对象,包含各种方法,如red()、bgRed()等,用来在终端生成彩色文字。this.logger = getLogger()自定义warn、error、info、success的log方法,增加“[webpack-cli]”前缀。this.program = program将commander实例挂载到this.program上。初始化program名字,配置错误写入时使用this.logger.error,并配置错误输出格式
run()方法- 利用
this.program.action方法来响应终端命令。如果没有传入命令,默认命令为build。 - 然后执行
loadCommanByName()方法,如果判断是build命令,执行makeCommand()方法。
- 利用
- 实例化
继续学习
makeCommand()方法
上一节中学习了loadCommanByName()中,执行了makeCommand()方法。那么该方法如何使用呢?
参数:接受三个参数
commandOptions:一些命令的配置对象,其中包括name,usage,description,alias,dependecies字段。options:注册option参数action:commander.action回调函数
作用
-
在
this.program.commands中包含了所有已注册的命令,通过commandOptions中name,alias字段,判断命令是否注册过。注册过不重复注册 -
没有注册,初始化注册。注册描述,用法,别名等
-
检测
commandOptions中dependencies依赖是否已经安装。- 安装了:跳过
- 没安装,并且是查询该命令的
help,记录未全部安装,并跳过。(查询帮助不需要安装依赖) - 没安装
- 依赖
webpack但是WEBPACK_PACKAGE_IS_CUSTOM提供自定义依赖,跳过 - 依赖
dev-server但是WEBPACK_DEV_SERVER_PACKAGE_IS_CUSTOM提供自定义依赖,跳过 - 否则执行
doInstall()安装
- 依赖
checkPackageExists() 方法来判断是否安装
- 如果是 yarn pnp 管理包,直接默认已安装。
- 否则从当前文件的目录
node_modules开始向上逐级查找,直到顶层"/"- 还没找到,再从全局包安装路径
require("module").globalPaths中查找
doInstall() 方法逻辑
- 参数:
packageName:依赖的 npm 包名options.preMessage:预处理提示信息,这里提示使用某命令,需要安装对应哪个依赖- 逻辑
- 获取本地包管理工具(npm,pnpm,yarn)。
- 首先通过查询
xx-lock.json文件返回对应包管理工具- 否则利用
cross-spwansync()方法,启动一个进程执行xx -version命令,检测管理工具- 执行
option.preMessage()提示将安装依赖包- 利用
readline.createInterface创建可读流实例,调用question()方法询问是否安装依赖。
- 用户输入“Y”或者“yes”,使用
sync()方法安装- 否则退出
- 接下来处理第二个参数
options
- 如果是函数类型
options- 在请求
help但是有依赖没有安装完成。对command.description修改,提示要安装哪些依赖 - 否则获取
options执行结果,赋值给options
- 在请求
- 遍历
options,利用makeOption()函数,将options每一项处理成标准的配置,new Option()创建option对象,然后注册command.addOption。
5. 将第三个参数,作为
command.action的回调函数。
回顾一下makeCommand通过program.commands获取全部的注册命令,遍历全部命令查找alias或name是否有与传入的第一个参数commandOptions中的alias或name相同,来判断是否注册过。没有注册过,利用commandOptions参数中的值,把alias、descrption、name、usage都初始化。检测commandOptions.dependeces依赖是否安装,检测方法就是通过node_modeules逐层查找,没有再到全局安装路径下查找。没安装就利用cross-spwan的sync方法,执行安装命令。然后再把第二个参数的options中的每一项格式化后,绑定到command上。最后把第三个参数绑定到command.action上。
执行 build 命令时,在 loadCommanByName() 中是如何执行 makeCommand() 的?
在了解了makeCommand()就是在初始化注册一个命令,我们看看在build时loadCommandByName()中给makeCommand()传了什么参数
参数1(commandOptions):buildCommandOptions
参数2(options):传入的是一个函数。通过loadWeabpack()加载webpack。然后执行getBuiltInOptions返回options
-
loadWeabpack()函数加载weabpackloadWeabpack()使用tryRequireThenImport('weabpack', true)加载weabpack。而在
tryRequireThenImport()中根据moduleType类型:- 如果是
commonjs使用require()加载。 - 如果是
esm使用动态加载import()。 - 如果都不是,即
unknown优先用require()报错了,在catch中使用import()加载 - 最后在判断是不是通过
export.default导出的,是就是把export.default作为结果返回,否则直接返回结果
- 如果是
getBuiltInOptions()函数获取初始options
通过this.weabpack.cli.getArguments()获取到初始options,整理成与builtInFlags数据结构相同的类型。然后与builtInFlags合并数组,作为结果返回。
builtInFlags定义的一个常量,数据结构如下:
interface WebpackCLIBuiltInFlag {
name: string;
alias?: string;
type?: (
value: string,
previous: Record<string, BasicPrimitive | object>,
) => Record<string, BasicPrimitive | object>;
configs?: Partial<FlagConfig>[];
negative?: boolean;
multiple?: boolean;
valueName?: string;
description: string;
describe?: string;
negatedDescription?: string;
defaultValue?: string;
helpLevel: "minimum" | "verbose";
}
this.weabpack.cli.getArguments()结果是什么?
首先要找到cli.getArguments,在加载模块时是根据package.json中main字段来确定入口文件。指向了lib/index.js文件。
在lib/index.js文件中返回一个对象,其中cli字段,是返回的cli.js模块,因此getArguments()也就在这个文件中
getArguments()主要是利用traverse()将../schemas/weabpackOptions.json整理并收集到flag对象中。最后遍历flag再处理一遍数据结构。最终返回flag
traverse()根据type字段进行递归object类型,properties字段是一个对象,遍历每一项递归array类型,如果items是数组,遍历递归每一项。否则将整个items递归一次- 其他类型如果有
anyOf、allOf、oneOf遍历每一项递归 - 递归时会利用
addFlag()方法,将含有enum字段,或者type字段是string、number、boolean其中之一,或者instanceof字段是regexp。收集到flag对象中。flag对象以路径为key,值如下:
flags[name] = { configs: [], description: undefined, simpleType: undefined, multiple: undefined };- 其中
configs会将本次的argConfig放入其中。argConfig结构如下
// 将有 enum字段,instanceof 是RegExp,或 type是boolean,string,number返回对象{type:xxx,value?:xx}。其他返回 undefined const argConfigBase = schemaToArgumentConfig(path[0].schema); const argConfig = { ...argConfigBase, multiple, // 优先返回 schema.cli.description,其次 schema.description description: getDescription(path), // path第一个就是当前的对象 path: path[0].path };
for循环,为flag中每项初始化时,description、simpleType、multiple三个字段赋值description:把config中每一项description合并到一起simpleType:config中所有都是相同type则返回type,否则返回stringmultiple:config有一项multiple是true,就是true
总结下,第二个参数到底做了什么?首先利用loadWeabpack()加载weabpack,loadWeabpack()利用tryRequireThenImport()兼容commonjs与esm不同模块化的加载方式。然后执行getBuiltInOptions()获取build options,在getBuiltInOptions()中,将初始builtInFlags与this.weabpack.cli.getArguments()结果合并。最终返回一个如下的数组对象。
参数3(action):触发命令的回调函数
前面都是build命令的前菜,接下来就来到了build的主要命令。这个篇幅比较长,留到下一篇详细介绍