docs: 渐进式插件教程(L0→L5)+ DESIGN.md 视觉规范 + plugin-template 脚手架
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
# L4 · 宿主内 React 组件(entry.renderer)
|
||||
|
||||
> 前置:[L3](./04-ui-slots.md)。iframe 页是隔离的「贴进去的网页」;`entry.renderer` 则是**真正的宿主内 React 组件**——与内置插件同槽位、同观感、同主题。这是外置插件的最高 UI 形态。
|
||||
|
||||
## 1. 声明
|
||||
|
||||
```json
|
||||
{ "id": "my-plugin", "panel": true, "entry": { "renderer": "renderer.mjs" } }
|
||||
```
|
||||
|
||||
> 文件名**不要叫 index.ts/index.js**——那是 pi 原生扩展的自动发现名,会被 pi CLI 双重加载。
|
||||
|
||||
## 2. renderer.mjs 契约
|
||||
|
||||
宿主动态 import 后调用默认导出 `mount(ctx)`:
|
||||
|
||||
```js
|
||||
export default function mount(ctx) {
|
||||
// ctx.react 是宿主的 React 单实例——必须用它创建组件(自带 React 副本会让 hooks 全部失效)
|
||||
const R = ctx.react
|
||||
|
||||
function Panel() {
|
||||
const [n, setN] = R.useState(0)
|
||||
return R.createElement('div', { className: 'p-3 space-y-2' },
|
||||
R.createElement('div', { className: 'text-[12.5px] text-ink' }, `点击 ${n} 次`),
|
||||
R.createElement('button', {
|
||||
className: 'rounded-control border border-line-strong px-2.5 py-1 text-[11.5px] text-ink hover:bg-hover',
|
||||
onClick: () => setN(n + 1)
|
||||
}, '+1')
|
||||
)
|
||||
}
|
||||
|
||||
ctx.registerPanel(Panel) // 右侧面板 tab 内容(需 manifest.panel: true)
|
||||
ctx.registerView(Panel) // 整页视图(侧栏入口 → 主内容区);两个都注册也可以
|
||||
}
|
||||
```
|
||||
|
||||
## 3. JSX 工程构建配方(推荐)
|
||||
|
||||
手写 `createElement` 只适合玩具;真实插件用 JSX + esbuild,构建时把 `react` 别名到**宿主全局实例**:
|
||||
|
||||
```sh
|
||||
esbuild renderer.tsx --bundle --format=esm --outfile=renderer.mjs \
|
||||
--jsx=automatic \
|
||||
--alias:react=./shims/react.js --alias:react/jsx-runtime=./shims/jsx-runtime.js
|
||||
```
|
||||
|
||||
```js
|
||||
// shims/react.js —— 从宿主全局取单例
|
||||
const R = window.__ASO_PLUGIN_HOST__.react
|
||||
export default R
|
||||
export const { useState, useEffect, useRef, useMemo, useCallback, createContext,
|
||||
useContext, Fragment, memo, forwardRef, createElement } = R
|
||||
```
|
||||
|
||||
```js
|
||||
// shims/jsx-runtime.js
|
||||
export const { jsx, jsxs, Fragment } = window.__ASO_PLUGIN_HOST__.jsxRuntime
|
||||
```
|
||||
|
||||
`packages/plugin-template/` 已内置这套配置,复制即用。
|
||||
|
||||
## 4. 硬性约束
|
||||
|
||||
| 约束 | 原因 |
|
||||
|---|---|
|
||||
| React 必须来自 `ctx.react` / 宿主全局 | 双 React 实例 → hooks 全部报错 |
|
||||
| 模块**自包含**(单文件 bundle) | 宿主以 Blob URL 装载,相对导入没有基路径 |
|
||||
| 不 import 裸包名(react 等) | 同上;一切依赖打进 bundle(react 走别名除外) |
|
||||
| 单文件 ≤ 8MB | 超限拒绝装载 |
|
||||
| 仅开发版装载 | 用户版第三方装载关闭(`ASO_EXTERNAL_PLUGINS=1` 豁免) |
|
||||
|
||||
## 5. 样式
|
||||
|
||||
组件直接跑在宿主树里——**宿主的 Tailwind 类与 token 全部可用**(`text-ink`、`border-line`、`rounded-card`、`bg-accent` …)。必须遵守 [DESIGN.md](./DESIGN.md) 的类名词汇与布局模式,禁止硬编码色值/阴影。
|
||||
|
||||
## 6. 生命周期
|
||||
|
||||
- 装载:插件列表变化(启停/重载/启动)即重装;`mount` 同步执行注册
|
||||
- 卸载:插件禁用/移除 → 注册的组件自动从宿主卸载
|
||||
- 崩溃隔离:渲染错误被 `PluginErrorBoundary` 捕获,不影响宿主;装载失败仅 console.error + 日志
|
||||
|
||||
## 7. 验证
|
||||
|
||||
1. 重载插件 → 插件页该行出现 ⚙(若已声明设置)/侧栏入口/面板 tab
|
||||
2. 控制台无 `[plugins] … renderer 装载失败`
|
||||
3. 点击组件内按钮,state 正常更新(hooks 生效 = 单实例约束满足)
|
||||
|
||||
---
|
||||
|
||||
**上一篇** [L3](./04-ui-slots.md) · **下一篇**:[L5 自诊断](./06-self-diagnosis.md)——让 Agent 能发现并修复你的插件问题。
|
||||
Reference in New Issue
Block a user