跳到主要内容

原生脚手架概述

研发的视角:开发脚手架的必要性

核心目标是:提升前端研发效能

脚手架核心价值

将研发过程:

  • 自动化:项目重复代码拷贝/git 操作/发布上线操作
  • 标准化:项目创建/git flow/发布流程/回滚流程
  • 数据化:研发过程系统化、数据化,是的研发过程可量化

和自动化构建工具区别

问题:jenkins、travis 等自动化构建工具已经比较成熟了,为什么还需要自研脚手架?

  • 不满足需求:jenkins、travis 通常在 git hooks 中触发,需要在服务端执行,无法覆盖研发人员本地的功能,如:创建项目自动化、本地 git 操作自动化等
  • 定制复杂:jenkins、travis 定制过程需要开发插件,其过程较为复杂,需要使用 java 语言,对前端同学开发不友好

从使用角度理解什么是脚手架

脚手架简介

脚手架本质是一个操作系统的客户端,它通过命令执行,比如

vue create vue-test-app

上面这条命令由 3 个部分组成:

  • 主命令:vue
  • command: create
  • command 的 param: vue-test-app

他表示创建一个 vue 项目,项目的名称为 vue-test-app,以上是一个较为简单的脚手架命令,但实际场景往往更加复杂,比如:

当前目录已经有文件了,我们需要覆盖当前目录下的文件,强制进行安装 vue 项目,此时我们就可以输入:

vue create vue-test-app --force

这里的 --force 叫做 option,用来辅助脚手架确认在特定场景下用户的选择(可以理解为配置)。还有一种场景:

通过 vue create创建项目时,会自动执行npm install帮用户安装依赖,如果我们希望使用淘宝源来安装,可以输入命令:

vue create vue-test-app --force -r https://registry.npm.taobao.org

这里的-r也叫做 option,它与--force不同的是它使用-,并且使用简写,这里的 -r 也可以替换成 --registry

-r https://registry.npm.taobao.org 后面的 https://registry.npm.taobao.org 成为 option 的 param,其实 --force 可以理解为--force true,简写为 --force-f

脚手架执行原理

1. which vue
2. ll
3. cd bin/ (bin 下面有一个vue超|软链接)
  • 在终端输入 vue create vue-test-app
  • 终端解析出 vue 命令
  • 终端在环境变量中找到 vue 命令
  • 终端根据 vue 命令链接到实际文件 vue.js
  • 终端利用 node 执行 vue.js
  • vue.js 解析 command/options
  • vue.js 执行 command
  • 执行完毕,退出执行

从应用的角度看如何开发一个脚手架

这里以 vue-cli 为例

  • 开发 npm 项目,该项目中应包含一个 bin/vue.js 文件,并将这个项目发布到 npm
  • 将 npm 项目安装到 node 的 lib/node_modules
  • 在 node 的 bin 目录下配置 vue 软链接指向 lib/node_modules/@vue/cli/bin/vue.js

这样我们在执行 vue 的命令的时候就可以找到 vue.js 进行执行

脚手架的实现原理

QUESTION

  • 为什么全局安装 @vue/cli 后会添加的命令为 vue?

    npm install -g @vue/cli

    通过 which vue 等命令找到 vue 的最终执行文件名称为 vue.js 我们可以在 vue 的文件夹中找到 package.json ,会发现有一个指令为 bin 中配置了相关的安装完包之后 软连接对应的名称 vue 以及对应的执行文件 bin\vue.js

  • 全局安装 @vue/cli 时发生了什么

    1. npm 将包安装到 node 下面的 node_modules 中
    2. 当包完全下载完成之后会解析包下面的 package.json 中的 bin,如果发现有这个配置,就会在 node 的 bin 目录下安装一个软链接,指向包的 package.json 中 bin 后面配置的文件
  • 为什么 vue 指向一个 js 文件,我们却可以直接通过 vue 命令直接去执行它

    • 在执行 vue 命令的时候,操作系统会根据 which vue 命令找到并执行相关文件(执行 vue 和直接执行 which vue 找到的文件地址是一样的)【在环境变量中查找 vue 命令是否被注册,如果没有注册则给出提示,如果注册了则执行相关的文件】

    • 自己写的文件直接执行是执行不起来的

      • 添加可执行权限

        • chmod 777 自己的文件.js
      • 添加完之后直接执行 自己的文件.js 还是执行不起来,为什么呢?JS 文件必须通过一个解释器 node 来进行执行

      • 运行 node 自己的文件.js 这时候就能执行起来了

    • 可以直接执行 vue 是因为在 vue.js 的上方添加了一个环境变量 #!/usr/bin/env node(为什么可以直接通过命令执行的原因)

    • #!/usr/bin/env node 说明

      • 这句话告诉我们 到环境变量中查找 node 命令,找到 node 命令后去使用 node 命令来执行
      • /usr/bin/env node test.js === ./test.js === node test.js
    • 觉得通过./的方式执行不太优雅,想直接通过命令来执行怎么做呢?

      1. 找到环境变量 echo $PATH
      2. 在环境变量中创建一个软连接
        1. pwd 查找当前文件目录地址并拷贝
        2. 找到本机 node 下的 bin 目录
        3. 创建软连接 ln -s 命令名称 刚才拷贝的文件目录地址/文件名称
      3. 这个时候就可以使用命令来进行执行了
  • 以下两种写法有啥区别?

    #!/usr/bin/env node
    #!/usr/bin/node
    • 第一种是在环境变量中查找 Node
    • 第二种是直接执行 /usr/bin/ 目录下的 node
  • 为什么说脚手架本质是操作系统的客户端?它和我们在 PC 上安装的应用/软件有什么区别?

    • 执行脚手架的时候会发现它其实是作为一个操作系统的可执行文件来进行执行的,所以我们说开发的脚手架就是操作系统上的一个客户端(客户端不是我们编写的脚手架文件)。为什么说它是客户端?因为脚手架执行是依靠 node 命令的,node 是一个操作系统的客户端,而我们的执行文件仅是 node 的一个参数。(Node 本身是一个可执行文件)
    • 本质来说没有区别
  • 如何为 node 脚手架命令创建别名?

    • 通过软链接(可以做嵌套使用)
      • ln -s 命令名称 已有的名称
  • 描述脚手架命令执行的全过程-流程图

