背景

一般来说,脚手架是帮你减少「为减少重复性工作而做的重复性工作」的工具。

比如我们在写项目0的时候,搭建了一个项目工程,技术栈用了React。基本功能:ES6编译到ES5、代码压缩、less编译···,后来有了项目2、项目3···,每次技术栈都相同,打包脚本也差不多。每次修改代码都要做的编译ES6、less编译、压缩html这些、或者复制粘贴工程文件模板。想必这里多了好多 重复性工作。

CLI

脚手架的核心就是 「为减少重复性工作而做的重复性工作」。

  • CLI 只是「Command Line Interface」 命令行界面的缩写,本质是一个系统操作系统的客户端。
  • 脚手架一个命令,目录结构、打包脚本、babel配置、代码部署、性能优化、空的测试文件都帮你搞好了,直接写核心业务代码,不做重复性工作,这就是脚手架的作用。
  • 脚手架不仅仅限于生成项目文件,代码部署、测试化流程、git提交,能用命令行实现的功能都是ok。

    比如 vue.js 就有个 vue-cli 脚手架,基于node.js的开发环境,作者帮你把开发环境大部分东西都配置好了,你把脚手架下载下来就可以直接开发了,不用再考虑搭建这些工具环境。

搭建一个CLI

目标: 实现在命令行执行 mys-cli 来启动我们的脚手架,以下为 CLI 目录结构:
image.png

1、新建项目目录 mys-cli

  1. mkdir mys-cli # 新建 mys-cli 文件夹
  2. cd mys-cli # 切换到 mys-cli 文件夹
  3. npm init -y # 生成 package.json 文件

2、新建程序入口文件 myscli.js

切换到 mys-cli 文件下 新建 bin 文件夹,在 bin 文件下新建 myscli.js 文件

  1. touch myscli.js # 新建 myscli.js 文件

在 package.json 文件中 bin字段下面有解析」中 指定入口文件为 cli.js

  1. {
  2. "name": "mys-cli",
  3. "version": "1.0.0",
  4. "description": "",
  5. "main": "index.js",
  6. "bin": {
  7. "mysCli": "./bin/myscli.js"
  8. }, // 手动添加入口文件为 myscli.js
  9. "scripts": {
  10. "test": "echo \"Error: no test specified\" && exit 1"
  11. },
  12. "author": "",
  13. "license": "ISC"
  14. }

编辑 cli.js

  1. #!/usr/bin/env node
  2. console.log('hello world');

3、npm link 链接到全局

执行 npm link

  1. npm link # or yarn link

命令行执行 「npm link」,结果如下图:
image.png
测试一下,命令行执行 「mysCli」,查看结果

  1. mysCli # 执行 CLI 命令
  2. hello world # 打印结果

好了,一个简单的 CLI 就好了

4、package.json bin字段

参考文档:package.json 字段解析npm package.json属性详解
bin 用来指定每个内部命令对应的可执行文件位置。如果你编写的是一个node工具的时候一定会用到 bin 字段。
当我们编写一个 CLI 工具的时候,需要指定工具的运行命令,比如常用的 webpack 模块,他的运行命令就是webpack。当它作为一个对象存在时,可以设置多个运行命令,否则默认启动命令为 name「package.json 中name字段」字段中的值。

  1. "bin": {
  2. "webpack": "bin/index.js",
  3. }
  4. # or
  5. "bin": "bin/index.js"

当我们执行 webpack 命令的时候就会执行 bin/index.js 文件中的代码。这就好像 scripts 字段,能减少一些命令行的输入,不用输入全路径。

在模块以依赖的方式被安装,如果存在 bin 选项。「以 mac 为例」

  • 若是全局安装,依赖会被安装在 /usr/local/lib/node_modules 目录下面, 在 /usr/local/bin 目录下创建一个 软连接 「这里只是一个快捷方式,与依赖建立连接」, 由于系统的 PATH 环境变量会指向 /usr/local/bin,因此在运行 npm 时,就可以不带路径,直接通过命令「CLI名称,定义在bin中」来调用这些脚本「会查找 PATH 环境变量,并找到对应的 软连接」
  • 若是局部安装,依赖会被装在当前项目 ./node_modules 下,并在项目内的 ./node_modules/.bin/ 目录下创建一个软链接。运行局部命令只能通过 package.jsonscriptsnpx 来启动,或者命令行直接运行 /node_modules/.bin/xxx.js 「连接项目依赖的软连接」

npx

