Skip to content

往共享库加组件

把一个组件放进 ui/,让多个站共用。本文是检查清单;背后的约定见 reference/ui/,端到端的走查见 tutorials/shared-component-walkthrough.md

1. 先判断它该不该进库

该进不该进
纯展示,由 props 驱动自己取数、自己轮询
至少两个站会用(或明确将用)只有一个站用得上的一次性版式
语义是通用的(表格、图表、徽章、看板)语义绑死某个业务流程

带写操作的组件可以进库,但写能力必须做成可选 prop(见第 4 条)。

2. 放对目录

组件类型分,不按使用方分: primitives/ forms/ layout/ feedback/ data/ charts/ boards/ flaps/ map/

目录不得出现 control/ peering/ 之类的划分——那会立刻失效,还会诱导出 「control 专用组件」这种伪概念。

尚未 populated 的目录等组件迁进来时再建,不预先留空壳。

3. 不许反向依赖 app

组件里不能出现 $lib/...$app/...。需要宿主能力时:

  • 当前语言 / 明暗态 → 读 $ui/host.sveltelocale / theme
  • 导航、toast、API 调用 → 由调用方通过 prop 传函数进来。

4. 写操作做成可选 prop

svelte
let { data, onRequestSnapshot }: { data: X; onRequestSnapshot?: () => void } = $props();

{#if onRequestSnapshot}
  <IconButton onclick={onRequestSnapshot} … />
{/if}

不传即不渲染,那段 DOM 根本不进产物——不是渲染出来再拒绝。这是同一个组件能在 一处可写、在另一处只读的全部机制。

5. 文案进库字典,四种语言一起加

组件自有语义的 key(topo.*fstat.*pflap.* 这类)加进 ui/i18n.svelte.tsen / zh / zh-Hant / ja 四条一起加

通用词汇(common.updatedAtcommon.noMatch不要搬进库——它们留在各 app 字典里, 库通过宿主回落读到。判断标准:这个词 app 自己也在到处用吗?

6. 样式只用共享令牌

不写死颜色、字号、圆角。用 --c-data-*--text / --text-dim--fs-*--radius*--border。全表见 reference/design-tokens.md

图表颜色运行时从 CSS 变量解析,不在 JS 里硬编码——theme.mode 只当重绘信号。

数量图用 --c-data-*,状态图用 --c-ok/warn/bad。两套不要混。

7. 从桶文件导出

ui/index.ts 的对应分组下加一行 export { default as X } from './<目录>/X.svelte';, 连同它的公开类型。

横切模块不进桶文件format / types / host.svelte 走子路径导入), 这样纯 .ts 模块不必把 .svelte 组件拖进依赖图。

⚠️ 如果组件引入 ?url 资源(wasm、worker、字体),注意它会因为桶文件的 re-export 被 emit 到所有导入 $ui 的产物目录里,即使 JS 侧摇得掉。首页的 fleet 岛就为此 绕开桶文件。

8. 跑检查

bash
npm run check:i18n     # 文案完整性——最容易漏的一步
npm run check          # 三个站的 svelte-check
npm run build          # 四个站全建

check:i18n 守的是「组件搬进库、key 却留在原 app 字典里,于是另一边整块回显 key」 这类不会报错、类型检查也发现不了的回退。

9. 两个站各看一眼

至少在控制台与门户各渲染一次,确认亮暗两个主题都正常、切语言时文案跟着变。

文案不跟着变 → 检查 app 的 setUiHost() 是不是解构了对象。解构会切断反应性。

10. 记住授权界面

如果授权界面也用到这个组件(或它引用的令牌),push 不会部署它——要手工 npm run build:auth 并投放,见 deploy-auth.md