大家好,我是一只不守妇道的花喵。
本文你能够学到:
- 相熟
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-cli
、vite
、create-react-app
、git
等 - 理解
npmjs
注册、登录、公布等流程(上面有起码常识教程)
需要
- 分包,一个脚手架如果较为简单就必然波及分包,分包能够无效升高工程复杂度、进步开发 / 保护效率、并且每个分包绝对独立,能够独自应用,
babel
就有将近 150 的包,点击查看,相互依赖,设想下你怎么治理这么多包?每次更新版本、装置依赖怎么操作?分包的依赖降级又要怎么整? - 命令 + 子命令,日常用的
git
,vue-cli
都有子命令,脚手架如果足够简单也会波及子命令 - 日志
- 命令行交互
- 网络申请
- 本地文件解决
- ……
注:咱们做一个我的项目的需要调研到技术选型,不会只思考当下须要什么,都会再需要下“多思考一步”,会思考其扩展性,如下面的分包、命令 + 子命令。当然如果的确是一个很小的脚手架,是能够不思考分包以升高设计的复杂度(当然相熟了 lerna
没啥复杂度),比如说只有一两个功能模块。然而如果像 vue-cli
或babel
,波及的模块 / 插件几十上百个,那就必须要思考。作为预留,咱们这边应用能分包(Multirepo
)的扩大计划。
Monorepo vs Multirepo
选型
- 分包,
lerna
,业界闻名的Multirepo
计划解决方案 - 命令 + 子命令,
commander
、yargs
- 日志,
npmlog
、colors
- 命令行交互
- 网络申请
- 本地文件解决,
fs-extra
其余
- 版本比拟:
semver
- 解析参数选项:
minimist
- ……
<!– - 从
.env
文件中加载环境变量:dotenv
- npm 装置:
npminstall
- root 权限查看:root-check
- 获取用户主目录的门路:user-home
–>
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
外围包,输出lerna create core
,一路回车特地留神:package name: (core) @huamiao-cli/core
,这边须要蕴含组织名称@huamiao-cli
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
点击 Skip
当初能够在 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
这个组织外面
npmjs 偶然会不稳固,并且对于国内网络互联互通问题常常拜访较慢,如果失败,判断不是咱们的问题,能够从新公布,就是 learn 公布每次都会要求加个版本,这点有点不合理
注:这边老手会遇到各种问题,如有问题,能够在留言,老衲帮掘友解决。
强烈建议老手测试公布胜利后再进行开发。
3. 起码必要常识
3.1.learn
3.1.1. 初始化
- lerna init,初始化我的项目,创立
lerna.json
存储version
,并查看package.json
中devDependency
有无lerna
,没有则增加
3.1.2. 创立 package
-
lerna create,创立 package
以下 <> 为必须,[]为可选 Usage:lerna create <name> [loc] name:惟一、蕴含作用域名(本文为 @huamiao-cli,eg:@huamiao-cli/core)loc:包和包能够嵌套,抉择包的绝对地址,默认为第一个配置的包 地位
- lerna add,装置依赖,eg:
lerna add module-1 --scope=module-2
,scope
指定包,不指定会装置到每个包里,每次只能装置一个依赖
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}
`);
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 了
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
:
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 --debug
,minimist
默认反对
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
增加个简略的命令:
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 aaaa
、miao test --all bbbb
、miao diy
吧!
内容有点多,第一节先这样吧!
FAQ
-
依赖谬误或未知谬误,清理所有依赖
lerna clean
,重装依赖lerna bootstrap
Loadmap
- init【重磅】:抉择模板、自定义模板等
- 公布【重磅】:公布、git 操作、构建等
- 更多实现细节
参考
- [1] 什么是 CI/CD:https://linux.cn/article-9926…
- lerna doc:https://github.com/lerna/lerna
- CI/CD 是什么:https://www.redhat.com/zh/top…