Files
inkreach-agent/docs/plugins/05-renderer-components.md
T

4.3 KiB
Raw Blame History

L4 · 宿主内 React 组件(entry.renderer)

前置:L3。iframe 页是隔离的「贴进去的网页」;entry.renderer 则是真正的宿主内 React 组件——与内置插件同槽位、同观感、同主题。这是外置插件的最高 UI 形态。

1. 声明

{ "id": "my-plugin", "panel": true, "entry": { "renderer": "renderer.mjs" } }

文件名不要叫 index.ts/index.js——那是 pi 原生扩展的自动发现名,会被 pi CLI 双重加载。

2. renderer.mjs 契约

宿主动态 import 后调用默认导出 mount(ctx):

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 别名到宿主全局实例:

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 无法按包名解析,指路径即可。

// 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
// 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 报错?直接查 常见错误速查 §3。

5. 样式

组件直接跑在宿主树里——宿主的 Tailwind 类与 token 全部可用。标准模式已代码化:优先 import { Button, SettingRow, LoadingState, ErrorState, EmptyState, Switch } from '@aso/plugin-sdk/ui'(组件目录与三态铁律见 L6),不要手写;遵守 DESIGN.md 的类名词汇,禁止硬编码色值/阴影。

6. 生命周期

  • 装载:插件列表变化(启停/重载/启动)即重装;mount 同步执行注册
  • 卸载:插件禁用/移除 → 注册的组件自动从宿主卸载
  • 性能护栏:读源与 import 各限时 8s——超时/失败时槽位呈现宿主统一「装载失败」ErrorState(带重试),见 L7
  • 崩溃隔离:渲染错误被 PluginErrorBoundary 捕获,不影响宿主

7. 验证

  1. 重载插件 → 插件页该行出现 ⚙(若已声明设置)/侧栏入口/面板 tab
  2. 控制台无 [plugins] … renderer 装载失败
  3. 点击组件内按钮,state 正常更新(hooks 生效 = 单实例约束满足)

上一篇 L3 · 下一篇:L6 公共组件库——别手写样式,宿主把标准模式做成了组件。