【初级FE也能懂】前端架构师如何设计并开发一个脚手架解决日常CI/CD问题的?(上)

本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战

大家好,我是一只不守妇道的花喵。

本文你可以学到:

  • 熟悉cli开发的全流程,可以开发出类似@babel/core@babel/preset-env的npm包(是不是很装逼)
  • 熟悉npm发布流程
  • 熟悉lerna开发cli工具,快速入门lerna
  • 了解架构师如何完整设计、开发一个脚手架
  • 如何利用脚手架高效解决日常开发中的CI/CD流程

注:本文老衲在mac下测试,使用win的同学目录自己改下哈

1. 准备

为什么要开发脚手架(像个架构师一样思考)

这就需要说到CI/CD

工厂里的装配线以快速、自动化、可重复的方式从原材料生产出消费品。同样,软件交付管道以快速、自动化和可重复的方式从源代码生成发布版本。如何完成这项工作的总体设计称为“持续交付”(CD)。启动装配线的过程称为“持续集成”(CI)。确保质量的过程称为“持续测试”,将最终产品提供给用户的过程称为“持续部署”。一些专家让这一切简单、顺畅、高效地运行,这些人被称为运维开发(DevOps)践行者。[1]

解决的问题:

传统(事实上大一点点的公司都会考虑CI/CD自动化)手动操作低效、出错率高、依赖某些特定人员等。尤其在项目越来越大的时候,这些问题将更为严重,更不用谈“灰度发布”,这些点有一个就有足够的理由接着看本文。作为前端架构师就是要解决各种“效能”问题。

解决的场景:

  • 统一CI/CD规范
  • 提高CI/CD效率
  • 减少CI/CD出错率
  • 不依赖任何人,按流程走
  • ……

前置知识

  • 熟悉js,了解nodejs
  • 了解过一种或几种命令行工具,如:vue-clivitecreate-react-appgit
  • 了解npmjs注册、登录、发布等流程(下面有最少知识教程)

需求

  • 分包,一个脚手架如果较为复杂就必然涉及分包,分包可以有效降低工程复杂度、提高开发/维护效率、并且每个分包相对独立,可以单独使用,babel就有将近150的包,点击查看,互相依赖,想象下你怎么管理这么多包?每次更新版本、安装依赖怎么操作?分包的依赖升级又要怎么整?
  • 命令+子命令,日常用的gitvue-cli都有子命令,脚手架如果足够复杂也会涉及子命令
  • 日志
  • 命令行交互
  • 网络请求
  • 本地文件处理
  • ……

注:我们做一个项目的需求调研到技术选型,不会只考虑当下需要什么,都会再需求下“多考虑一步”,会考虑其扩展性,如上面的分包、命令+子命令。当然如果确实是一个很小的脚手架,是可以不考虑分包以降低设计的复杂度(当然熟悉了lerna没啥复杂度),比如说只有一两个功能模块。但是如果像vue-clibabel,涉及的模块/插件几十上百个,那就必须要考虑。作为预留,我们这边使用能分包(Multirepo)的扩展方案。

Monorepo vs Multirepo
16ca298f560d81d8.png

选型

  • 分包,lerna,业界出名的Multirepo方案解决方案
  • 命令+子命令,commanderyargs
  • 日志,npmlogcolors
  • 命令行交互
  • 网络请求
  • 本地文件处理,fs-extra

其他

  • 版本比较:semver
  • 解析参数选项:minimist
  • ……

2. 开始

2.1.初始化

  • 创建mkdir ~/huamiao-cli_all文件夹,后面有后端服务
  • 创建mkdir ~/huamiao-cli_all/huamiao-cli
  • 切换目录:cd ~/huamiao-cli_all/huamiao-cli
  • 初始化为npm项目:npm init -y
  • 安装lerna:npm i -D lerna,为了使用方便(使用的时候不用使用npx,)全局也安装lerna:npm i -g lerna
  • lerna初始化:lerna init(如果不想全局安装lerna,可以使用npx lerna init),生成lerna.json并修改版本为1.0.0
{
  "packages": [
    "packages/*"
  ],
  "version": "1.0.0"
}

复制代码
  • lerna init后会默认执行git init,创建.gitignore,忽略以下文件:
**/node_modules
.vscode
.DS_Store
lerna-debug.log
复制代码
  • git暂存:git add . && git status
git add . && git status
位于分支 master

尚无提交

要提交的变更:
  (使用 "git rm --cached <文件>..." 以取消暂存)
        新文件:   .gitignore
        新文件:   lerna.json
        新文件:   package-lock.json
        新文件:   package.json