脚手架的开发流程

脚手架开发流程详解

开发流程

  • 创建 npm 项目

  • 创建脚手架入口文件,最上方添加:

    #!/usr/bin/env node
  • 配置 package.json 添加 bin 属性

  • 编写脚手架代码

  • 将脚手架发布到 npm

  • 📢 注意点:

    1. 脚手架在全局安装后如果指向了本地的代码仓库(指向原因:在当前目录下存在相关代码仓库,npm 就会将本地文件作为软链接直接链接过去-npm 方便开发者进行本地的开发和调试),如果本地仓库进行改变了,那么直接使用脚手架命令也会得到相应的更新

    2. 如果不希望出现 1 的情况,只需要离开相关目录进行 npm 安装脚手架即可

使用流程

  • 安装脚手架

    npm install -g your-own-cli
  • 使用脚手架

    your-own-cli

脚手架开发难点

  • 分包: 将复杂的系统拆分成若干个模块

  • 命令注册:

    vue create
    vue add
    vue invoke
  • 参数解析

    vue command [options] <params>
    • options 全称: --version、--help
    • Options 简写: -v、-h
    • 带 params 的 options: --path /users/...
    • 帮助文档:
      • Global help
        • usage
        • options
        • Commands
  • 命令行交互

  • 日志打印

  • 命令行文字变色

  • 网络通信:http/websocket

  • 文件处理

  • ...

开发调试

  1. 在有脚手架仓库的文件夹下执行npm i -g 脚手架名称 进行安装调试

  2. 在脚手架目录下执行 npm linknpm link 会在全局 node_modules 下安装相关库文件

    说明:执行完 npm link 之后,会在 Node 里安装一个相关的命令链向 node_modules 下的文件,node_modules 下的文件链向本地仓库,实现软链

  3. 复杂脚手架调试方法

    1. 创建子库文件夹:注意修改 package.json 中的 main 指向入口文件

    2. 在子库文件夹下执行 npm link

    3. 到主库里执行npm link 库文件夹名称

    4. 手动添加子库 package.json 依赖

    5. 至此便可顺利调试复杂脚手架了

    6. 如果此时子库文件已经开发好,将子库文件进行发布

    7. 当子库文件进行上线之后进入到主库文件夹

      1. 执行 npm unlink 子库文件包 将文件解开绑定

      2. 安装线上库文件包 npm install

        注解:如果此时安装还报错的话:

        1. 将子库文件重新绑定 link 再解绑 unlink
        2. 执行 which 主库文件包 例如 which imooc-test 查看包文件地址并 复制地址
        3. 执行 ll 刚才复制的地址(注意这里的地址回退到 node 版本处拼接查看 lib/node_modules
        4. 如果看到上线后的库文件依然存在的话执行 npm remove -g 子库文件包
        5. 执行 npm i 子库文件包

链接本地脚手架

cd your-cli-dir
# 将当前项目链接到 node 全局 node_modules 中作为一个库文件,并解析 bin 配置创建可执行文件
npm link

链接本地库文件

cd your-lib-dir
# 将当前项目链接到 node 全局 node_modules 中作为一个库文件,并解析 bin 配置创建可执行文件
npm link
cd your-cli-dir
# 将当前项目中 node_modules 下指定的库文件链接到 node 全局 node_modules 下的库文件
npm link your-lib

取消链接本地库文件

cd your-lib-dir
# 将当前项目从 node 全局 node_modules 中移除
npm unlink
cd your-cli-dir
# link 存在:将当前项目中的库文件依赖移除
npm unlink your-lib
# link 不存在
rm -rf node_modules
npm install

命令注册&参数解析

通过 process 库进行实现

const argv = require("process").argv;

const command = argv[2];

附:完整代码详见Github

发布上线

  1. 先发布工具包(如果没有请忽略)
    1. 执行 npm unlink 解除全局绑定
    2. 升级版本号
    3. 执行 npm publish 发布工具包
  2. 发布脚手架
    1. 发布 scaffold-demo-utils 包
    2. 执行 npm unlink 解除全局绑定,如果遇到报错:删掉 node_modules 重新执行命令
    3. 执行 npm unlink scaffold-demo-utils
    4. 升级版本号
    5. 执行 npm publish 发布

原生脚手架开发痛点分析

痛点一: 重复操作

  • 多 package 本地 link
  • 多 package 依赖安装
  • 多 package 单元测试
  • 多 package 代码提交
  • 多 package 代码发布

痛点二: 版本一致性

  • 发布时版本一致性
  • 发布后相互依赖版本升级

package 越多,管理复杂度越高,所以如果你的脚手架开发有 多 package 的情况,建议使用Lerna进行开发