我们接着上一篇《这里有从零开始构建现代化前端UI组件库所需要的一切(二)》继续,在这一篇文章里我们将主要为我们的组件库实现一套高效的版本管理和发布解决方案。
版本管理和发布方案
在Monorepo
项目中,版本管理和发布是一项关键的任务,特别是当项目包含多个独立的 package 时。以下是一些常见的Monorepo
项目中的版本管理和发布方案:
- Lerna :
- 版本管理: Lerna 是一个流行的 monorepo 管理工具,它可以帮助管理项目中的多个 package。通过 Lerna,你可以将每个 package 单独版本化,并在需要时进行升级。
- 发布: Lerna 支持将 package 一起发布,确保它们的版本号保持一致。此外,Lerna 可以集成到 CI/CD 流程中,自动触发发布流程。
- Changesets :
- 版本管理: Changesets 是一个语义版本控制工具,它通过智能检测项目中的变更类型,自动生成符合规范的语义版本号。可以与 Lerna 结合使用,提供更智能的版本控制。
- 发布: Changesets 支持自动化版本升级和发布,可以与 CI/CD 集成,使得版本发布流程更为自动化和可控。
- Rush Stack :
- 版本管理: Rush Stack 是一套用于管理 monorepo 项目的工具,包括 Rush 和 Pnpm。Rush 支持多 package 的版本管理,允许单独版本化每个 package。
- 发布: Rush 提供了版本发布的命令,可以一次性发布多个 package,并支持版本的批量升级。
- Semantic Release :
- 版本管理: Semantic Release 是一个自动化版本管理和发布工具,它基于提交信息智能决定版本号的升级。它适用于 monorepo 项目,可以与其他工具结合使用。
- 发布: Semantic Release 支持自动发布到各种代码托管平台,并能够触发 CI/CD 流程。
这里我们选择Changesets
,因为它提供了一种更智能、更易于使用的版本管理和发布方案。通过Changesets
,我们能够轻松地识别和管理项目中的变更,自动生成符合语义版本规范的版本号。它与Monorepo
构建的项目天然契合,支持多个 package 的独立版本化,确保每个模块都能保持一致的版本。Changesets
还可以与 CI/CD 系统集成,实现自动化的版本升级和发布流程,减轻了手动管理的负担。总体而言,Changesets
提供了一个简单而强大的解决方案,使得版本控制和发布变得更加可控和可预测。而且个人觉得还是Changesets
使用起来最简便。
还是老规矩,先将Changesets
集成到项目里来(后面会实际操作一下代码提交到发布的整个流程):
根据文档,我们在根目录下运行:
js
pnpm add @changesets/cli -D -w && pnpm exec changeset init
// 再安装两个 changesets 的插件
// 用于在 GitHub 上生成发布日志和获取发布计划。
pnpm add @changesets/changelog-github @changesets/get-release-plan @changesets/types -D -w
成功之后会在根目录下生成.changeset/
文件目录,格式如下:
lua
|-- .changeset
|-- config.json
|-- README.md
Changesets
的配置都在.changeset/config.json
文件中,只不过现在是自动生成的默认配置,我们需要更改一下,更改之后的.changeset/config.json
文件为:
json
{
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
"changelog": ["@changesets/changelog-github", { "repo": "1111mp/blankui" }],
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
"onlyUpdatePeerDependentsWhenOutOfRange": true
},
"ignore": ["@blankui-org/storybook"]
}
changelog
选项用于设置如何生成包的更改日志。如果是false
,则不会生成任何变更日志。使用@changesets/changelog-github
会将提交的链接添加到变更日志中,并向添加变更集的人添加一条感谢消息以及指向相关 PR 的链接,同时它需要 github 身份验证。
对于一些 Changesets 的功能,如发布到 GitHub 或使用 GitHub Actions 进行 CI/CD 集成,你可能需要设置 GitHub Token 令牌。GitHub Token 用于验证你对仓库的权限,以执行发布等敏感操作。
在使用 Changesets 时,你可以在 GitHub 上生成一个个人访问令牌(Personal Access Token),然后将该令牌配置到你的 CI/CD 环境中,或者在本地设置为环境变量。确保你的令牌具有足够的权限来执行所需的操作,比如发布新版本或更新发布日志。
在 GitHub 上生成 Personal Access Token 的步骤:
- 进入 GitHub 个人设置页面。
- 在左侧导航栏中选择 "Developer settings"。
- 在 "Access tokens" 部分,点击 "Generate token"。
- 根据需要选择相关权限(如 repo、workflow、write:packages 等)。
- 完成后生成令牌,并将其保存在安全的地方。
更多的配置说明还请查看官方文档。
那么我们在本地测试一下Changesets
的用法,不过为了使用方便,我们先在package.json
文件中添加几个新命令:
json
// ...
"scripts": {
// ...
"changeset": "changeset add",
"version": "changeset version",
"version:snapshot": "changeset version --snapshot dev",
"release": "changeset publish",
"release:snapshot": "changeset publish --snapshot --no-git-tag --tag dev"
},
// ...
然后运行pnpm changeset
(不添加命令也可以使用pnpm exec changeset add
直接运行),这时候终端会有交互提示:
shell
> blankui@1.0.0 changeset /Users/******/Documents/public/blankui
> changeset
🦋 Which packages would you like to include? ...
◯ unchanged packages
◯ @blankui-org/button
◯ @blankui-org/react
这里的提示让你选择本次添加的变更记录所涉及的包(它已经正确识别出了我们Monorepo
项目下的所有子包),这里我们全部都选择(上下按键切换,空格键选择
):
shell
> blankui@1.0.0 changeset /Users/******/Documents/public/blankui
> changeset
🦋 Which packages would you like to include? · @blankui-org/button, @blankui-org/react
🦋 Which packages should have a major bump? ...
◯ all packages
◯ @blankui-org/button@1.0.0
◯ @blankui-org/react@1.0.0
Changesets
中的变更日志有三个级别,分别是:major
| minor
| patch
(主要|轻微|修补),具体选择哪个级别其实就是根据版本更新的内容来决定,比如这是我们第一个版本,将要发布两个全新的包出来,那么我们肯定选择major
,但如果是一些组件用法上的变更,那么我们选择minor
就行,bug修复的话选择patch
就好。但是需要注意的是,级别不同也会影响到后续通过changeset version
命令更新包的版本的版本号,简单来说就是:一般我们的版本号是三位的(比如1.0.0
),如果我们选择的级别是major
,那么这时候重新生成的版本号就是2.0.0
,同理我们选择patch
那么新的版本号就是1.0.1
。
那么我们选上所有的包并选择major
继续(还是上下按键切换,空格键选择
,这里注意:什么都不选的话按回车才会切换major
| minor
| patch
不同的级别):
shell
> blankui@1.0.0 changeset /Users/******/Documents/public/blankui
> changeset
🦋 Which packages would you like to include? · @blankui-org/button, @blankui-org/react
🦋 Which packages should have a major bump? · @blankui-org/button, @blankui-org/react
🦋 Please enter a summary for this change (this will be in the changelogs).
🦋 (submit empty line to open external editor)
🦋 Summary ›
这里需要我们为这次变更记录添加摘要,那么我们输入feat: blankui first version
:
shell
> blankui@1.0.0 changeset /Users/******/Documents/public/blankui
> changeset
🦋 Which packages would you like to include? · @blankui-org/button, @blankui-org/react
🦋 Which packages should have a major bump? · @blankui-org/button, @blankui-org/react
🦋 Please enter a summary for this change (this will be in the changelogs).
🦋 (submit empty line to open external editor)
🦋 Summary › feat: blankui first version
然后回车,选择是即可:
shell
> blankui@1.0.0 changeset /Users/******/Documents/public/blankui
> changeset
🦋 Which packages would you like to include? · @blankui-org/button, @blankui-org/react
🦋 Which packages should have a major bump? · @blankui-org/button, @blankui-org/react
🦋 Please enter a summary for this change (this will be in the changelogs).
🦋 (submit empty line to open external editor)
🦋 Summary · feat: blankui first version
🦋
🦋 === Summary of changesets ===
🦋 major: @blankui-org/button, @blankui-org/react
🦋
🦋 Note: All dependents of these packages that will be incompatible with
🦋 the new version will be patch bumped when this changeset is applied.
🦋
🦋 Is this your desired changeset? (Y/n) · true
🦋 Changeset added! - you can now commit it
🦋
🦋 warn This Changeset includes a major change and we STRONGLY recommend adding more information to the changeset:
🦋 warn WHAT the breaking change is
🦋 warn WHY the change was made
🦋 warn HOW a consumer should update their code
🦋 info /Users/******/Documents/public/blankui/.changeset/fluffy-rockets-chew.md
这时候在.changeset/
目录下会多出个fluffy-rockets-chew.md
文件:
md
---
"@blankui-org/button": major
"@blankui-org/react": major
---
feat: blankui first version
这个文件保存着此次变更记录的一些关键信息,当然在这个文件被消耗
之前内容是可以自定义修改的(包括文件名)。这里的变更记录是可以被多次创建并被累积下来的,直到我们准备发出一个新版本,然后这些变更记录都会被这个版本消耗
掉。
那么我们开始准备发布我们的第一个版本,运行pnpm run version
命令,这时候你会发现:
-
.changeset/fluffy-rockets-chew.md
文件消失,被消费
了 -
pakcages/components/button/
目录下多出了CHANGELOG.md
文件,并且button
的package.json
文件中的版本号被更改成了:2.0.0
md# @blankui-org/button ## 2.0.0 ### Major Changes - feat: blankui first version
-
pakcages/core/react/
目录下多出了CHANGELOG.md
文件,并且其package.json
文件中的版本号也被更改成了:2.0.0
md# @blankui-org/react ## 2.0.0 ### Major Changes - feat: blankui first version ### Patch Changes - Updated dependencies []: - @blankui-org/button@2.0.0
那么这时候我们运行pnpm release
(发布前不要忘记运行pnpm build
提前打包),那么就开始正式包的发布流程了(其实就是npm的发布流程,提前先npm login
,如果开启了动态验证码,需要pnpm release --otp={code}
)。不过这时候还是会报错:
shell
an error occurred while publishing @blankui-org/button: E402 402 Payment Required - PUT https://registry.npmjs.org/@blankui-org%2fbutton - You must sign up for private packages
只需要在各个包的package.json
文件中加上:
json
// ...
"publishConfig": {
"access": "public"
},
// ...
packages/storybook/package.json
还需要加上(才能够被忽略):
json
// ...
"private": true,
// ...
距离发布成功其实还差一步,你们应该还会报错:
shell
an error occurred while publishing @blankui-org/button: E404 Not Found - PUT https://registry.npmjs.org/@blankui-org%2fbutton - Scope not found
这个报错是因为我们需要提前在 npm
中创建 @blankui-org
的组织,但是这个组织名已经不能注册了,你们可以根据自己的实际情况创建一个组织,比如@blankuis-org
,当作临时测试,这时候发布就会成功了:
然后本地也会生成这几个分支:
这时候可以手动推送到远程,并在github
上手动发布release
。但是如果你需要这一切都自动进行,则使用pnpm version:snapshot
& pnpm release:snapshot
命令即可。详情请参阅官方的文档:snapshot-releases,这里就不赘述了。
此时我们的版本管理和发布方案的实现就差不多了。但是,其实有经验的朋友这时候肯定发现了一个问题,包@blankui-org/button
&@blankui-org/react
的package.json
有问题("main": "src/index.ts"
),应该是dist/index.js
才对。别担心,下一节我们将解决这个问题。
到这里为止的源代码:commit 7e841f6
发布前自动更改package.json
文件
之所以我们的包package.json
文件的main
字段需要设置成src/index.ts
,是因为我们需要在本地的开发环境(@blankui-org/storybook
)中实时的展示和测试我们的组件,但是在实际包发布的时候又得改成正确的配置,看似"鱼与熊掌不可兼得"的场景(不可能有人通过手动的方式在发布前一个一个改吧...),但其实是有解
的:那就是让这一切自动化起来。
这一点我们可以借助 clean-package 来实现。
This clean-package tool is used for removing development configuration from 'package.json' before publishing the package to NPM.
clean-package
可以根据相关配置让我们在发布前对package.json
做一些额外的处理工作。那么根据文档我们开始将其集成到我们的项目中来。
-
安装
pnpm add clean-package -D -w
-
分别编辑
@blankui-org/button
&@blankui-org/react
的package.json
文件:json// ... "scripts": { // ... "prepack": "clean-package", "postpack": "clean-package restore" // ... } // ... "clean-package": "../../../clean-package.config.json" // ...
-
根目录下创建
clean-package.config.json
文件:json{ "remove": ["devDependencies"], "replace": { "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts", "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.mjs", "require": "./dist/index.js" }, "./package.json": "./package.json" } } }
这个配置文件应该很好懂吧,见名知意;
-
.gitignore
忽略*.backup
文件:md# ... # clean-package packages/**/*.backup packages/**/*.backup.ts # ...
到这里clean-package
的相关配置就完成了,接下来我们使用changesets
提交并发布一个正式版本修复这个问题:
-
pnpm changeset
:shell> blankui@1.0.0 changeset /Users/******/Documents/public/blankui > changeset add 🦋 Which packages would you like to include? · @blankui-org/button, @blankui-org/react 🦋 Which packages should have a major bump? · No items were selected 🦋 Which packages should have a minor bump? · No items were selected 🦋 The following packages will be patch bumped: 🦋 @blankui-org/button@2.0.0 🦋 @blankui-org/react@2.0.0 🦋 Please enter a summary for this change (this will be in the changelogs). 🦋 (submit empty line to open external editor) 🦋 Summary · fix: package.json 🦋 🦋 === Summary of changesets === 🦋 patch: @blankui-org/button, @blankui-org/react 🦋 🦋 Note: All dependents of these packages that will be incompatible with 🦋 the new version will be patch bumped when this changeset is applied. 🦋 🦋 Is this your desired changeset? (Y/n) · true 🦋 Changeset added! - you can now commit it 🦋 🦋 If you want to modify or expand on the changeset summary, you can find it here 🦋 info /Users/******/Documents/public/blankui/.changeset/smooth-cars-behave.md
-
pnpm run version
-
pnpm release
发布成功之后可以看到这个问题已经被修复了:
到这里为止的源代码:commit e4c2ff1
我们已经为了我们的组件库实现了一套高效的版本管理和发布解决方案,当然这个过程中如果大家遇到了其它任何问题都可以随时给我留言,有一些其它方案或者补充的也非常欢迎,那么这篇文章就到这里结束啦,下篇见~
接下来的方向主要是这两个方面:
- 为我们的组件库搭建一个网站子项目,能够高效的为我们的组件提供对应的文档及其在线运行实例等
- 聚焦组件开发本身,实现在组件开发中所需要考虑的问题,比如:统一配置和扩展的主题、一些可以共享复用的功能代码等