复制代码
  • 提交到本地仓库:git commit -m 'init'

注:npm官方源如果太慢,可以切换为淘宝源或使用cnpm/yarn

npm config set registry https://registry.npm.taobao.org/
复制代码

2.2.创建包&测试发布

  • 创建core核心包,输入package name,一路回车

特别注意:package name: (core) @huamiao-cli/core

lerna create core

package name: (core) @huamiao-cli/core

{
  "name": "@huamiao-cli/core",
  "version": "1.0.0",
  "description": "> TODO: description",
  "homepage": "",
  "license": "ISC",
  "main": "lib/core.js",
  "directories": {
    "lib": "lib",
    "test": "__tests__"
  },
  "files": [
    "lib"
  ],
  "publishConfig": {
    "registry": "https://registry.npm.taobao.org"
  },
  "scripts": {
    "test": "echo \"Error: run tests from root\" && exit 1"
  }
}


Is this OK? (yes) 
lerna success create New package @huamiao-cli/core created at ./packages/core
复制代码
  • 创建utils工具包,同上
  • npmjs创建组织@huamiao-cli

点击Create
image
点击Skip
image
现在可以在npmjs->个人头像->packages查看到

  • 发布,lerna publish,发布组织的包需要配置publishConfig

如果你用淘宝源,可能会出现下面的情况:

{
  "name": "@huamiao-cli/core",
  "version": "1.0.6",
  "description": "> TODO: description",
  "homepage": "",
  "license": "ISC",
  "main": "lib/core.js",
  "directories": {
    "lib": "lib",
    "test": "__tests__"
  },
  "files": [
    "lib"
  ],
  "publishConfig": {
    "registry": "https://registry.npm.taobao.org"
  },
  "scripts": {
    "test": "echo 'run utils test'"
  }
}
复制代码

看这边

  "publishConfig": {
    "registry": "https://registry.npm.taobao.org"
  },
复制代码

packages中两个包的package.json都改为:

  "publishConfig": {
    "access": "public"
  }
复制代码

发布前要git提交,并且要绑定远程仓库,否则:

lerna publish     
info cli using local version of lerna
lerna notice cli v4.0.0
lerna info current version 1.0.0
lerna ERR! ENOREMOTEBRANCH Branch 'master' doesn't exist in remote 'origin'.
lerna ERR! ENOREMOTEBRANCH If this is a new branch, please make sure you push it to the remote first.
复制代码

我们在gitee创建一个公开库,这边不赘述了,有疑问留言。

配置远程仓库并推送:

git remote add origin https://gitee.com/xxx/huamiao-cli.git
git push -u origin master
复制代码

切换到npm官方源:

npm config set registry https://registry.npmjs.org
复制代码

注:如果这边不慎没切换到官方源,再提交一个git,再发布,版本累加即可发布

发布:lerna publish选第一个先:

❯ Patch (1.0.1) 
  Minor (1.1.0) 
  Major (2.0.0) 
  Prepatch (1.0.1-alpha.0) 
  Preminor (1.1.0-alpha.0) 
  Premajor (2.0.0-alpha.0) 
  Custom Prerelease 
  Custom Version 
复制代码

输入y

lerna publish                                                
info cli using local version of lerna
lerna notice cli v4.0.0
lerna info current version 1.0.0
lerna info Assuming all packages changed
? Select a new version (currently 1.0.0) Patch (1.0.1)

Changes:
 - @huamiao-cli/core: 1.0.0 => 1.0.1
 - @huamiao-cli/utils: 1.0.0 => 1.0.1

? Are you sure you want to publish these packages? (ynH) y
>> Yes
复制代码

完整日志:

info cli using local version of lerna
lerna notice cli v4.0.0
lerna info current version 1.0.4
lerna info Looking for changed packages since v1.0.4
? Select a new version (currently 1.0.4) Patch (1.0.5)

Changes:
 - @huamiao-cli-dev/core: 1.0.4 => 1.0.5
 - @huamiao-cli-dev/utils: 1.0.4 => 1.0.5

