跳转到内容

插件开发指南

Eidos Lite 插件可以为 Space 增加文档格式化工具、快捷指令和自定义界面。 插件运行在完全隔离的本地沙箱中,全设备安装一份,并按 Space 分别启用,无需网络连接或外部 CDN。

一个插件可以在同一份 plugin.json 清单中组合以下能力:

你要实现的功能 使用的接口 用户入口
格式化 Markdown 或文本 Formatter provider 格式化文档、选择格式化程序
统计字数、执行文档操作 Action Cmd+K,可选自定义快捷键
编辑 CSV、Markdown 或自定义文件 Document view 文件右键菜单 → Open with
打开独立的插件页面 Page view 插件的导航入口
在地图或图表上展示表格记录 Table view 表格视图菜单 → 新建视图

快速上手:创建并运行你的第一个插件

Section titled “快速上手:创建并运行你的第一个插件”

确保已安装 Node.js 22.12 或更新版本以及常用的包管理器(npm、pnpm 或 yarn)。同时安装 Eidos Lite 用于本地运行和调试。

你可以选择以下任意一种方式创建插件项目:

方式一:使用 npm / npx(推荐,无需预装额外工具)

终端窗口
npx @eidos.space/plugin-tools create my-plugin
cd my-plugin
npm install # 或 pnpm install

方式二:使用 Eidos CLI

终端窗口
eidos plugin create my-plugin
cd my-plugin
pnpm install

生成的项目目录结构如下:

my-plugin/
├── plugin.json # 声明插件能力、视图与入口的清单配置
├── package.json # 项目依赖与 npm scripts
├── tsconfig.json # 严格模式 TypeScript 配置
└── src/
├── main.ts # 视图挂载入口源码
├── csv.ts # 业务解析逻辑
└── style.css # 样式表

脚手架已在 package.json 中配置好官方发布的开发依赖与开箱即用的 scripts:

  • @eidos.space/plugin-sdk: 包含插件 1.0 规范的完整 TypeScript 契约与接口。
  • @eidos.space/plugin-tools: 提供 eidos-plugin check(源码校验)与 eidos-plugin pack(自包含打包)命令。
  • npm run check: 运行 eidos-plugin check .,执行静态校验。
  • npm run pack:plugin: 运行 eidos-plugin pack .,打包输出离线 .eidos-plugin

3. 在 Eidos Lite 中即时加载与调试

Section titled “3. 在 Eidos Lite 中即时加载与调试”

调试插件无需提前打包或 build,Eidos Lite 内置编译器支持实时动态编译:

  1. 启动 Eidos Lite,打开任意 Space。
  2. 点击顶部工具栏中的 Plugins 图标(位于搜索与更多菜单之间)。
  3. 点击 Load development source…(加载开发源码),选中项目的 plugin.json(或单文件 .ts)。
  4. 检查权限并点击启用插件。
  5. 在 Space 中找到或新建一个 .csv 文件,右键点击并选择 Open with → CSV Table

当你在编辑器中修改 src/ 下的代码并保存时,Eidos Lite 会自动重新编译并热重载视图!


清单文件声明插件的元数据、所需权限和扩展点配置:

{
"apiVersion": 1,
"id": "example.my-plugin",
"name": "My Plugin",
"version": "0.1.0",
"extension": "./src/extension.ts",
"views": [],
"actions": [],
"formatters": [],
"placements": []
}
  • apiVersion: 当前固定为 1,遵循 Plugin 1.0 契约。
  • id: 全局唯一标识符(如 vendor.feature)。
  • extension: 后台控制模块入口,插件激活时执行。
  • views: 自定义 UI 界面列表。
  • placements: 告知宿主在界面的哪些入口展示这些视图和操作。

核心 SDK 仅包含 TypeScript 类型

import type { ExtensionContext, ViewContext } from "@eidos.space/plugin-sdk"

