Monorepo 最佳实践之 React 组件库搭建
Monorepo 概念
Monorepo(也称为单体存储库或单一存储库)是一种软件开发管理模式,其中多个相关的项目或组件被统一地放置在一个版本控制存储库中。
传统上,每个项目或组件都有自己独立的代码仓库和版本控制。但是,在使用 Monorepo 的方式下,多个项目或组件可以共享相同的代码库和版本管理。这意味着它们可以在同一个代码仓库中共享依赖、构建脚本、配置文件等,并且可以进行统一的版本控制、发布和协作。
Monorepo 的主要目的是为了简化多项目间的协作和管理,并提供更好的代码重用和一致性。一些主流的软件开发平台和框架,如Google、Facebook、Microsoft的一些开源项目等,都采用了 Monorepo 的开发模式。
Dumi
Dumi 是一个基于 React 和 Markdown 的文档工具,它旨在帮助开发者更轻松地编写和展示技术文档。Dumi 提供了一组丰富的功能和组件,用于创建漂亮、可交互和易于导航的文档网站。
Dumi 当前版本为2.0.2
Dumi 2.0特性:
- 更好的编译性能:通过结合使用
Umi 4 MFSU
、esbuild
、SWC
、持久缓存等方案,带来比dumi 1.x
更快的编译速度- 内置全文搜索:不需要接入任何三方服务,标题、正文、
demo
等内容均可被搜索,支持多关键词搜索,且不会带来产物体积的增加- 全新主题系统:为主题包增加插件、国际化等能力的支持,且参考
Docusaurus
为主题用户提供局部覆盖能力,更强更易用- 约定式路由增强:通过拆分路由概念、简化路由配置等方式,让路由生成一改
dumi 1.x
的怪异、繁琐,更加符合直觉- 资产元数据 2.0:在 1.x 及
JSON Schema
的基础上对资产属性定义结构进行全新设计,为资产的流通提供更多可能- 继续为组件研发而生:提供与全新的 NPM 包研发工具
father 4
集成的脚手架,为开发者提供一站式的研发体验
脚手架
# 建空目录
$ mkdir my-ui && cd my-ui
# 通过官方工具创建项目,选择你需要的模板
$ npx create-dumi
# 选择一个模板
$ ? Pick template type › - Use arrow-keys. Return to submit.
$ Static Site # 用于构建网站
$ ❯ React Library # 用于构建组件库,有组件例子
$ Theme Package # 主题包开发脚手架,用于开发主题包
# 选择 npm 包管理器
$ ? Pick NPM client › - Use arrow-keys. Return to submit.
$ npm
$ cnpm
$ tnpm
$ yarn
$ ❯ pnpm
# 选择 npm 包名
$ ? Input NPM package name › my-ui
# 全局安装 pnpm
$ npm install -g pnpm
# 启动项目
$ pnpm start
配置 pnpm
pnpm 是一个面向 JavaScript 项目的包管理器,与 npm 和 Yarn 类似。它旨在改进包依赖的安装和管理效率
pnpm 需要额外配置一些内容,在组件库根目录下创建 pnpm-workspace.yaml
文件,配置内容参考
packages:
- "packages/*"
用 Lerna 管理组件库
Lerna 是一个用于管理基于 Monorepo 结构的 JavaScript 项目的工具。它提供了一组命令和功能,简化了在单个代码仓库中管理多个相关项目或软件包的过程。
Lerna 当前使用版本为6.6.1
-
全局安装
lerna
$ npm install --global lerna
-
在项目根目录中初始化 Lerna
$ lerna init
使用
lerna
初始化之后,会在项目的根目录中多出一个lerna.json
文件和packages
目录并且会在
package.json
中添加这样一段内容:"workspaces": [ "packages/*" ]
因为我们使用了
pnpm
,用到了pnpm-workspace.yaml
,所以这一段用不到了,要删掉 -
在 packages 目录中创建子包
# 创建一个test1组件 $ lerna create @my-ui/test1 # 创建一个test2组件 $ lerna create @my-ui/test2
lerna
会帮我们创建两个组件包,这里只留package.json
就行了,其余的可以删掉,然后在两个组件目录下创建src
目录,在下面创建index.ts
文件import React from 'react'; const index: React.FC = () => { return ( <div>this is test1</div> ) } export default index;
最后,在根目录的 package.json
下配置组件私有(避免误发布)
{
"private": true,
}
配置 Turbo
全局安装 turbo
,官网地址
$ npm install turbo --global
然后添加 turbo.json
到根目录
{
"$schema": "https://turborepo.org/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
}
}
}
然后到根目录下的 package.json
,配置下面的内容
"packageManager": "pnpm@8.1.1" // 这里写你安装的 pnpm 版本
配置 Father
由于 dumi
集成了 father
,所以在构建组件时已经安装过 father
,father
当前版本为4.1.0。
在每一个组件目录的 package.json
中配置内容
"scripts": {
"build": "father build"
},
然后每个组件目录下面都要配置 .father.ts
,可以参考 umi
的方法,在根目录写一个 .fatherrc.base.ts
,然后组件目录下用 extends
加载配置(仅限father4
)
//.fatherrc.base.ts
import { defineConfig } from 'father';
export default defineConfig({
cjs: {
output: 'lib',
},
esm: {
output: 'es',
},
umd: {
output: 'dist'
}
});
组件目录下的 .fatherrc.ts
// packages/test1/.fatherrc.ts
import { defineConfig } from 'father';
export default defineConfig({
extends: '../../.fatherrc.base.ts',
});
开始打包
执行
$ pnpm turbo build
// 或者 npx turbo build
发包方式
Lerna
执行
$ lerna publish --registry http://xxx:4873/
抛出错误
lerna notice cli v6.6.1 lerna info current version 0.0.0 lerna ERR! ENOCOMMIT No commits in this repository. Please commit something before using version.
解决:提示当前仓库在发布之前需要提交到
git
,创建git
仓库并提交
再次执行:$ lerna publish --registry http://xxx:4873/ lerna notice cli v6.6.1 lerna info current version 0.0.0 lerna info Assuming all packages changed ? Select a new version (currently 0.0.0) Patch (0.0.1) Changes: - @my-ui/test1: 0.0.0 => 0.0.1 ? Are you sure you want to publish these packages? Yes
发布成功,在私服上可以看到该 npm
包
稍作修改,需要提交后才可发布,提交到 git
时,会有提示格式不规范,请按照代码提交规范:
- build:构建工具或者外部依赖包的修改,比如更新依赖包的版本
- chore:构建过程和辅助工具的变动注释
- ci:持续集成的配置文件或者脚本的修改
- docs:只改动了文档相关的内容
- feat:增加新功能
- fix: 修复 bug
- perf:提高性能的修改
- refactor:重构 (既不是新增功能,也不是修改 bug 的代码变动)
- revert:撤销某次提交
- style:格式(不影响代码运行的变动)
- test:增加测试
修改后再发包,会报错
lerna notice cli v6.6.1 lerna info current version 0.0.2 lerna notice Current HEAD is already released, skipping change detection.
官方给出的解决方案:
添加参数
from-package
执行命令如下:
$ lerna publish from-package --registry http://xxx:4873/
lerna notice cli v6.6.1
lerna WARN Unable to determine published version, assuming "@my-ui/test2" unpublished.Found 2 packages to publish:
- @my-ui/test1 => 0.0.2
- @my-ui/test2 => 0.0.2
Changesets
在根目录安装 changeset cli
工具并添加 changeset
参考文档
# 安装@changesets/cli
$ pnpm add @changesets/cli -w && npx changeset init
# 添加 `changeset`
$ npx changeset
🦋 Which packages would you like to include? · @my-ui/test1, @my-ui/test2
🦋 Which packages should have a major bump? …
◉ all packages
◉ @my-ui/test1@0.0.2
◉ @my-ui/test2@0.0.2
🦋 Are you sure you want to release the first major version of @my-ui/test1? (Y/n) › true
🦋 Please enter a summary for this change (this will be in the changelogs).
🦋 (submit empty line to open external editor)
🦋 Summary · 测试更改日志
🦋
🦋 === Summary of changesets ===
🦋 major: @my-ui/test1, @my-ui/test2
🦋
🦋 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/xxx/work/my-ui/.changeset/eleven-dodos-hang.md
版本管理与发布
可以运行以下命令更新日志条目:
$ npx changeset version
然后发布软件包:
$ npx changeset publish
这里需要发布到私服,所以在 package.json
中配置,私服地址:
"publishConfig": {
"access": "public",
"registry": "http://xxx:4873/"
}
如果某个包版本没有改动,自动忽略不更新,只更新有版本变化的包。
以上命令,综合在 package.json
中添加一个publish-packages脚本:
{
"scripts": {
"publish-packages": "turbo run build && changeset version && changeset publish"
}
}
小结
在尝试了 lerna 和 changeset 后,我决定使用 changeset 取而代之,不再使用 lerna 作为您的包管理器。为了实现 Monorepo 包管理方式,我决定删除 lerna.json 配置文件,并用 pnpm 的 workspace 功能来代替。
通过使用 pnpm 的 workspace,可以将多个相关项目或软件包收集到一个 Monorepo 中,并使用一个统一的依赖管理机制进行管理。pnpm 的 workspace 提供了一些命令和功能,方便对 Monorepo 进行版本控制、构建和测试等操作
目录结构
组件库采用多包架构搭建,目录结构整理为如下所示:
├── docs # 组件库文档目录
│ ├── index.md # 组件库文档首页
│ ├── guide # 组件库其他文档路由表(示意)
│ │ ├── index.md
│ │ └── help.md
├── packages # 子包集目录
│ ├── test1 # 子包test1
│ ├── test2 # 子包test2
│ └── typings.d.ts
├── .dumirc.ts # dumi 配置文件
├── pnpm-lock.yaml # pnpm 包管理器生成的锁文件
├── package.json # Node.js 项目的元数据文件
├── turbo.json # Turbo 配置文件
├── .fatherrc.base.ts # father-build 的子包统一基础配置文件
└── .fatherrc.ts # father-build 的配置文件,用于组件库打包
子包目录结构
├── .turbo # turbo 缓存目录
│ └── turbo-build.log # 构建日志
├── src # 组件库源码目录
│ ├── Button # 单个组件
│ │ ├── index.tsx # 组件源码
│ │ ├── index.less # 组件样式
│ │ └── index.md # 组件文档
│ ├── index.md # 组件库入口文档
│ └── index.ts # 组件库入口文件
├── CHANGELOG.md # 版本日志
├── package.json # package 文件
├── README.md # readme 文件
└── .fatherrc.ts # father-build 的配置文件,用于组件库打包
子包的 package.json
{
"name": "@my-ui/test1",
"version": "0.0.1",
"description": "xxx组件库",
"repository": {
"type": "git",
"url": "git+https://xxx/my-ui.git"
},
"module": "es/index.js", // 指向支持 ESM 模块的入口文件
"types": "lib/index.d.ts", // 指向Type 的入口文件
"directories": {
"lib": "lib",
"test": "__tests__"
},
"files": [ // 保留的文件集
"dist",
"lib",
"es"
],
"scripts": {
"build": "father build"
},
"publishConfig": { // 发包配置
"access": "public",
"registry": "http://xxx:4873/" // 私服地址
}
}
命令
安装到根目录需要加参数
$ pnpm add @changesets/cli -w
删除软件包,npm unpublish <包名>@版本号 --force
:
$ pnpm unpublish my-ui --registry http://xxx:4873/ --force
Link
pnpm 支持 workspace 协议,需要把依赖改为这样的形式,然后安装依赖:
"dependencies": {
"@babel/runtime": "^7.21.0",
"@changesets/cli": "^2.26.1",
"@my-ui/test1": "workspace:*",
"@my-ui/test2": "workspace:*"
},
执行
$ pnpm install
pnpm workspace
,能达到在 npm install
的时候自动 link
的目的。
项目命令
# 加载依赖
$ pnpm install
# 启动组件库文档的demo
$ pnpm start
# 安装到根目录需要加参数
$ pnpm add <包名>@版本号 -w
# 构建组件库
$ pnpm run build-packages
# 发布组件库
$ pnpm run publish-package
# 删除软件包,npm unpublish <包名>@版本号 --force:
# 删除的版本24小时后方可重发!
# 只有发布72小时之内的包可以删除!
$ pnpm unpublish <包名>@版本号 --registry http://xxx:5873/ --force
# 删除软件包,没有时间限制
$ pnpm --force unpublish <包名>@版本号 --registry http://xxx:5873/
本文来自博客园,作者:孤持的庄稼人,转载请注明原文链接:https://www.cnblogs.com/papillon/articles/17620595.html