? Are you sure you want to publish these packages? Yes
lerna info execute Skipping releases
lerna info git Pushing tags...
lerna info publish Publishing packages to npm...
lerna notice Skipping all user and access validation due to third-party registry
lerna notice Make sure you're authenticated properly ¯\_(ツ)_/¯
lerna WARN ENOLICENSE Packages @huamiao-cli-dev/core and @huamiao-cli-dev/utils are missing a license.
lerna WARN ENOLICENSE One way to fix this is to add a LICENSE.md file to the root of this repository.
lerna WARN ENOLICENSE See https://choosealicense.com for additional guidance.
lerna http fetch PUT 200 https://registry.npmjs.org/@huamiao-cli-dev%2futils 4431ms
lerna success published @huamiao-cli-dev/utils 1.0.5
lerna notice 
lerna notice ?  @huamiao-cli-dev/utils@1.0.5
lerna notice === Tarball Contents === 
lerna notice 73B  lib/utils.js
lerna notice 487B package.json
lerna notice 108B README.md   
lerna notice === Tarball Details === 
lerna notice name:          @huamiao-cli-dev/utils                  
lerna notice version:       1.0.5                                   
lerna notice filename:      huamiao-cli-dev-utils-1.0.5.tgz         
lerna notice package size:  565 B                                   
lerna notice unpacked size: 668 B                                   
lerna notice shasum:        9af0a5740f88b0a6829ea4a90c7107435d0f5489
lerna notice integrity:     sha512-8kKZVoH1sF0WB[...]1aTA3UQiBl70g==
lerna notice total files:   3                                       
lerna notice 
lerna http fetch PUT 200 https://registry.npmjs.org/@huamiao-cli-dev%2fcore 7474ms
lerna success published @huamiao-cli-dev/core 1.0.5
lerna notice 
lerna notice ?  @huamiao-cli-dev/core@1.0.5
lerna notice === Tarball Contents === 
lerna notice 71B  lib/core.js 
lerna notice 485B package.json
lerna notice 105B README.md   
lerna notice === Tarball Details === 
lerna notice name:          @huamiao-cli-dev/core                   
lerna notice version:       1.0.5                                   
lerna notice filename:      huamiao-cli-dev-core-1.0.5.tgz          
lerna notice package size:  566 B                                   
lerna notice unpacked size: 661 B                                   
lerna notice shasum:        a26f686a3951de2df1ef2196a1b82c9d87e299ec
lerna notice integrity:     sha512-rP/WQo7oROLD3[...]7LZKGyzZ9vHYQ==
lerna notice total files:   3                                       
lerna notice 
Successfully published:
 - @huamiao-cli-dev/core@1.0.5
 - @huamiao-cli-dev/utils@1.0.5
lerna success published 2 packages
复制代码

等待成功后,在npmjs packages中的huamiao-cli组织,查看是否发布成功。

这边发布在huamiao-cli-dev这个组织里面

WX20210701-155428@2x.png
注:npmjs不稳定,并且对于国内网络互联互通问题经常访问较慢,如果失败,判断不是我们的问题,可以重新发布,就是learn发布每次都会要求加个版本,这点有点不合理

注:这边新手会遇到各种问题,如有问题,可以在留言,老衲帮掘友解决。

强烈建议新手测试发布成功后再进行开发。

3. 最少必要知识

3.1.learn

3.1.1.初始化

  • lerna init,初始化项目,创建lerna.json存储version,并检查package.jsondevDependency有无lerna,没有则添加

3.1.2.创建package

以下<>为必须,[]为可选
Usage:lerna create <name> [loc]
name:唯一、包含作用域名(本文为@huamiao-cli,eg:@huamiao-cli/core)
loc:包和包可以嵌套,选择包的相对地址,默认为第一个配置的包
位置
复制代码
  • lerna add,安装依赖,eg:lerna add module-1 --scope=module-2scope指定包,不指定会安装到每个包里,每次只能安装一个依赖
Usage:lerna add <package>[@version] [--dev] [--exact] [--peer]
复制代码
  • lerna link,链接依赖,本地调试包的互相依赖,多个包一起开发

3.1.3.开发和测试

  • lerna exec,对packages中每个包执行命令,eg:lerna exec -- rm -rf ./node_modules
  • lerna run,对packages中每个包执行npm script,eg:lerna run test
  • lerna clean,删除所有packages中的node_module,约等于lerna exec -- rm -rf ./node_modules
  • lerna bootstrap,对packages中每个包重新按安装依赖

3.2. npm发布简易流程

3.2.1. npmjs注册账号

3.2.2. 切换为官方源

打开终端,切换为官方源(如果之前没切换过源,默认是官方的,不需要切换)

npm config set registry https://registry.npmjs.org
复制代码

3.2.3. npm登录

npm login

Username: <username>
Password: 
Email: (this IS public) <username>@qq.com
Logged in as <username> on https://registry.npmjs.org/.
复制代码