所有的运行时能力均由宿主在激活或挂载时动态注入到 ctx 上下文中。没有 SDK 运行时体积负担,也没有 connect() 初始化开销。

  • 后台生命周期: Action 和 Formatter 的 extension 默认导出 activate(ctx: ExtensionContext) 函数。
  • 视图生命周期: 每个 View 的 entry 默认导出 mount(ctx: ViewContext, root: HTMLElement) 函数。
  • 资源清理: 监听器、定时器请通过返回 { dispose() { ... } } 或调用 ctx.subscriptions.add(...) 在卸载时清理。

品牌 Logo 与功能图标(Icons & Branding)

Section titled “品牌 Logo 与功能图标(Icons & Branding)”

插件体系清晰区分插件品牌标识具体功能图标

  • 插件 Logo (manifest.icon):代表整个插件的品牌,展示在插件市场列表、插件详情面板以及 Space 插件管理设置中。
  • 视图/动作图标 (view.icon / action.icon):代表特定视图或操作的功能(如脑图、表格、格式化等),展示在文件右键「打开方式」(Open with)菜单、视图 Tab 栏以及命令面板中。若未单独设置 view.icon,系统会自动回退使用 manifest.icon;若均未提供,则自动生成优雅的主题首字母占位符。

图标支持以下 3 种声明格式:

{
"icon": {
"paths": ["M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"]
}
}
  1. 内联单色 SVG 路径对象(推荐):{ "paths": ["..."] },由宿主按照设计系统当前主题色(currentColor)自动渲染,像素严格对齐且零额外网络开销。
  2. 本地文件相对路径:如 "./assets/logo.svg""./assets/view.png"。在执行 eidos-plugin pack 打包时,打包器会自动将其读取并转为 Base64 Data URL 内嵌在包内,保持离线自包含。
  3. Data URL 字符串:如 "data:image/svg+xml;base64,..."

通过在 placements 中声明 location: "file/open",插件视图可以注册为特定文件扩展名的编辑器:

"placements": [
{ "location": "file/open", "view": "table", "extensions": [".csv"] }
]
  • 右键即用:在 Space 文件列表中右键点击 .csv 文件,光标移至 Open with(打开方式),即可看到带独立功能图标的插件视图选项。
  • 全局默认编辑器设置:在 Eidos Lite 偏好设置 Settings → Files(设置 → 文件)中,系统会集中展示 Markdown、HTML 以及所有已安装插件声明的文件扩展名。用户可以在下拉菜单中为 .csv 等文件全局选定默认编辑器,后续双击即可直接通过该插件打开。

插件按设备安装一次,按 Space 分别启用:

  • Eidos Lite 与 eidos serve 统一使用 ~/.eidos/plugins(Windows 下为 %USERPROFILE%\\.eidos\\plugins)。
  • 设置环境变量 EIDOS_HOME 可迁移整个存储目录。
  • 存储目录内包含内容寻址的归档(packages/<sha256>.eidos-plugin)、设备配置文件(config.json),以及插件私有数据(data/<plugin-id>/)。

Formatter 接收编辑器文本并返回格式化后的文本。撤销/重做、选区管理和文件保存由宿主全权托管。

plugin.json 中声明:

{
"apiVersion": 1,
"id": "example.typography",
"name": "Typography",
"version": "0.1.0",
"extension": "./src/extension.ts",
"formatters": [
{
"id": "trim",
"title": "Trim trailing whitespace",
"extensions": [".md", ".txt"]
}
]
}

src/extension.ts 中实现:

import type { ExtensionContext } from "@eidos.space/plugin-sdk"
export default function activate(ctx: ExtensionContext) {
ctx.formatters.register("trim", {
format({ text, signal }) {
signal.throwIfAborted()
return { text: text.replace(/[ \t]+(?=\r\n|\r|\n|$)/g, "") }
},
})
}

触发方式:

  • Cmd+K 选择 格式化文档(Format Document)。
  • 快捷键:macOS 为 Shift+Option+F,Windows 为 Shift+Alt+F

Action 用于从命令面板(Cmd+K)或快捷键触发自定义业务逻辑。

plugin.json 中声明:

