多框架的组件库开发思路

383 阅读4分钟

我们知道,茴香豆的茴字有三种写法,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.