旧版飞鹊组件需要升级视觉效果,并且旧版组件组织形式过于分散,开发和使用都有诸多不便。因此飞鹊亟需更新升级
什么是飞鹊组件
飞鹊组件是一套基于 UED 设计规范,服务于中国制造网前后台的 React UI 组件库。
特性
1
提炼自前后台产品的交互语言和视觉风格
2
开箱即用的高质量 React 组件
3
使用 TypeScript 开发,提供完整的类型定义文件
4
完整的全链路开发支持
5
深入每个细节的主题定制能力
因为飞鹊组件拥有高度定制化的特性,你可以在任何地方使用飞鹊组件。
2
为什么要开发新版组件
旧版飞鹊组件视觉效果过时,需要升级交互视觉效果;旧版飞鹊组件设计规范并不统一,不同组件之间的规范有差别,需要按照新的设计规范进行统一;新功能组件需要开发。
还有一个比较重要的点:旧版组件解决按需加载的方案比较简陋,导致组织形式过于分散,因而开发非常繁琐、文档与代码疏远、使用也有诸多不便。
3
怎么开发新版组件
基于上述问题,在开发新版组件的时候就要做到,组织形式既集中又分散
集中是为了解决开发、使用时的繁琐问题。
分散是为了降低代码耦合度,从物理上区别每一个组件;分散的另一个好处是,每一个组件都可以单独发包,可以更原子化的使用组件。
道理我都懂,但这种既要还要的需求有办法解决么?
诶...还真有
当我们遇到这种多项目集中管理(Monorepo)的问题时,很自然的就会想到Lerna 。
当然,新版的 npm 也不可或缺,因为它提供了 workspaces 功能支持。
那么项目的简单框架就呼之欲出了:
.
├─ dist
├─ packages
│ ├─ button
│ │ ├─ dist
│ │ ├─ src
│ │ └─ package.json
├─ src
├─ lerna.json
└─ package.json
如上面所示,我们将组件代码分散得置于 packages 目录下,再使用根目录下的 src 将所有分散的组件集中在一起,最终将所有组件输出到 dist 目录。这样就可以解决我们希望既集中又分散的问题。
既要还要的问题解决了,那文档怎么办?毕竟我们希望文档是和项目在一起的。
好办,开源项目 dumi 就是解决这类问题的。
我们在项目中增加一下 dumi,并开始书写文档:
.
├─ dist
├─ public
│ └─ doc.html // 最终汇总生成的文档页面
├─ packages
│ ├─ button
│ │ ├─ dist
│ │ ├─ src
│ │ ├─ package.json
│ │ └─ README.md // 每个组件的文档
├─ src
├─ .dumirc.ts // dumi 配置文件
├─ lerna.json
└─ package.json
这样我们就解决了文档和代码疏远的问题,小伙伴们可以愉快的写文档啦。
4
遇到的问题
新项目开荒也并非一片坦途,过程中肯定会遇到这样那样的问题,这里我举例一些有代表性的问题。
组件内部引用问题
button 和 icon 都是飞鹊组件,假设我们在 button 组件内部使用了 icon 组件,那么 button 组件的代码应该这么写:
import Icon from "../icon";
乍一看没什么问题,但不要忘了我们的组件是既要还要的,这样一来就破坏了组件的原子性。
那我们改一下:
import Icon from "@future/icon";
emmm...这样倒是保住了原子性,但是集中发包时就有问题了,在集中模式下应该使用 “../icon” 以确保逻辑正确、性能强劲。
这怎么办呢?
有办法,我们可以在集中模式下将 "@future/icon" 动态替换为 “../icon”。说到动态替换,小伙伴想到了什么?
对了,我们可以直接使用正则表达式来处理这种情况。但我们这次换一种方法:语法分析(AST)
我们可以使用 acorn 工具分析 button 代码,生成 ast,遍历 ast 得到导入语句的节点,再将节点上的 "@future/icon" 替换为 “../icon”,最后使用修改信息并借助 magic-string 可以生成新的代码。
文档 API 生成问题
如果说上面使用语法分析来解决替换问题属于高射炮打蚊子,那么接下来这个问题就只能使用语法分析来解决了。
dumi 可以通过带类型的组件声明来生成 API 文档信息。例如:
interface ButtonProps {
icon: ReactNode;
}
export const Button:React.FC<ButtonProps>;
dumi 会根据上述类型声明,生成 button 的 API 信息,里面包含了 icon 字段。这是正常的逻辑。
然而,在某些情况下组件需要继承原生组件的属性,仍以 button 组件为例:
interface ButtonProps extend React.ButtonHTMLAttributes {
icon: ReactNode;
}
export const Button:React.FC<ButtonProps>;
这时候 dumi 生成的 API 信息就包含了数量繁多的原生属性信息,但我们不希望展示这些原生属性信息,这就是问题所在。
那该怎么办呢?
不怕,我们有语法分析这个大招。
我们可以将 Button 的属性拆分为 CustomProps 和 NativeProps :
type NativeProps = React.ButtonHTMLAttributes;
interface CustomProps {
icon: ReactNode;
}
export const Button:React.FC<ButtonProps & NativeProps>;
然后使用语法分析工具将上面的代码解析为 AST,遍历 AST 得到 CustomProps 接口,使用 CustomProps 生成 Button2 组件类型声明:
type NativeProps = React.ButtonHTMLAttributes;
interface CustomProps {
icon: ReactNode;
}
export const Button:React.FC<ButtonProps & NativeProps>;
export const Button2:React.FC<ButtonProps>
最后,设置 dumi 使用 Button2 的类型声明来生成 API,大功告成。
是不是还蛮简单的(o゚v゚)?
5
工程优化
文档部署自动化
童鞋,release-it 了解一下?
release-it 可以一站式解决版本升级、打包、Git 提交、npm 发布、GitLab 发布等需求,并且这些功能都提供了交互式执行和自动化执行,保证你用了以后腰不酸腿不疼~
文档部署自动化
过去组件文档部署需要借助 jenkins 来实现,这一方面是因为文档与代码分离,另一方面也因为持续集成了解不深。
现在得益于代码与文档共存,我们可以借助 GitLab CI 来自动化部署文档。
只需要在项目根目录下创建如下 .gitlab-ci.yml 脚本:
image: node:16.15.0
pages:
tags:
- node
stage: build
script:
- npm run build
artifacts:
paths:
- public
only:
- master
就可以在每次提交组件代码之后,自动生成组件文档,并部署到 GitLab Pages 上。
注意:项目仓库需要启用 GitLab CI
6
结语
前端项目工程化是一个不断推陈出新的过程,正所谓学无止境、折腾不止。新版飞鹊组件是一个比较经典的前端项目工程化落地案例,但这也只代表目前的技术方向。而技术是在不断进步的,最近就涌现出来 Turbopack、Rspack 等许多使用 Rust 写的打包工具,也许下次我们就要讲这些工具了😂
欢迎大家提建议 ( ゜- ゜)つロ