{
"apiVersion": 1,
"id": "example.text-tools",
"name": "Text Tools",
"version": "0.1.0",
"extension": "./src/extension.ts",
"actions": [
{
"id": "count",
"title": "Count characters",
"context": "document",
"access": "read",
"extensions": [".md", ".txt"]
}
],
"placements": [
{ "location": "command-palette", "action": "count" },
{ "location": "keybinding", "action": "count", "key": "Mod+Alt+C" }
]
}

src/extension.ts 中实现:

import type { ExtensionContext } from "@eidos.space/plugin-sdk"
export default function activate(ctx: ExtensionContext) {
ctx.actions.register("count", async ({ binding, ui }) => {
if (binding.kind !== "document") return
const snapshot = await binding.document.read()
await ui.notify(`字符数统计:${Array.from(snapshot.text).length}`)
})
}

3. 自定义文件视图(Document View)

Section titled “3. 自定义文件视图(Document View)”

文件视图可以接管特定扩展名文件的预览或编辑(通过右键菜单 Open with 打开)。

plugin.json 中声明:

{
"apiVersion": 1,
"id": "example.markdown-viewer",
"name": "Markdown Preview",
"version": "0.1.0",
"views": [
{
"id": "preview",
"title": "Rich Preview",
"entry": "./src/preview.ts",
"context": "document",
"access": "write"
}
],
"placements": [
{ "location": "file/open", "view": "preview", "extensions": [".md"] }
]
}

src/preview.ts 中实现:

import type { ViewContext } from "@eidos.space/plugin-sdk"
export default async function mount(ctx: ViewContext, root: HTMLElement) {
if (ctx.binding.kind !== "document") return
const file = ctx.binding.document
// 监听来自宿主或其他编辑器的修改
const observation = await file.observe((snapshot) => {
root.textContent = snapshot.text
})
ctx.subscriptions.add(observation.subscription)
// 首次渲染
root.textContent = observation.snapshot.text
return {
dispose() {
root.replaceChildren()
},
}
}

编辑文档内容:

const before = await file.read()
const result = await file.edit({
text: nextText,
expectedVersion: before.version,
label: "修改内容",
})
if (result.status === "stale") {
// 版本冲突协调处理
}

独立页面为 Space 侧边栏添加导航入口:

plugin.json 中声明:

{
"views": [
{
"id": "home",
"title": "控制面板",
"context": "page",
"entry": "./src/page.ts"
}
],
"placements": [{ "location": "navigation", "view": "home" }]
}

src/page.ts 中实现:

import type { ViewContext } from "@eidos.space/plugin-sdk"
export default function mount(ctx: ViewContext, root: HTMLElement) {
if (ctx.binding.kind !== "page") return
root.innerHTML = `
<div style="padding: 24px;">
<h1>插件控制面板</h1>
<p>当前子路由:${ctx.binding.route || "/"}</p>
</div>
`
return {
dispose() {
root.replaceChildren()
},
}
}

表格视图可以用图表、地图或画廊等形式展现 .eidos 表格数据。

plugin.json 中声明:

{
"views": [
{
"id": "map",
"title": "地图视图",
"entry": "./src/map.ts",
"context": "table",
"access": "read"
}
],
"placements": [{ "location": "table/view", "view": "map" }]
}

src/map.ts 中实现:

import type { ViewContext } from "@eidos.space/plugin-sdk"
export default async function mount(ctx: ViewContext, root: HTMLElement) {
if (ctx.binding.kind !== "table") return
const table = ctx.binding.table
// 读取视图与字段定义
const meta = await table.read()
// 分页获取符合宿主当前搜索、筛选与排序的记录
const page = await table.getPage({ offset: 0, limit: 100 })
// SQLite 服务端加速的分组聚合计算
const aggregates = await table.aggregate({
metric: { op: "count" },
groupBy: { fieldId: "category" },
})
// 监听表格数据变化
const unsubscribe = await table.observe(() => {
// 记录变更时重新渲染
})
ctx.subscriptions.add(unsubscribe)
return {
dispose() {
root.replaceChildren()
},
}
}

插件可以使用 React、Vue、Svelte 或原生 DOM。使用 React 时:

终端窗口
pnpm add react react-dom
pnpm add -D @types/react @types/react-dom

src/main.tsx 中挂载:

