外观
往共享库加组件
把一个组件放进 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.svelte的locale/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.ts, en / zh / zh-Hant / ja 四条一起加。
通用词汇(common.updatedAt、common.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。