110 lines
4.6 KiB
Markdown
110 lines
4.6 KiB
Markdown
# 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')
|
||
```
|
||
|
||
**存储也不例外**:`api.storage.*`(SQLite)在宿主的共享 worker 线程里执行,
|
||
主进程只做异步 RPC——大查询不会卡聊天流(见 [L9](./11-storage.md))。
|
||
自己开的文件句柄同理:全 async。
|
||
|
||
### 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`
|
||
## 一张自查表
|
||
|
||
- [ ] `pnpm plugin:check <目录>` 0 error
|
||
- [ ] main.mjs 无同步 IO,激活 10s 内完成
|
||
- [ ] 一切异步有超时与重试,catch 后 `api.log` + `ErrorState`
|
||
- [ ] 长列表不整批渲染
|
||
- [ ] 深浅色各看一眼(token 正确时自动成立)
|
||
|
||
---
|
||
|
||
**上一篇** [L6 公共组件库](./07-ui-components.md) · 出问题了?[常见错误速查](./09-troubleshooting.md) · 返回 [教程索引](./README.md)
|