import { createRoot } from "react-dom/client"
import type { ViewContext } from "@eidos.space/plugin-sdk"
import { App } from "./app"
import "./style.css"
export default function mount(ctx: ViewContext, element: HTMLElement) {
const root = createRoot(element)
root.render(<App context={ctx} />)
return {
dispose() {
root.unmount()
},
}
}

使用宿主语义 CSS 变量,无需额外编写亮色与暗色模式逻辑:

.card {
background-color: var(--e-color-bg-subtle);
color: var(--e-color-text-primary);
border: 1px solid var(--e-color-border-subtle);
border-radius: var(--e-radius-md);
}

离线沙箱与第三方库开发避坑指南

Section titled “离线沙箱与第三方库开发避坑指南”

Eidos 插件运行在严格隔离且无外部网络的 iframe 沙箱中。为了确保第三方 UI 组件库(如 @glideapps/glide-data-grid、图表库、富文本编辑器等)正常渲染,建议遵循以下实践:

  1. 务必显式引入组件库样式: 许多基于现代零运行时 CSS(如 Linaria、Vanilla Extract)或纯 CSS 的组件库,其核心布局与绝对定位规则保存在独立 .css 文件中。
    • 务必在入口 main.tsxstyle.css 顶部通过 @import 引入组件库 CSS
    • 如果遗漏核心 CSS,会导致宿主容器高度坍塌(计算高度为 0px),组件内部测量逻辑(如 ResizeObserver / useResizeDetector)将误判尺寸为 0,从而直接跳过 <canvas> 或 DOM 内容挂载,导致“工具栏正常但中间内容完全空白”。
  2. 纯净 ESM 依赖与避免动态 require
    • 宿主打包器会将代码和依赖静态编译为自包含 ESM 模块。
    • 避免在浏览器端源码中编写 CommonJS 风格的动态 require(),统一使用标准静态 import
  3. 严格遵守文档并发与冲突协议
    • 插件通过 const snapshot = await ctx.binding.document.read() 获取当前快照及版本号 snapshot.version
    • 编辑时调用 await ctx.binding.document.edit({ text, expectedVersion: snapshot.version, label: "修改单元格" })
    • 如果返回 { status: "stale" },说明该文件在编辑期间已被外部(或宿主内置编辑器)修改。插件应重新读取最新快照进行合并或提醒用户,避免盲目覆盖。

插件可以使用独立的二进制持久化键值存储,跨重启和重新加载保留:

plugin.json 中申请配额:

{
"storage": { "maxBytes": 536870912 }
}

在代码中存取:

// 写入数据
await ctx.storage.write(
"config",
new TextEncoder().encode(JSON.stringify({ dark: true }))
)
// 读取数据
const bytes = await ctx.storage.read("config")

沙箱禁止任意网络请求与动态 eval。外部网络必须在 browser.networkOrigins 中精确声明 HTTPS 来源:

{
"browser": {
"networkOrigins": ["https://api.example.com"]
}
}

你可以在插件工程中直接使用 npm scripts,也可以调用 CLI:

终端窗口
# 校验清单元数据、TypeScript 类型与模块依赖
npm run check
# 或者直接调用:
# npx eidos-plugin check .
# eidos plugin check .
# 打包为离线自包含的 .eidos-plugin 归档
npm run pack:plugin
# 或者自定义输出位置:
# npx eidos-plugin pack . --out ./dist/my-plugin-0.1.0.eidos-plugin

打包器执行后将自动生成 <id>-<version>.eidos-plugin 文件。该文件本质是经过 gzip 压缩的标准化 JSON 包,内含 format: 1、完整 manifest 元数据以及编译自包含的模块代码。用户在 Eidos Lite 中点击 Plugins → Install plugin… 即可直接导入,无需安装 Node.js 或连接互联网。

  1. <id>-<version>.eidos-plugin 作为 GitHub Release 附件发布。
  2. 官方注册中心 提交 PR,在 plugins.registry.json 中登记插件 ID、版本号、下载地址和 SHA-256 校验和。
  3. 合并后,所有 Eidos Lite 用户即可在插件市场中直接浏览与安装。