前言
最近一直在捣鼓团队的脚手架开发,一个脚手架中往往包含多个库,所以就想着用monorepo来实现。当然程序员首要任务就是偷懒,所以顺便把自动化构建和发布都搞了,顺便水一篇文章(第一次发文章,有不足处多多讨论)
构建Monorepo项目
在管理monorepo
项目时,pnpm
的优势非常明显,就如官方文档介绍所说的,pnpm
是一个能够提高安装速度和节省磁盘空间的工具,能够更好的处理多个项目间的依赖关系,并且解决了很多令人诟病的问题
1. 安装pnpm
npm i pnpm -g
2. 创建仓库
mkdir monorepo && cd monorepo
pnpm init
git init
3. 添加工作区说明文件(在根目录创建pnpm-workspace.yaml
文件)
packages:
- 'packages/**'
4. 创建项目
mkdir packages && cd packages
<!-- 创建main项目 -->
mkdir main && cd main
pnpm init
修改项目package.json的name为: cx-monorepo-main
<!-- 创建utils项目 -->
mkdir ../utils && cd ../utils
pnpm init
修改项目package.json的name为: cx-monorepo-utils
5. pnpm怎么安装依赖
-
安装全局依赖
pnpm i typescript -w
-
安装局部依赖
pnpm i typescript --filter cx-monorepo-xxx 或者 cd packages/main && pnpm i typescript
-
安装项目内依赖
与安装局部依赖一致,包名为package的name。 安装后会产生一条
workspace
的依赖,在publish
之后会转变为真实路径依赖
6. 安装依赖
typescirpt
用于编译代码,@changesets/cli
用于包版本更新、changelog
生成、包的发布
pnpm i typescript @changesets/cli -w -D
7. 写点代码
-
utils
包新增文件
index.ts
// packages/utils/index.ts export function sum(n: number, m: number) { return n + m }
-
main
包安装
utils
依赖pnpm i cx-monorepo-utils --filter cx-monorepo-main
新增文件
index.ts
// packages/main/index.ts import { sum } from 'cx-monorepo-utils' sum(1, 2)
在
packages/utils/package.json
和packages/main/package.json
添加脚本{ "scripts": { "build": "tsc index.ts" } }
-
根目录
在
package.json
添加脚本,执行成功后,我们在gitlab
创建仓库,并将master
分支先推送上去{ "scripts": { "build": "pnpm --filter=./packages/* build" } }
8. changeset
-
初始化
changeset
<!--在根目录执行--> pnpm changeset init <!--编辑.changeset/config.json--> 将access选项改为public baseBranch选项改为master,并提交代码 <!--tips:以@开头的包默认是私有包,需要付费,如果包名是@开头又不想付费,命名规则就必须为@npm用户名/包名-->
-
提交变更记录
追加脚本并执行
{ "scripts": { "changeset": "changeset" } }
-
更新依赖版本并生成
changelog
追加脚本并执行
{ "scripts": { "update:version": "changeset version && pnpm i --frozen-lockfile=false" } }
-
发布到
npm
先将
npm
源切换到npm
,然后追加脚本并执行pnpm run publish
{ "scripts": { "publish": "pnpm build && pnpm release:only", "release:only": "changeset publish" } }
自动化发布
上文我们介绍了monorepo
的构建以及发布,接下来我们探索一下如何在CI/CD
中自动发布
1. NPM TOKEN
由于我们在之前执行publish
的时候还需要验证登录,但在自动化构建过程中如果还需要执行登录流程,那这个自动化程度显然并不高,那么我们要怎么跳过这个流程呢,答案就是token
-
创建
token
- 首先在
npm官网
登录,在用户菜单选择Access Tokens
- 选择
Classic Token
- 因为要用在自动化构建,需要避开双因素验证(2FA),所以选择
Automation
类型,创建完记得保存token
- 首先在
-
使用
token
-
我们创建
token
是为了在用于npm发包
的时候可以避开登录,这个token
我们可以配置在.nrmrc
文件中,对应的key是_authToken
-
编辑
.npmrc
,追加以下内容//registry.npmjs.org/:always-auth=true //registry.npmjs.org/:_authToken=刚刚保存的token
-
先执行
npm logout
,保证现在npm
是退出状态,然后执行publish
脚本,如果推送成功说明配置有效tips:如果之前使用了
token
发布,然后执行npm logout
,会导致token
失效
-
2. CI/CD
-
安装依赖
pnpm i changesets-gitlab -w -D
-
前置环境变量
- 由于使用
changesets-gitlab
插件,所以需要提供以下几个环境变量 GITLAB_HOST
:如果是私有化部署的话需要提供GITLAB_TOKEN
:gitlab账号token
GITLAB_CI_USER_NAME
:gitlab账号
- 由于使用
-
创建
gitlab token
- gitlab首页,在右上角用户信息,选择
Settings
- 选择
Access Tokens
,然后创建token
- gitlab首页,在右上角用户信息,选择
-
配置环境变量
- 进入
monorepo
项目仓库,选择Settings→ CI/CD
- 展开
Variables
- 添加以下变量
key
:GITLAB_CI_USER_NAME
value
: gitlab账号key
:GITLAB_HOST
value
: 因为我们是私有化的,所以填上https://gitlab.xxx.cn
key
:GITLAB_TOKEN
value
: 刚刚生成的gitlab token
key
:NPM_TOKEN
value
:npm
重新生成token
,刚刚生成的token
已经在本地使用过了。由于token
是比较私密的数据,而.nrmrc
是会提交到仓库的,所以我们需要使用变量,然后在在CI/CD
执行发布包之前再写入token
- 进入
-
将
.npmrc
文件以下内容删除//registry.npmjs.org/:always-auth=true //registry.npmjs.org/:_authToken=刚刚保存的token
-
在根目录新增
.gitlab-ci.yml
文件stages: - check:changeset - update:version - publish before_script: - npm config set registry https://registry.npmmirror.com/ - npm i pnpm -g - pnpm i 检查是否有执行changeset: stage: check:changeset image: node:16.1.0 script: - pnpm changesets-gitlab comment rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_COMMIT_REF_NAME != "changeset-release/master" 版本更新: stage: update:version image: node:16.1.0 script: - pnpm changesets-gitlab variables: INPUT_VERSION: pnpm run update:version INPUT_COMMIT: 'build: 版本更新' INPUT_TITLE: '版本更新' rules: - if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH && $CI_COMMIT_TITLE != "Merge branch 'changeset-release/master' into 'master'" when: manual 发布包: stage: publish image: node:16.1.0 script: - pnpm run build - npm config set registry https://registry.npmjs.org/ - echo -e "\n//registry.npmjs.org/:always-auth=true \n//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> .npmrc - pnpm changesets-gitlab variables: INPUT_PUBLISH: pnpm run publish rules: - if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH && $CI_COMMIT_TITLE == "Merge branch 'changeset-release/master' into 'master'" changes: - packages/**/package.json
结语
通过学习和阅读本文内容,我们实现了从零构建一个monorepo
项目,并通过changeset
进行版本管理和CHANGELOG
更新,同时也掌握了如何优雅的发布一个npm
包。但这些还是远远不够的,比如eslint配置
、commitlint
、lint-staged
等等都还需要去完善,gitlab-ci.yml
配置方面也是一笔带过,又或者某些包没有改动过,但是每次发布都执行打包命令,这些都值得大家去继续探索优化。
问题排查
-
执行publish时报错
npm adduser
- 如果是在本地终端报错,首先排查
npm
是否已经登录,随后排查npm
源地址是否是https://registry.npmjs.org/
- CI/CD环境报错,需要排查有没有创建
NPM_TOKEN
变量、NPM_TOKEN
是否正确、NPM_TOKEN
类型是否有避开2FA
验证、npm
源地址是否正确
- 如果是在本地终端报错,首先排查
-
执行
pnpm changesets-gitlab comment
报错Response code 401 (Unauthorized)
- 检查
GITLAB_CI_USER_NAME
、GITLAB_HOST
、GITLAB_TOKEN
是否有配置
- 检查
-
子仓库独立
eslint
,vscode
的eslint
插件报错- 添加
vscode
配置// .vscode/setting.json { "eslint.workingDirectories": [{ "mode": "auto" }] }
- 添加
-
使用私有化部署
npm
方案,无法创建npm token
-
由于无法创建
token
,并且CI/CD
中又无法输入账号密码,所以需要依赖npm-cli-login
帮我们在CI/CD
中完成登录的过程 -
修改
.gitlab.ci.yml
部分内容发布包: stage: publish image: node:16.1.0 script: - pnpm run build - NPM_RC_PATH=.npmrc NPM_REGISTRY=$NPM_REGISTRY NPM_USER=$NPM_USER NPM_PASS=$NPM_PASS NPM_EMAIL=$NPM_EMAIL pnpm dlx npm-cli-login - pnpm changesets-gitlab variables: INPUT_PUBLISH: pnpm run publish rules: - if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH && $CI_COMMIT_TITLE == "Merge branch 'changeset-release/master' into 'master'" when: manual changes: - packages/**/package.json
-
创建
gitlab
环境变量NPM_REGISTRY
: 私有化npm
地址,例如:http://npm.xxx.cn
NPM_USER
:私有化npm
用户名NPM_PASS
:密码NPM_EMAIL
:邮箱
-
文档
npm
授权相关配置: docs.npmjs.com/cli/v9/conf….gitlab-ci.yml
配置: docs.gitlab.com/ee/ci/yaml/changesets-gitlab
: www.npmjs.com/package/cha…npm-cli-login
:www.npmjs.com/package/npm…