我们知道,茴香豆的茴字有三种写法,Angular, React, Vue。在公司内部,我们不免会遇到跨项目,多框架的开发,而多个项目间,往往又会有一些共同的部分。这就需要我们能够维护一套多框架的组件库。比如,Ant Design, AgGrid 之类,多是如此。
那么如何维护一个多框架的组件库呢?经过个人实践,有以下经验可以分享:
package 管理
- 通过 pnpm 实现 monorepo 中不同 package 之间的本地调用。(或者 yarn2)
- 使用 lerna 和 cz-lerna-changelog 实现版本的自动升级与管理,以及 changelog 的生成。
编译
- Angular 使用 angular-cli, 以及自带的多 package 方式
- React 使用 vite
- Vanilla 使用 swc(不用 vite 是因为作为 vanilla lib, 本身没有 bundle 的需求。将 TS build 成 JS 即可。如果要生成 .d.ts 文件,依然要使用 tsc, 所以本质上,并没有提速多少)
发布
- 可以使用 github action 在合并代码的时候发布
- 一般情况下,我们编译的 dist folder 只是 src folder 的编译文件。发布的时候,我们可以直接 ignore src 即可。然而 angular 跟其他有所不同,编译出来的是 APF (angular package format)。也就是说,dist 下面的内容是包含所有内容的,我们直接 publish dist folder 就可以了。这使得我们需要对 angular 和 其他 lib 分开处理。目前使用的方案是 pnpm 所支持的 publishConfig.directory 用来制定当前 package 下发布的目录。从而实现脚本的统一。
- 如果简单的 package 中的 scripts 依然不够使用,需要使用 bash。这里推荐对前端友好的 zx。可以使用 js 来写命令。
目录结构
.packages
|-- angular
|-- demo
|-- ngx-p1
|-- ngx-p2
|-- react
|-- demo
|-- react-p1
|-- react-p2
|-- vanilla
|-- p1
|-- p2
Package 测试
- Unit test,有多种选择,随意,这里推荐 jest
- 本地 package 连接测试。当我们需要手动测试 package 的时候,不太可能每次发布完以后在项目中测试。一定是测试完再发布的。那么我们该如何测试呢。比较常见的是新建一个 demo package, react, angular 分别单独新建。然后链接本地的 package 测试。可以使用 pnpm 的 workspace:* 或者 lerna 自带的本地 package 连接。当然,其实还有其他方法,比如 tsconfig 中设置 path,或者软连接,package 中制定 file path 等等。这里推荐,workspace 或者 lerna 比较简单。
- Storybook,不仅测试 component 比较方便,还可以当作文档供使用者参考使用。缺点是对 component 比较方便。对纯逻辑部分比如 angular 的 service, react 的 hook, 不太好使用。
逻辑共享
普通的 UI 组件可能没有太多可共享的部分,不同的框架单独处理就行。但是涉及到较为复杂的组件,我们并不想把相同的逻辑重复实现好几次。
基本的思路是这样的,我们将组件的逻辑层抽离,做成与框架无关的。暴露为 state 和 action 被组件调用。这样,不同的框架都可以调用相同的状态逻辑,区别,只是不同的框架需要根据状态 render view 而已。
这种跨平台的状态管理,大家很容易想到的当然是 redux, mobx 这类状态管理库。
最开始我的想法是 redux, 因为 angular 中有基于 redux 实现的 ngrx store. 但是,react 对于全局的,复杂的状态管理是比较合适的,对于一个 component 层的逻辑,太重,也太复杂了。而且,将局部状态用全局状态表示,反而增加了工作量。
Mobx 在 Angular 中却不太流行。在 Vue 中使用应该也不多(毕竟 vuex 跟 mobx 是非常接近的)。
我们在上一遍文章提过,rxjs 中的 behaviorSubject 其实是最简单 store。我们有没有可能基于 rxjs 实现一个简单的状态管理呢。其实 angular 本身就存在这样一个东西,就是 component store. 因为 rxjs 的关系,component store 的逻辑实现其实非常简单,那么我们也可以非常简单的复刻一个 component store. 从而通过 rxjs 来实现一个简单的状态管理库。
可以看到,这里不足 400 行: platform/component-store.ts at master · ngrx/platform (github.com)
简洁一点,我们也只需要实现,select, updater, effect, patchState 几个方法而已,这里不做赘述。
class RxStore {
constructor(initState?: T) {}
select<M extends (state: T) => any>(mapper: M): Observable<ReturnType<M>> {};
effect<T>(effectFunc: (stream: Observable<T>) => Observable<unknown>) {}
updater<A, P extends (state: T, param: A) => T>(project: P) {}
patchState(project: ((state: T) => Partial<T>) | Partial<T>) {};
}
Angular 中用 Service 包裹,React 中用 hooks 包裹即可实现一个简单的跨框架的 component store.