Biosan 前端公共资源库:基于 React + TypeScript + Antd 的组件库 Monorepo,统一管理 UI 组件、Hooks、工具函数与消息文案,并通过 dumi 自动生成在线文档。
本文档基于 2026-08 完成的一次大规模升级编写(工具链 dumi 2 / father 4 / lerna 8,运行时 React 18 + Antd 4.24)。
| 类别 | 选型 |
|---|---|
| 框架 | React 18(^18.3.1) |
| UI 库 | Ant Design 4(^4.24.16,保持 4.x 稳定线) |
| 语言 | TypeScript |
| Monorepo | Lerna 8(Nx 驱动)+ npm workspaces |
| 组件/库构建 | father 4(bundless + dts 生成) |
| 文档站 | dumi 2(.dumirc.ts 配置) |
| 代码规范 | ESLint(@umijs/fabric)+ Prettier + commitlint + lint-staged |
| 提交规范 | commitizen + cz-customizable |
| 工具 | 版本 | 说明 |
|---|---|---|
| Node.js | >=24 <25 |
必须 Node 24 LTS(当前推荐 24.19.0) |
| npm | >=11 <12 |
Node 24 自带 npm 11 |
版本由 package.json 的 engines 字段 + .npmrc 的 engine-strict=true 双重强制,不满足会直接报错:
npm error notsup Required: {"node":">=24 <25","npm":">=11 <12"}
推荐用 nvm 管理:
nvm install 24
nvm use 24
node -v # v24.x# 1. 安装依赖(npm workspaces 会同时安装 4 个子包的依赖并建立本地软链)
npm install
# 2. 构建全部子包(lerna 编排,自动按依赖顺序:utils → components/hooks/msgs)
npm run build
# 3. 启动文档站(本地预览,默认 http://localhost:8000)
npm start💡 若执行
npm install时遇到 safe-delete 相关报错(WorkBuddy 等工具注入的删除保护),可临时禁用保护后执行:env -u BASH_ENV -u CODEBUDDY_SAFE_DELETE_BIN_DIR -u CODEBUDDY_SAFE_DELETE_BULK_STATE_DIR npm install
frontend/
├── package.json # 根配置:workspaces、engines、scripts
├── lerna.json # Lerna 8 配置(nx 构建编排)
├── .npmrc # engine-strict / legacy-peer-deps
├── .dumirc.ts # dumi 2 文档站配置(含子包 alias)
├── docs/ # 文档站源码(首页、指南、全局样式)
│ ├── index.md # 首页(hero + features 支持 link 跳转)
│ ├── guide/ # 指南:快速上手 / 代码规范 / 组件开发 / 版本管理
│ └── styles/global.css # 全局样式(苹果风 + 响应式,经 .dumirc.ts 内联注入)
├── public/ # 静态资源(feature 背景图等,构建时拷贝到产物根)
├── packages/ # 子包(npm workspaces)
│ ├── components/ # @b1/components UI 组件
│ │ └── src/ # 组件源码 + index.md 文档(ColorPicker 等)
│ ├── hooks/ # @b1/hooks React Hooks
│ ├── msgs/ # @b1/msgs 提示用语文案
│ └── utils/ # @b1/utils 通用工具类
├── docs-dist/ # 文档站构建产物(gh-pages 部署)
└── .workbuddy/ # WorkBuddy 工作区数据(勿删,已 gitignore)
每个子包内部结构(以 utils 为例):
packages/utils/
├── package.json # name/version/main/module/scripts
├── .fatherrc.ts # father 4 构建配置(输出 lib/es)
├── tsconfig.json # TS 编译配置
└── src/ # 源码(目录即文档路由)
├── index.ts # 入口
└── common/format/ # 功能模块
├── index.ts
└── index.md # dumi 文档(frontmatter 控制路由)
| 子包 | npm 包名 | 版本 | 说明 | 构建产物 |
|---|---|---|---|---|
| components | @b1/components |
1.1.28 | 通用公共组件(DragModal、ColorPicker、UnifiedLogin、SearchBar、SortDrag、ImageSecurity 等) | lib/ + es/ |
| hooks | @b1/hooks |
1.1.26 | 通用 React Hooks(debounce 等) | lib/ + es/ |
| msgs | @b1/msgs |
1.1.24 | 通用项目提示用语(文案常量) | lib/ + es/ |
| utils | @b1/utils |
1.1.25 | 通用工具类(format、deepClone、jsEncrypt 等) | lib/ + es/ |
包间依赖:components、hooks 依赖 @b1/utils(通过 dependencies 声明,workspaces 软链本地源码)。构建时 lerna 按 dependsOn: ^build 保证 utils 先构建。
@b1/components 内置功能强大的颜色选择器 ColorPicker(零第三方依赖,颜色转换全手写):
- HSV 取色面板:饱和度 × 亮度矩形 + 色相条,鼠标拖动取色(纯 CSS 渐变实现)
- 预设色板(默认 36 色,可自定义)+ 最近使用(自动记录最近 8 个)
- 透明度调节 + HEX / RGB / HSL 三格式输入切换 + 一键复制
- 受控 / 非受控、
allowClear(清除回传 null)、disabled、size、panelWidth等 onChange统一返回{ hex, rgb, hsl, alpha, rgba }完整对象
使用示例:
import { ColorPicker } from '@b1/components';
<ColorPicker
value={color}
onChange={c => c && setColor(c.hex)}
presets={['#f5222d', '#52c41a', '#1677ff']}
showFormat="hex"
/>;npm start # 启动 dumi 文档站(热更新)
npm run docs:build # 构建文档站到 docs-dist/
npm run docs:deploy # 部署到 gh-pages(docs-dist 内容)
npm run deploy # docs:build + docs:deploynpm run build # 构建全部子包(lerna run build,按依赖顺序)
cd packages/utils && npx father build # 构建单个子包
npm run clean # 清理所有子包的 node_modules子包 build 脚本为
father build --no-clean:跳过输出目录清理(增量覆盖),可避免 CI/工具链中 rimraf 被删除保护拦截,同时提升重复构建速度。
npm test # 运行测试(umi-test)
npm run test:coverage # 测试覆盖率
npm run lint # ESLint 检查 packages/
npm run prettier # 格式化全部代码npm run release # 构建 + 发布全部子包到 npm(--access public)
npm run publish # lerna publish(打 tag + 发布变更包)
npx lerna version # 仅升级版本号 + 打 git tag
lerna.json中version: "independent":各子包独立版本号;command.version.exact: true:子包间依赖使用精确版本。
文档采用 dumi 2 约定式路由:在子包 src/ 下写 index.md 即为该模块的文档页。
demo 直达源码(无需先构建):.dumirc.ts 的 alias 把 @b1/components、@b1/hooks、@b1/utils、@b1/msgs 指向各自 src/,文档里写 import { X } from '@b1/components' 会直接编译源码,新增/修改组件后刷新即可在文档站预览。
首页(docs/index.md):hero + features 布局,feature 支持:
link字段:点击卡片跳转对应页面(整卡可点由global.css的 stretched-link 实现)- 半透明背景图(
public/下的 WebP,每张卡片不同,0.2 透明度水印效果) - 响应式:平板 2 列、手机单列(
global.css媒体查询)
frontmatter 控制页面归属(旧版 nav.path 写法已失效):
---
title: format
nav:
path: /utils # 顶部导航(需与 .dumirc.ts 的 themeConfig.nav 对应)
group:
title: common # 侧边栏分组
path: /utils/common
---
# format 格式化
```js
import { formatFloat } from '@b1/utils';
console.log(formatFloat(1)); // 1.000
```
**代码块即演示**:带语言的代码块(`js`/`tsx`)会自动渲染为"示例 + 实时运行 + 可复制"的卡片,无需额外标记。
**组件 API 表格**(组件库文档用 mdx):
```mdx
<code src="./demos/basic.tsx" />
## API
<API id="Button" />
<API id="Button" /> 会自动从组件源码的 TS 类型定义提取 props 生成表格,注释即文档。
⚠️ 文档配置变更点(dumi 1 → 2):
- 配置文件:
.umirc.ts→.dumirc.tsnavs→themeConfig.nav(字段path→link)resolve.includes→resolve.docDirs+resolve.atomDirslocales:[['zh-CN','中文']]→[{ id: 'zh-CN', name: '中文' }]favicon→favicons(复数,数组)
| 文件 | 作用 |
|---|---|
package.json |
workspaces: ["packages/*"] 声明子包;engines 强制 Node 24 / npm 11 |
.npmrc |
engine-strict=true 强制版本校验;legacy-peer-deps=true 跳过老包(react-captcha-code 等)的 peer 严格检查 |
lerna.json |
useNx: true 启用 Nx 编排;nx.targets.build.dependsOn: ["^build"] 保证子包按依赖顺序构建 |
.dumirc.ts |
dumi 2 文档站:title/logo/nav/atomDirs/extraBabelPlugins;alias 用 $ 精确匹配让文档 demo 直接解析子包源码('@b1/components$': packages/components/src),改组件无需构建即可在文档站生效 |
public/ |
静态资源目录(feature 背景图等),构建时拷贝到产物根路径,CSS 用绝对路径 /xxx.webp 引用 |
子包 .fatherrc.ts |
father 4 构建:cjs 输出 lib/、esm 输出 es/、antd babel-plugin-import |
子包 tsconfig.json |
TS 编译配置(4 个子包均已建) |
💡 alias 精确匹配的原因:
@b1/utils若用前缀匹配会连 less 里的~@b1/utils/es/styles/tokens.less一起替换成不存在的src/es/...导致编译失败;$结尾只替换 JS 包名导入,带子路径的 less 导入仍走 node_modules 软链产物。
Q1:npm install 报 notsup Required: {"node":">=24 <25"}?
当前 Node 版本不对,用 nvm use 24 切到 Node 24。
Q2:npm install 报 peer 依赖冲突(react-captcha-code 等)?
已通过 .npmrc 的 legacy-peer-deps=true 处理。若在 CI 中报错,确认 CI 也读取了项目 .npmrc。
Q3:lerna bootstrap 不存在?
Lerna 8 已移除 bootstrap 命令(v7 起废弃),依赖管理改用 npm workspaces,直接 npm install 即可。
Q4:构建时提示 Cannot find module '@rollup/pluginutils' / esbuild 加载失败?
老锁文件导致的依赖缺失,删除 package-lock.json 与 node_modules 后重新 npm install(勿用历史遗留的 yarn.lock)。
Q5:文档站构建报 Invalid config keys: favicon/navs?
这是 dumi 1 的配置写法,请按上方"文档配置变更点"迁移到 dumi 2 格式(.dumirc.ts)。
Q6:father build 报 Declaration generation failed + TS 类型错误?
father 4 的 dts 生成比 father-build 1.x 严格,多为 React 18 类型差异:
useRef<T>()→useRef<T | null>(null)navigator.msSaveBlob(IE API)→ 类型断言- props 缺少
children→ 在 interface 中补充children?: React.ReactNode
Q7:文档站 dev 报 Element type is invalid ... got undefined?
文档 demo 里 import { ColorPicker } from '@b1/components' 解析到了 lib/es 旧构建产物(新组件未构建)。已通过 .dumirc.ts 的 alias($ 精确匹配)让 demo 直达源码解决;若仍出现,检查是否重启过 dev server。
Q8:改了 global.css / .dumirc.ts 但文档站没变化?
global.css 是在 dev server 启动时被 .dumirc.ts 读取内联的,修改后必须重启 npm start 才能生效(普通组件代码修改则热更新即时生效)。
2026-08 · 大规模升级(Node 24 兼容)
| 项 | 旧 | 新 | 说明 |
|---|---|---|---|
| dumi | 1.0.10 | 2.4.48 | 配置迁移至 .dumirc.ts |
| father-build | 1.17.2 | father 4.6.35 | 包名变更 + 子包独立配置 |
| lerna | 3.22.1 | 8.2.4 | bootstrap 移除,改用 workspaces |
| react / react-dom | 16.12 | 18.3.1 | 修复多处严格类型 |
| antd | 4.16.1 | 4.24.16 | 保持 4.x 稳定线(未升 5/6) |
| prettier | 1.19.1 | 3.9.6 | — |
| lint-staged | 10.0.7 | 17.3.0 | — |
| gh-pages | 3.0.0 | 6.3.0 | — |
源码修复:DynamicGrid 大小写路径、DragModal children 类型、ImageSecurity/UnifiedLogin useRef、utils/download/excel IE API 类型、dynamicGrid 嵌套数组类型。
2026-08-12 · 体验升级
| 项 | 内容 |
|---|---|
| 新增组件 | ColorPicker 颜色选择器(HSV 取色 / 预设色板 / 最近使用 / 透明度 / 三格式输入 / 复制,零依赖) |
| 文档站 demo | .dumirc.ts 增加 $ 精确匹配 alias,demo 直达源码免构建 |
| 首页 | 苹果风视觉 + 响应式(≤900px 平板 2 列 / ≤480px 手机单列)+ features 2×2 排版 |
| 首页背景 | 4 张哆啦A梦半透明背景图(WebP 700×300,每张 ~22KB,0.2 透明度水印效果) |
| 首页交互 | feature 卡片整卡可点击跳转(stretched-link) |
| 全局样式 | docs/styles/global.css(暗色模式、侧边栏、表格、代码块等打磨) |
| 工程完善 | 测试 / lint / CI 全链路修复;public/ 静态资源目录;.workbuddy 加入 gitignore |
MIT