feat: 插件检验器 plugin:check + L6/L7 教程 + 模板接入组件库——性能与样式标准成为机器关卡
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
# L7 · 性能与检验(plugin:check)
|
||||
|
||||
> 前置:L0–L6。约 20 分钟。
|
||||
> 目标:插件百花齐放,但**绝不卡宿主、绝不风格跑偏**;发布前有机器关卡把关。
|
||||
|
||||
## 性能铁律
|
||||
|
||||
### 1. 主进程不阻塞(检验器:error)
|
||||
|
||||
`main.mjs` 运行在宿主主进程。任何同步 IO——`fs` 的 `*Sync`、`execSync`、
|
||||
`spawnSync`——都会卡住**整个应用**的事件循环(聊天流、其他插件全部冻结)。
|
||||
一律用 `node:fs/promises` 与异步子进程:
|
||||
|
||||
```js
|
||||
// ❌ const text = fs.readFileSync(path, 'utf8')
|
||||
// ✅
|
||||
import { readFile } from 'node:fs/promises'
|
||||
const text = await readFile(path, 'utf8')
|
||||
```
|
||||
|
||||
### 2. 激活限时 10 秒(宿主强制)
|
||||
|
||||
插件工厂若返回 Promise,宿主限时 10s;超时或 reject 即把插件标记为失败
|
||||
(插件管理页可见红字),绝不无限等待拖垮 runtime 重建。因此:
|
||||
|
||||
- 激活路径只做「注册」级别的工作,重活(预取数据、预热连接)放到工具首次调用
|
||||
- 自己 `catch` 并 `api.log.error`——被宿主兜住意味着插件没跑起来
|
||||
|
||||
### 3. renderer 装载限时 8 秒(宿主强制)
|
||||
|
||||
读源与 import 各限时 8s,超时/失败由宿主呈现统一失败态(重试按钮);
|
||||
组件渲染期崩溃由 PluginErrorBoundary 兜底。插件只需管好组件内部的异步。
|
||||
|
||||
### 4. 自己的异步自己兜(sdk 提供 `withTimeout` / `withRetry`)
|
||||
|
||||
先于宿主超时并自行恢复,而不是等宿主判死:
|
||||
|
||||
```js
|
||||
import { withTimeout, withRetry, TimeoutError } from '@aso/plugin-sdk'
|
||||
|
||||
// 网络请求:3s 超时,失败退避重试 2 次(300ms → 600ms)
|
||||
const data = await withRetry(() => callApi(), { retries: 2, delayMs: 300, timeoutMs: 3000 })
|
||||
|
||||
// 单次限时
|
||||
const cfg = await withTimeout(loadConfig(), 2000, '加载配置')
|
||||
try {
|
||||
await risky()
|
||||
} catch (err) {
|
||||
if (err instanceof TimeoutError) { /* 降级路径 */ }
|
||||
throw err
|
||||
}
|
||||
```
|
||||
|
||||
注意:`withTimeout` 只是竞速,底层操作不会被取消——有 `AbortController` 的
|
||||
调用方请把 signal 接到底层,再用它兜底计时。
|
||||
|
||||
### 5. 渲染不卡顿
|
||||
|
||||
长列表分页/虚拟化,不要一次渲染数万节点;重计算放进工具(Agent 侧)或
|
||||
`setTimeout(fn, 0)` 分帧,不占渲染主线程。
|
||||
|
||||
## 三态呈现(检验器:warn)
|
||||
|
||||
任何异步 UI 必须是四态之一:`LoadingState` / `ErrorState` / `EmptyState` / 数据。
|
||||
`alert`/`confirm`/`prompt` 是阻塞式同步对话框,禁止(用 `Button` + 状态组件替代)。
|
||||
详见 [L6](./07-ui-components.md)。
|
||||
|
||||
## 样式一致性(检验器:error)
|
||||
|
||||
- 只用公共组件库 + [DESIGN.md](./DESIGN.md) token;硬编码 hex 色值是 error
|
||||
(`#fff`/`#ffffff` 白字除外)
|
||||
- renderer.mjs 必须经宿主 shim 使用单实例 React(`__ASO_PLUGIN_HOST__`)——
|
||||
自带 React 副本 = hooks 全部失效
|
||||
|
||||
## 日志(检验器:error)
|
||||
|
||||
`main.mjs` 禁 `console.*`,统一 `api.log.*`——Agent 自诊断闭环见 [L5](./06-self-diagnosis.md)。
|
||||
|
||||
## 检验器:`pnpm plugin:check <插件目录>`
|
||||
|
||||
发布前必跑;Agent 自迭代时,**通过检验器是完成定义(DoD)的一部分**。
|
||||
|
||||
| 级别 | 检查项 |
|
||||
|---|---|
|
||||
| error | manifest 缺失/不可解析;id/name/version(semver) 缺失;minContract 高于宿主契约;entry 指向的文件不存在;renderer.mjs > 8MB / main.mjs > 2MB;main.mjs 同步 IO;main.mjs `console.*`;renderer.mjs 未引用 `__ASO_PLUGIN_HOST__`;任何源码硬编码 hex 色值 |
|
||||
| warn | 缺 minContract;renderer.mjs > 300KB(疑似打包自带 React);注册 view/panel 但未用三态组件;`alert/confirm/prompt`;entry.renderer 产物缺失但有 src/renderer.tsx(未构建) |
|
||||
|
||||
宿主契约版本从 `plugin-host.ts` 源码提取(单一事实源),检验器与宿主永不会各执一词。
|
||||
|
||||
## 一张自查表
|
||||
|
||||
- [ ] `pnpm plugin:check <目录>` 0 error
|
||||
- [ ] main.mjs 无同步 IO,激活 10s 内完成
|
||||
- [ ] 一切异步有超时与重试,catch 后 `api.log` + `ErrorState`
|
||||
- [ ] 长列表不整批渲染
|
||||
- [ ] 深浅色各看一眼(token 正确时自动成立)
|
||||
|
||||
---
|
||||
|
||||
**上一篇** [L6 公共组件库](./07-ui-components.md) · 返回 [教程索引](./README.md)
|
||||
Reference in New Issue
Block a user