参考文档:阮一峰老师 npx 使用教程npm 和 npx 有什么区别?

  • npx 的原理很简单,就是运行的时候,会到 node_modules/.bin 路径和环境变量 $PATH 里面,检查命令是否存在。

    npm scripts

    参考文档:阮一峰老师 npm scripts 使用指南

  • npm 脚本(npm scripts)npm 允许在 package.json 文件里面,使用 scripts 字段定义脚本命令。

  • npm 脚本 的原理非常简单。每当执行 npm run,就会自动新建一个 Shell,在这个 Shell 里面执行指定的脚本命令。因此,只要是 Shell(一般是 Bash)可以运行的命令,就可以写在 npm 脚本里面。
  • 比较特别的是,npm run 新建的这个 Shell,会将当前目录的 node_modules/.bin 子目录加入 PATH 变量,执行结束后,再将 PATH 变量恢复原样。这意味着可以直接用脚本名调用,而不必加上路径。

所有 node_modules/.bin/ 目录下的命令,都可以用 npm run [命令] 的格式运行。在命令行下,键入npm run,然后按 tab 键,就会显示所有可以使用的命令。

5、#! /usr/bin/env node 是用来干嘛的?

参考文档:#!/usr/bin/env node 到底是什么?Shell脚本中的shebang到底是什么Linux 上的 Shebang 符号(#!)

#!这个符号的名称叫 Shebang,Shebang通常出现在类Unix系统的脚本中第一行,作为前两个字符。在Shebang之后,可以有一个或数个空白字符,后接解释器的绝对路径,用于指明执行这个脚本文件的解释器。

我们在调用 #! /usr/bin/env 时,系统程序会分析 Shebang 后的路径内容,其实告诉系统可以在 PATH目录 中查找,所以配置 #!/usr/bin/env node , 就是解决了不同的用户node路径不同的问题,可以让系统动态的去查找node来执行你的脚本文件。

#!/usr/bin/env node 说简单点这句话就是让系统用 node 来执行此文件。

6、npm link 解说

参考文档:npm link 全方位解读npm link用法总结npm-link

执行 npm link 和全局安装包差不多,会在 /usr/local/lib/node_modules 目录下面 创建依赖软连接「和全局安装不同」, 在 /usr/local/bin 目录下创建一个 软连接 ,该链接 链接到 npm link 执行命令的包。

本质是在 当前项目 本地 npm 模块之间建立连接,直接通过命令「CLI名称,定义在bin中」来调用这些脚本「会查找 PATH 环境变量,并找到对应的 软连接」,可以在本地进行模块测试。

npm link package-name 将创建一个从全局安装 package-name 到 node_modules/ 当前文件夹的符号链接。 请注意,该 package-name 取自 package.json,而不是目录名称。

热门脚手架工具库

实际生产中搭建一个脚手架或者阅读其他脚手架源码的时候需要了解下面这些工具库

名称 简介
commander 命令行自定义指令
inquirer 命令行询问用户问题,记录回答结果
chalk 控制台输出内容样式美化 [颜色、背景颜色、下划线、反向、···]
log-symbols 控制台打印各种状态的图标 [info、success、warning、error]
ora 控制台 loading 样式
figlet 控制台打印 logo
easy-table 控制台输出表格
download-git-repo 下载远程模版
fs-extra 系统fs模块的扩展,提供了更多便利的 API,并继承了fs模块的 API
cross-spawn 支持跨平台调用系统上的命令
- 为什么会有 cross-spawn ?
- node 自带的 child_process.spawn() 方法在 Windows 上存在兼容问题
- 参考文档
- 用spawn 来编写跨平台 Node.js命令
simple-git node应用中执行各种git命令 [拉取、推送、打tag、···]
yargs-parser 用于解析命令行参数 如:’—foo=99 —bar=33’ ==> { foo: 99, bar: 33 }
debug Node.js 和 Web 浏览器 调试实用程序
resolve 实现node的 require.resolve() 算法,可以异步和同步使用require.resolve()代表文件
single 将模块组合成 单个 js 以供浏览器运行
dayjs Moment.js 的快速 2kB 替代品,操作和显示日期和时间
semver npm的语义版本器,Semver 是当下被库使用的一套版本命名规范工具
@irim/cli-base Node Cli 基础方法库 *
got Node.js 的人性化和强大的 HTTP 请求库
open 打开像URL [图片、网页、···],文件,可执行文件等的东西
update-notifier 更新CLI应用程序的通知,以非侵入方式告知用户您的更新包。
chokidar 监听指定文件化
decache 删除require缓存
ejs EJS 是一套简单的模板语言,帮你利用普通的 JavaScript 代码生成 HTML 页面
prettier 代码格式化工具
naming-style 工具类库,用于将文本转化为不同格式的命名风格(如:驼峰式、连字符式、…)
Redis Node.js的现代高性能 Redis 客户端,Redis 是什么?
pm2 node 负载均衡管理器
mem-fs &
mem-fs-editor
提供了一系列 API,方便操作模板文件(作者也是Inquirer.js的作者)

参考文件