lemon_js_extensions 0.2.1
lemon_js_extensions: ^0.2.1 copied to clipboard
Unified extension package and manifest model for lemon_js and lemon_js_ui.
lemon_js_extensions #
lemon_js_extensions 是 lemon_js 与 lemon_js_ui 之上的统一插件管理层。一个安装包可以
只包含 Core 数据服务、只包含 JSUI 页面,或同时包含两者。Manager 负责安装、恢复、更新、
启停、卸载、按 ID 调用和插件级 KV 隔离;插件管理页面由宿主业务层实现。
安装 #
dependencies:
lemon_js_extensions: ^0.2.1
import 'package:lemon_js_extensions/lemon_js_extensions.dart';
插件目录 #
site_plugin/
├── manifest.json
├── service/
│ └── main.mjs
└── ui/
└── login.mjs
统一 Extension 必须包含 manifest;旧 Core 或 JSUI 裸入口需要通过对应 adapter 显式安装。
最小 manifest #
{
"schemaVersion": 2,
"id": "site.example",
"name": "示例站点",
"description": "提供数据和登录页面",
"version": "1.0.0",
"versionCode": 10000,
"compatibilityCode": "content-source-v1",
"service": {
"entry": "service/main.mjs",
"contract": "content-source/v1",
"publicExports": ["getHome"],
"uiExports": ["submitLogin"]
},
"ui": {
"routes": {
"login": {
"entry": "ui/login.mjs",
"title": "登录"
}
}
},
"flows": {
"authentication": {"route": "login"}
},
"capabilities": {
"required": {"network": 1},
"optional": {"crypto": 1}
},
"permissions": ["network", "storage"]
}
插件可以只实现 contract 的部分可选方法,但必须实现宿主兼容策略声明的必需方法。插件的 内部辅助函数不影响校验。
Core 模块 #
export function getHome() {
return { items: [] };
}
export async function submitLogin(account, password) {
return { ok: true, account };
}
JSUI 调用同插件 Core #
import { ElevatedButton, Page, Text } from 'quickjs_ui';
import pluginService from 'lemon_js_extensions/plugin_service';
import storage from 'lemon_js_extensions/storage';
export default Page({
name: 'LoginPage',
createState() { return { status: '未登录' }; },
build(state, props, page) {
return ElevatedButton({
child: Text(state.status),
onPressed: page.login()
});
},
async login() {
const result = await pluginService.call('submitLogin', 'demo', 'password');
await storage.set('session', result);
return { status: result.ok ? '已登录' : '登录失败' };
}
});
JSUI 只能调用同一 Session 中 manifest 声明的 uiExports,不接收任意插件 ID。Core
不能直接控制 UI;需要交互时应返回业务状态,由 Flutter 决定是否打开 flow。
创建 Manager #
final manager = JsExtensionManager(
constraints: <JsExtensionConstraint>[
JsExtensionConstraint(
compatibilityCode: 'content-source-v1',
requiredPublicExports: const <String>{'getHome'},
optionalPublicExports: const <String>{'search', 'getDetail'},
),
],
);
await manager.restore();
store 可以省略:原生平台默认保存到应用支持目录,Web 默认使用 SharedPreferences Web
后端。宿主可传入自定义 JsExtensionStore。插件 KV 默认使用
JsSharedPreferencesKvStore,并按插件 ID 隔离。
加载、安装和调用 #
final package = await JsExtensionPackage.asset(
manifestAsset: 'assets/extensions/site_plugin/manifest.json',
);
await manager.install(
package,
grantedPermissions: const <String>{'network', 'storage'},
);
final home = await manager.call('site.example', 'getHome');
还支持 file、network、assetZip、fileZip、networkZip 和 zipBytes。裸 Core/JSUI
模块统一使用 moduleAsset、moduleFile 或 moduleNetwork,并通过 adapter 指定模块类型。不同来源最终
都会归一化为 JsExtensionPackage。
打开插件页面:
final installed = manager.find('site.example')!.installed!;
JsExtensionView.route(
session: installed.session,
route: 'login',
initialProps: const <String, Object?>{},
)
第三方 JsUiPlugin 不能序列化,应通过 Manager 的 uiPluginsResolver 在恢复时按
插件 ID 重新注入。
更新与数据迁移 #
Manager 会校验插件 ID、compatibilityCode 和数字 versionCode,默认拒绝同版本和
降级,也不会在尚未调用 restore() 时覆盖 Store 中的同 ID 插件。
插件升级需要修改 KV 结构时,提高 storageVersion 并声明:
{
"storageVersion": 2,
"service": {
"storageMigrationExport": "migrateStorage"
}
}
export async function migrateStorage(fromVersion, toVersion) {
// 使用 lemon_js_extensions/storage 迁移当前插件命名空间。
}
迁移或新版本激活失败时,Manager 会恢复旧 KV 和旧安装记录。
资源和能力 #
目录、asset 和网络插件通过 manifest 的 resources 列出需要持久化的非 JS 文件;ZIP
中的非 JS 文件会自动收集。必需宿主能力缺失时安装会抛出包含完整检查结果的
JsExtensionCapabilityException;可选能力缺失只报告,不阻止安装。
Manager 默认提供隔离 KV、网络/Axios 和 Web Crypto。宿主可通过
JsExtensionFeatures 替换或关闭。
管理 API #
final installed = manager.extensions;
await manager.disable('site.example');
await manager.enable('site.example');
await manager.uninstall('site.example', clearStorage: false);
Manager 只提供无 UI 的状态和管理 API,安装列表、权限确认和更新页面由宿主业务层负责。
示例与设计 #
完整可运行工程位于 GitHub;pub 包中保留最小 Dart 示例。