飞鹊组件开发纪实

科技   2023-06-01 09:15   江苏  

旧版飞鹊组件需要升级视觉效果,并且旧版组件组织形式过于分散,开发和使用都有诸多不便。因此飞鹊亟需更新升级

1

什么是飞鹊组件

飞鹊组件是一套基于 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 的属性拆分为 CustomPropsNativeProps

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 写的打包工具,也许下次我们就要讲这些工具了😂



欢迎大家提建议 ( ゜- ゜)つロ

中国制造网UED
这里是中国制造网UED公众号,我们是一个专注于用户体验研究领域和前瞻技术的团队,我们致力于为大家提供最新、最实用的设计资讯、前端技术和案例分享。通过优秀的用户体验来传递美好。