Monorepo 最佳实践之 React 组件库搭建

Monorepo 概念

Monorepo(也称为单体存储库或单一存储库)是一种软件开发管理模式,其中多个相关的项目或组件被统一地放置在一个版本控制存储库中。

传统上,每个项目或组件都有自己独立的代码仓库和版本控制。但是,在使用 Monorepo 的方式下,多个项目或组件可以共享相同的代码库和版本管理。这意味着它们可以在同一个代码仓库中共享依赖、构建脚本、配置文件等,并且可以进行统一的版本控制、发布和协作。

Monorepo 的主要目的是为了简化多项目间的协作和管理,并提供更好的代码重用和一致性。一些主流的软件开发平台和框架,如Google、Facebook、Microsoft的一些开源项目等,都采用了 Monorepo 的开发模式。

Dumi

Dumi 是一个基于 React 和 Markdown 的文档工具,它旨在帮助开发者更轻松地编写和展示技术文档。Dumi 提供了一组丰富的功能和组件,用于创建漂亮、可交互和易于导航的文档网站。

Dumi 当前版本为2.0.2

Dumi 2.0特性

  • 更好的编译性能:通过结合使用 Umi 4 MFSUesbuildSWC、持久缓存等方案,带来比 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

  1. 全局安装 lerna

    $ npm install --global lerna
    
  2. 在项目根目录中初始化 Lerna

    $ lerna init
    

    使用 lerna 初始化之后,会在项目的根目录中多出一个 lerna.json 文件和 packages 目录

    并且会在 package.json 中添加这样一段内容:

    "workspaces": [
      "packages/*"
    ]
    

    因为我们使用了 pnpm,用到了 pnpm-workspace.yaml,所以这一段用不到了,要删掉

  3. 在 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 ,所以在构建组件时已经安装过 fatherfather 当前版本为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

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/
posted @ 2023-08-10 19:02  孤持的庄稼人  阅读(1344)  评论(0编辑  收藏  举报