# 插件规格指标
本文定义了什么是秋之盒插件:它的封装格式、必须和可选的文件、宿主如何解析和加载它、以及插件内部的组成结构。
# 什么是秋之盒插件
秋之盒插件是一个 .atmb 文件——本质是 ZIP 格式归档。宿主(@autumnbox/app)在启动时或用户手动安装时读取 .atmb 文件,解压、执行其中的 JavaScript 代码,将插件提供的 App、Card、Service 注册到运行时。
一句话定义:.atmb = ZIP(package.json + UMD bundle + 可选 resources/)。
# .atmb 文件结构
my-plugin.atmb (ZIP 归档)
│
├── package.json ← 必须。插件元数据和加载配置
├── index.js ← 必须。UMD 格式的 JavaScript bundle
│
├── resources/ ← 可选。随插件分发的静态资源
│ ├── icon.png ← 插件图标(128×128 PNG,透明背景)
│ ├── lang/ ← 国际化语言文件(自动扫描注册)
│ │ ├── zh-CN.json
│ │ └── en-US.json
│ └── ... ← 其他自定义资源(模板、二进制等)
│
└── assets/ ← 可选。Vite 构建产物(worker、WASM 等)
└── ...
# 必须文件
# package.json
插件的身份证。宿主通过 package.json 识别插件、确定入口文件、读取元数据。
{
"name": "@myplugins/my-toolkit",
"version": "1.0.0",
"description": "A powerful toolkit for Android devices",
"author": "Developer",
"license": "MIT",
"main": "index.js",
"repository": "https://github.com/dev/my-toolkit",
"autumnbox": {
"apps": [
{ "export": "ToolkitApp" }
],
"cards": [
{ "export": "StatusCard" }
],
"services": [
{ "className": "ToolkitService", "export": "ToolkitService" }
],
"hasPluginMain": true
}
}
| 字段 | 必须 | 说明 |
|---|---|---|
name | 是 | 插件唯一标识(pluginPackageName)。一旦发布不可更改 |
version | 是 | 语义化版本号 |
main | 否 | 入口文件路径,默认 index.js |
description | 否 | 插件描述(未提供翻译时的回退值) |
author | 否 | 作者名(未提供翻译时的回退值) |
license | 否 | 开源许可证 |
repository | 否 | 源码仓库地址(字符串或 { type, url } 格式) |
autumnbox | 否 | SDK 构建时自动生成的注册元数据 |
关于 name
name 字段就是 pluginPackageName——插件在整个系统中的唯一身份来源。没有额外的 pluginId、id、manifest.json。
autumnbox 字段详解:
autumnbox 字段由 autumnbox-sdk build 根据约定目录自动生成,不需要手动编写:
| 子字段 | 说明 |
|---|---|
apps | src/apps/ 目录中导出的 AutumnApp 对象列表 |
cards | src/cards/ 目录中导出的 AutumnCard 对象列表 |
services | src/services/ 目录中导出的 Service 类列表 |
hasPluginMain | 是否存在 src/main.ts 入口函数 |
# index.js(UMD Bundle)
插件的可执行代码。autumnbox-sdk build 将 TypeScript 源码编译为 UMD 格式的单文件 bundle。
关键特性:
- UMD 格式:通过
new Function('require', 'module', 'exports', code)执行 - 共享模块已 external 化:宿主通过
ModuleRegistry提供共享依赖,插件不需要打包这些模块
宿主提供的共享模块:
| 模块 | 说明 |
|---|---|
react | React 19 |
react-dom | React DOM |
react/jsx-runtime | JSX 运行时 |
antd | Ant Design UI 库 |
@ant-design/icons | Ant Design 图标 |
@autumnbox/interfaces | 接口和 Handle 类型 |
@autumnbox/common | State、ServiceContainer |
@autumnbox/core | 核心 Service 类 |
@autumnbox/app | 应用层 Service 和 Hooks |
@xterm/xterm | xterm.js 终端模拟器 |
@xterm/addon-fit | xterm 自适应插件 |
插件 require() 这些模块时,ModuleRegistry 返回宿主已加载的实例。如果插件 require() 了不在此列表中的模块,会抛出错误。
入口导出:
Bundle 必须导出以下之一(宿主按此顺序检测):
| 导出名 | 说明 |
|---|---|
__autumnbox_entry__ | 约定模式(推荐)。由 SDK 自动生成的入口函数,处理所有注册逻辑 |
main / pluginMain / default | Legacy 模式。手动编写的 PluginMain 函数 |
# 可选文件
# resources/ 目录
随插件分发的只读静态资源。构建时 autumnbox-sdk package 将整个 resources/ 打包进 .atmb。
运行时,宿主将 resources/ 下所有文件解压为 Map<path, Blob>,插件通过 context.getResource(path) 按需懒加载。
resources/lang/ 的特殊地位:
宿主在加载插件时自动扫描 resources/lang/*.json,按文件名识别 locale(如 zh-CN.json → zh-CN),将内容注册到 LanguageService。语言文件中的特殊键 <name>、<description>、<author> 在打包时已被 SDK CLI 替换为带命名空间的 key。
# assets/ 目录
Vite 构建时产生的额外产物(如 Web Worker、WASM 文件等),一般由构建工具自动管理。
# 宿主加载流程
宿主按以下步骤解析和加载 .atmb:
┌──────────────────────────────────────────────────────┐
│ 1. 读取 .atmb 文件(File 对象) │
│ ↓ │
│ 2. JSZip 解压 ZIP │
│ ↓ │
│ 3. 读取 package.json → 提取 pluginPackageName │
│ (已注册过?抛 "already registered") │
│ ↓ │
│ 4. 读取 index.js(或 package.json.main 指定的入口) │
│ ↓ │
│ 5. 提取 resources/ 为 Map<path, Blob> │
│ ↓ │
│ 6. 执行 UMD bundle(new Function + ModuleRegistry) │
│ ↓ │
│ 7. 检测入口模式: │
│ ├─ 有 __autumnbox_entry__ → 约定模式 │
│ └─ 有 main/pluginMain/default → Legacy 模式 │
│ ↓ │
│ 8. 创建插件数据目录 │
│ {autumnHome}/plugins/data/{pluginPackageName}/ │
│ ↓ │
│ 9. 自动加载语言文件 │
│ resources/lang/*.json → LanguageService.load() │
│ ↓ │
│ 10. 解析插件元数据(名称/描述/作者) │
│ 优先从语言 key 读取,回退到 package.json 字段 │
│ ↓ │
│ 11. 构建 PluginContext 并调用入口函数 │
│ 返回的 dispose 函数保存用于卸载 │
│ ↓ │
│ 12. 可选:持久化 .atmb 到 OPFS(刷新后保留) │
└──────────────────────────────────────────────────────┘
# 约定模式 vs Legacy 模式
| 特性 | 约定模式(推荐) | Legacy 模式 |
|---|---|---|
| 入口导出 | __autumnbox_entry__(context) | main(context) / pluginMain(context) |
| 注册方式 | SDK 自动扫描 src/apps/、src/cards/、src/services/ 生成注册代码 | 手动在 main 函数中调用 context.registerApp() 等 |
| 是否需要手写入口 | 不需要。autumnbox-sdk build 自动生成 __entry__.ts | 需要手写 main.ts |
| SDK 版本 | 当前版本 | 向后兼容保留 |
# 插件内部结构
一个插件可以包含三种注册物:
# App(全屏页面)
通过 defineApp() 定义,打开后显示为独立 Tab。
import { defineApp } from '@autumnbox/sdk';
export const MyApp = defineApp({
id: 'my-app',
name: 'app.name.my-app',
icon: 'data:image/svg+xml,...',
component: MyAppView, // React 模式
// 或 mount: (el) => () => {}, // 自定义 DOM 模式
shallSelectAdbDevice: true, // 可选:打开前要求选择设备
singleton: true, // 可选:只允许一个实例
tags: ['tools'], // 可选:分类标签
});
详见 App 详解。
# Card(首页卡片)
通过 defineCard() 定义,始终展示在主界面。
import { defineCard } from '@autumnbox/sdk';
export const StatusCard = defineCard({
id: 'status',
name: 'card.name.status',
order: 10,
component: StatusCardView,
// 或 mount: (el) => () => {},
});
详见 Card 详解。
# Service(无 UI 服务)
通过导出 Service 类定义,提供后台能力。
import { ServiceContainer } from '@autumnbox/sdk/common';
export class MyService {
constructor(private readonly services: ServiceContainer) {}
doSomething(): string {
return 'Hello from MyService';
}
}
Service 在 src/services/ 目录下导出后,SDK 自动扫描并在 __entry__.ts 中生成注册代码。其他插件的 App/Card 可通过 useService(MyService) 消费。
详见 Service 详解。
# main.ts(可选初始化逻辑)
如果需要在加载时执行一次性初始化:
import type { PluginContext } from '@autumnbox/sdk';
export function main(context: PluginContext): (() => void) | void {
console.log(`Plugin ${context.pluginPackageName} loaded`);
// 可选:返回清理函数
return () => console.log('Plugin unloaded');
}
main 在约定模式下是可选的。SDK 检测到 src/main.ts 存在时会在 __entry__.ts 中调用它。
# 开发时的源码结构
开发者在项目中按以下约定组织源码,SDK 构建工具自动扫描生成入口:
my-plugin/
├── src/
│ ├── apps/ ← 导出 AutumnApp 对象(*App)
│ │ ├── ToolkitApp.tsx
│ │ └── SettingsApp.tsx
│ ├── cards/ ← 导出 AutumnCard 对象(*Card)
│ │ └── StatusCard.tsx
│ ├── services/ ← 导出 Service 类(*Service)
│ │ └── ToolkitService.ts
│ ├── main.ts ← 可选:初始化逻辑
│ └── __entry__.ts ← 自动生成,不要手动编辑
├── resources/
│ ├── icon.png
│ └── lang/
│ ├── zh-CN.json
│ └── en-US.json
├── package.json
└── tsconfig.json
# 编译过程
从源码到 .atmb 分为两个阶段:
# 阶段一:autumnbox-sdk build
autumnbox-sdk build 执行以下步骤:
┌─────────────────────────────────────────────────────┐
│ 1. 扫描约定目录 │
│ src/apps/ → 匹配 export const *App │
│ src/cards/ → 匹配 export const *Card │
│ src/services/→ 匹配 export class *Service │
│ src/main.ts → 检查是否存在 │
│ │
│ 2. 生成入口文件 src/__entry__.ts │
│ ├─ import 所有发现的 App/Card/Service │
│ ├─ 生成 __autumnbox_entry__(context) 函数 │
│ │ ├─ 注册 Service(带 deriveServiceName) │
│ │ ├─ 注册 App │
│ │ ├─ 注册 Card │
│ │ └─ 调用 main(context)(如果存在) │
│ └─ 返回 dispose 函数 │
│ │
│ 3. Vite 构建 │
│ ├─ 入口: src/__entry__.ts │
│ ├─ 输出: dist/index.js(UMD 格式) │
│ ├─ 共享模块 external 化(不打包) │
│ └─ React JSX 转换 │
│ │
│ 4. 生成 dist/package.json │
│ ├─ 复制 name/version/description/author 等字段 │
│ └─ 写入 autumnbox 元数据(apps/cards/services 列表)│
│ │
│ 5. i18n 验证 │
│ ├─ 检查 <name> 是否至少在一个语言文件中定义 │
│ └─ 警告未翻译的 App/Card name key │
└─────────────────────────────────────────────────────┘
自动发现的命名规则:
| 目录 | 匹配正则 | 示例 |
|---|---|---|
src/apps/ | export const *App | export const ShellApp |
src/cards/ | export const *Card | export const BatteryCard |
src/services/ | export class *Service | export class AuthService |
支持的文件结构:Name.tsx、Name.ts、Name/index.tsx、Name/index.ts。
Service 名称派生:
Service 注册到 IoC 容器时的名称由类名派生:去掉 Service 后缀,首字母小写。
| 类名 | 注册名 |
|---|---|
AuthenticationService | authentication |
ScrcpyBridgeService | scrcpyBridge |
MyToolService | myTool |
# 阶段二:autumnbox-sdk package
autumnbox-sdk package 将构建产物打包为 .atmb ZIP:
┌──────────────────────────────────────────────────────┐
│ 1. 读取 dist/package.json │
│ │
│ 2. 处理语言文件语法糖 │
│ resources/lang/*.json 中的特殊 key: │
│ "<name>" → "plugin.name.{pluginPackageName}" │
│ "<description>" → "plugin.description.{...}" │
│ "<author>" → "plugin.author.{...}" │
│ │
│ 3. 创建 ZIP 归档(.atmb) │
│ ├─ package.json ← dist/package.json │
│ ├─ index.js ← dist/index.js │
│ └─ resources/ ← 整个 resources/ 目录 │
└──────────────────────────────────────────────────────┘
# 完整命令
# 分步执行
autumnbox-sdk build # TypeScript → UMD + __entry__.ts
autumnbox-sdk package # 打包 → .atmb
# 一步到位(build 内部已集成 package)
autumnbox-sdk build # 默认在构建后自动打包
# PluginContext
每个插件加载时收到独立的 PluginContext:
interface PluginContext {
pluginPackageName: string;
pluginHome: string;
fs: IFileSystem;
serviceContainer: ServiceContainer;
getResource(name: string): Promise<Blob | null>;
registerApp(...apps: AutumnApp[]): () => void;
registerCard(...cards: AutumnCard[]): () => void;
t(key: string): IReadonlyState<string>;
getService<T>(token: ServiceClass<T>): T;
}
| 成员 | 说明 |
|---|---|
pluginPackageName | 插件包名(package.json 的 name),唯一身份标识 |
pluginHome | 插件专属可读写数据目录路径 |
fs | 文件系统接口实例 |
serviceContainer | IoC 容器,获取任意 Service |
getResource | 从 .atmb 内 resources/ 目录懒加载资源(Blob) |
registerApp | 注册 App,返回取消注册函数 |
registerCard | 注册 Card,返回取消注册函数 |
t(key) | 获取响应式翻译状态(IReadonlyState<string>) |
getService | 从 IoC 容器获取 Service 的便捷方法 |
# TypeScript 配置
使用 AutumnBoxPluginTemplate 模板时已预配置。手动配置需添加 SDK 路径映射:
{
"compilerOptions": {
"jsx": "react-jsx",
"moduleResolution": "bundler",
"paths": {
"@autumnbox/sdk": ["./node_modules/@autumnbox/sdk/types/sdk.d.ts"],
"@autumnbox/sdk/hooks": ["./node_modules/@autumnbox/sdk/types/app.d.ts"],
"@autumnbox/sdk/services": ["./node_modules/@autumnbox/sdk/types/core.d.ts"],
"@autumnbox/sdk/common": ["./node_modules/@autumnbox/sdk/types/common.d.ts"],
"@autumnbox/sdk/interfaces": ["./node_modules/@autumnbox/sdk/types/interfaces.d.ts"]
}
},
"include": ["src/**/*.ts", "src/**/*.tsx"]
}
# 下一步
- 非 React 界面 — 自定义 DOM 挂载模式
- Native 机制 — 加载原生共享库
- 资源与文件系统 — 打包资源和插件文件系统
- 构建与发布 — 构建和打包流程