# 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)