3.2.4. npm发布

npm publish

Successfully published:
 - @huamiao-cli/core@1.0.0
 - @huamiao-cli/utils@1.0.0
lerna success published 2 packages
复制代码

4. cli准备工作

4.1. 配置命令

packages/core将作为cli入口

切换到packages/core目录

新增文件bin/index.js

#!/usr/bin/env node

require('../lib/index')();
复制代码

package.json添加:

"bin": {
  "miao": "bin/index.js"
},
复制代码

lib/index.js

console.log('hello world!');
复制代码

npm link安装到全局,全局就可以调用miao命令了

打开终端测试下:miao,测试成功,正常打印出:

hello world!
复制代码

4.2. 版本号、欢迎语

接着开发,获取版本号

const packageJson = require('../package');
复制代码

上面的欢迎语,比较一般,我们来个有逼格的:

const packageJson = require('../package');

console.log('欢迎使用');
console.log(` _   _             __  __ _                    ____ _ _ 
| | | |_   _  __ _|  \\/  (_) __ _  ___        / ___| (_)
| |_| | | | |/ _\` | |\\/| | |/ _\` |/ _ \\  ___ | |   | | |
|  _  | |_| | (_| | |  | | | (_| | (_) ||___|| |___| | |
|_| |_|\\__,_|\\__,_|_|  |_|_|\\__,_|\\___/       \\____|_|_|  Version ${packageJson.version}
`);
复制代码

image

4.3. 日志,分包开发

上面的console.log('欢迎使用');改颜色下,提升逼格:

const { log } = require('@huamiao-cli/utils');
复制代码

这边log我们还没开发

core/package.json添加

"dependencies": {
    "@huamiao-cli/utils": "^1.0.0"
},
复制代码

lerna link

现在就可以用packages/utils包的方法了

日志我们用的是npmlog,切换到packages/utils需要安装依赖:npm i npmlog

packages/utils/package.json修改:

  "main": "lib/index.js",
复制代码

lib/index.js文件:

'use strict';

const log = require('./log');

// 统一导出,后面还有很多工具
module.exports = {
  log
};
复制代码

lib/log.js文件:

const log = require('npmlog')

log.level = 'info'

log.heading = 'huamiao-cli' // 自定义头部
log.addLevel('success', 2000, { fg: 'green', bold: true }) // 自定义success日志
log.addLevel('notice', 2000, { fg: 'blue', bg: 'black' }) // 自定义notice日志

module.exports = log
复制代码

回到packages/core/lib/index.js

"use strict";

module.exports = core;

const packageJson = require("../package");
const { log } = require("@huamiao-cli/utils");

function core() {
  console.log(` _   _             __  __ _                    ____ _ _ 
| | | |_   _  __ _|  \\/  (_) __ _  ___        / ___| (_)
| |_| | | | |/ _\` | |\\/| | |/ _\` |/ _ \\  ___ | |   | | |
|  _  | |_| | (_| | |  | | | (_| | (_) ||___|| |___| | |
|_| |_|\\__,_|\\__,_|_|  |_|_|\\__,_|\\___/       \\____|_|_|  Version ${packageJson.version}
`);
  log.info("欢迎使用");
}
复制代码

这样版本号、欢迎语已经ok了
image

4.4. nodejs最低版本判断

我们这个cli使用到的一些库是需要nodejs version 12+,所以这边我们需要检查下当前nodejs的版本,这边需要semver包,安装下:npm i semver

packages/core/lib/index.js增加:

  const semver = require("semver");
  const MINIMUM_NODEJS_VERSION = "14.0.0";

  if (semver.lte(process.version, MINIMUM_NODEJS_VERSION)) {
    console.log(`huamiao-cli 最低要求Node.js版本 v${MINIMUM_NODEJS_VERSION}`);
    process.exit();
  }
复制代码

这个提示我们想让他变成红色,可以用log:

  const semver = require("semver");
  const MINIMUM_NODEJS_VERSION = "14.0.0";

  if (semver.lte(process.version, MINIMUM_NODEJS_VERSION)) {
    log.error(`huamiao-cli 最低要求Node.js版本 v${MINIMUM_NODEJS_VERSION}`);
    process.exit();
  }
复制代码

最低nodejs的版本号我们抽离常量出来,方便后面修改

packages/core/lib/index.js现在我们完整的代码如下:

"use strict";

module.exports = core;

const packageJson = require("../package");
const { log } = require("@huamiao-cli/utils");

