前言
最近一直在捣鼓团队的脚手架开发,一个脚手架中往往包含多个库,所以就想着用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账号tokenGITLAB_CI_USER_NAME:gitlab账号
- 由于使用
-
创建
gitlab token- gitlab首页,在右上角用户信息,选择
Settings - 选择
Access Tokens,然后创建token
- gitlab首页,在右上角用户信息,选择
-
配置环境变量
- 进入
monorepo项目仓库,选择Settings→ CI/CD - 展开
Variables - 添加以下变量
key:GITLAB_CI_USER_NAMEvalue: gitlab账号key:GITLAB_HOSTvalue: 因为我们是私有化的,所以填上https://gitlab.xxx.cnkey:GITLAB_TOKENvalue: 刚刚生成的gitlab tokenkey:NPM_TOKENvalue: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.cnNPM_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…