99 lines
4.3 KiB
Markdown
99 lines
4.3 KiB
Markdown
# 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 \
|
||
--alias:@aso/plugin-sdk=<sdk源码路径>/index.ts --alias:@aso/plugin-sdk/ui=<sdk源码路径>/ui.tsx
|
||
```
|
||
|
||
> 最后两条 alias 只在用到 `@aso/plugin-sdk`(withTimeout 等)或 `./ui` 组件库时需要;
|
||
> sdk 以源码形态发布(exports 指向 .ts/.tsx),esbuild 无法按包名解析,指路径即可。
|
||
|
||
```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` 豁免) |
|
||
|
||
装载失败/白屏/hooks 报错?直接查 [常见错误速查](./09-troubleshooting.md) §3。
|
||
|
||
## 5. 样式
|
||
|
||
组件直接跑在宿主树里——**宿主的 Tailwind 类与 token 全部可用**。标准模式已代码化:优先 `import { Button, SettingRow, LoadingState, ErrorState, EmptyState, Switch } from '@aso/plugin-sdk/ui'`(组件目录与三态铁律见 [L6](./07-ui-components.md)),不要手写;遵守 [DESIGN.md](./DESIGN.md) 的类名词汇,禁止硬编码色值/阴影。
|
||
|
||
## 6. 生命周期
|
||
|
||
- 装载:插件列表变化(启停/重载/启动)即重装;`mount` 同步执行注册
|
||
- 卸载:插件禁用/移除 → 注册的组件自动从宿主卸载
|
||
- 性能护栏:读源与 import 各限时 8s——超时/失败时槽位呈现宿主统一「装载失败」ErrorState(带重试),见 [L7](./08-performance.md)
|
||
- 崩溃隔离:渲染错误被 `PluginErrorBoundary` 捕获,不影响宿主
|
||
|
||
## 7. 验证
|
||
|
||
1. 重载插件 → 插件页该行出现 ⚙(若已声明设置)/侧栏入口/面板 tab
|
||
2. 控制台无 `[plugins] … renderer 装载失败`
|
||
3. 点击组件内按钮,state 正常更新(hooks 生效 = 单实例约束满足)
|
||
|
||
---
|
||
|
||
**上一篇** [L3](./04-ui-slots.md) · **下一篇**:[L6 公共组件库](./07-ui-components.md)——别手写样式,宿主把标准模式做成了组件。
|