function core() {
  console.log();
  console.log(` _   _             __  __ _                    ____ _ _ 
| | | |_   _  __ _|  \\/  (_) __ _  ___        / ___| (_)
| |_| | | | |/ _\` | |\\/| | |/ _\` |/ _ \\  ___ | |   | | |
|  _  | |_| | (_| | |  | | | (_| | (_) ||___|| |___| | |
|_| |_|\\__,_|\\__,_|_|  |_|_|\\__,_|\\___/       \\____|_|_|  Version ${packageJson.version}
`);
  log.info("欢迎使用");
  console.log();

  const semver = require("semver");
  const MINIMUM_NODEJS_VERSION = "14.0.0";

  if (semver.lte(process.version, MINIMUM_NODEJS_VERSION)) {
    log.error(`huamiao-cli 最低要求Node.js版本 v${MINIMUM_NODEJS_VERSION}`);
    process.exit();
  }
}
复制代码

执行下miao
image

4.5. 分析参数是否有效,是否debug

接下来分析下参数

为了调试方便packages/utils/lib/log.js可以修改日志等级为verbose,这个是调试用的等级:

log.level = 'verbose'
复制代码

校验入参,这边需要,minimist包,安装下依赖:npm i minimist

  const minimist = require("minimist");

  log.info("校验入参:");
  let args = minimist(process.argv.slice(2));
  log.info("args: ", args);
  
  if (args["_"].length === 0) {
    log.warn('请输入参数')
  }
复制代码

测试下
miao cmdChild cmdChildParams -a aParams -b bParams

huamiao-cli info 欢迎使用 
huamiao-cli verb 校验入参: 
huamiao-cli verb args:  { _: [ 'cmdChild', 'cmdChildParams' ], a: 'aParams', b: 'bParams' }
复制代码

我们经常需要调试,所以我们加个debug参数,miao --debugminimist默认支持

  const minimist = require("minimist");

  log.info("校验入参:");
  let args = minimist(process.argv.slice(2));
  log.info("args: ", args);

  if (args["_"].length === 0 && !args.debug) {
    log.warn("请输入参数");
  } else {
    // 可以在这配置debug相关设置,比如修改log的等级为verbose来打印调试日志
    log.level = "verbose";
    log.verbose("debug");
  }
复制代码

添加下环境变量process.env.LOG_LEVEL,并设置log的日志等级

  const minimist = require("minimist");

  log.verbose("校验入参:");
  let args = minimist(process.argv.slice(2));
  log.verbose("args: ", args);

  if (args["_"].length === 0 && !args.debug) {
    log.warn("请输入参数");
  } else {
    // 可以在这配置debug相关设置,比如修改log的等级为verbose来打印调试日志
    if (args.debug) process.env.DEBUGGING = "verbose";
    else process.env.DEBUGGING = "info";
    log.level = process.env.DEBUGGING;
  }
复制代码

准备工作算结束了

4.6. 注册命令

使用commander包,安装依赖:npm i commander

const program = require("commander");

// 设置版本号、自定义用法说明
program.version(packageJson.version).usage("<command> [options] 其他说明");

// 添加命令可以在这里添加
// ……

// 注册命令
program.parse(process.argv);
复制代码

我们测试下,miao -h
image
添加个简单的命令:

const program = require("commander");

  // 设置版本号、自定义用法说明
  program.version(packageJson.version).usage("<command> [options] 其他说明");

  // 添加命令可以在这里添加
  program
    .command("test")
    .description("描述")
    .option("-a, --all", "清空全部")
    .action((cmd, options) => {
      // cmd.all 自动由上面配置 .option("-a, --all", "清空全部") 创建
      console.log("cmd: ", cmd.all);
      log.success("测试", "一只花喵");
    });

  // 其他子命令
  program.command("*").action(function (cmd, options) {
    console.log("cmd: ", cmd);
    console.log("options: ", options.args);
    console.log("没有匹配到命令:miao", args['_']);
  });

  // 注册命令
  program.option("--debug", "打开调试模式").parse(process.argv);
复制代码

试试miao test -a aaaamiao test --all bbbbmiao diy吧!

内容有点多,第一节先这样吧!

FAQ

  • 依赖错误或未知错误,清理所有依赖lerna clean,重装依赖lerna bootstrap

Loadmap

  • init【重磅】:选择模板、自定义模板等
  • 发布【重磅】:发布、git操作、构建等
  • 更多实现细节

参考

点赞、关注越多更新越快哦~

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享