演示

插件前端与后端资源

随插件打包静态文件:后端放在 backend/assets/ 并用 AppFolder;前端 URL 用 PluginAssets.network。不要硬编码 APP_DATA_FOLDER;不要在运行时写入 asset 目录。

目标与何时使用

将模板、种子 JSON、Logo 以及其他随插件包分发的只读文件作为打包资源。应用运行期间创建或更新的内容使用插件数据目录。

  • 后端只读 — plugins/<name>/backend/assets/
  • 前端 URL — PluginAssets.network(metadata, relativePath)
  • 运行时写入 — AppFolder.getPluginDataFolder(metadata.name),不是 asset 目录

1. 前置条件

需要 metadata.name / pluginVersion 一致的插件,以及 AppFolder / PluginAssets 辅助。

  • module.metadata.json — name 与 pluginVersion 与已安装包一致
  • 后端 — @quan-erp/shared-backend-core(AppFolder)
  • 前端 — @quan-erp/shared-frontend-core(PluginAssets)
  • 更改打包资源后重建并重新安装(或 watch 刷新)

2. 后端资源

只读文件放在 plugins/<name>/backend/assets/。CLI 会打包到 available/installed 插件中。用 AppFolder 解析路径——不要硬编码 APP_DATA_FOLDER。

  • 源码 — plugins/<name>/backend/assets/…
  • 运行时路径 — getPluginAssetFolder(name, version)
  • 可写运行时文件 — getPluginDataFolder(name)
  • 安装后不要写入 asset 目录 — 随版本替换
SHsource layout
1plugins/<plugin-name>/ 2└── backend/ 3 ├── assets/ 4 │ ├── invoice-template.html 5 │ └── seed/ 6 │ └── defaults.json 7 └── src/
TSXread bundled file
1import { AppFolder } from "@quan-erp/shared-backend-core"; 2import metadata from "../../module.metadata.json" with { type: "json" }; 3import fs from "node:fs/promises"; 4import path from "node:path"; 5 6const assetDir = AppFolder.getPluginAssetFolder( 7 metadata.name, 8 metadata.pluginVersion, 9); 10const template = await fs.readFile( 11 path.join(assetDir, "invoice-template.html"), 12 "utf8", 13);
TSXwrite runtime data
const dataDir = AppFolder.getPluginDataFolder(metadata.name); await fs.mkdir(dataDir, { recursive: true }); await fs.writeFile(path.join(dataDir, "export.csv"), csv, "utf8");

3. 前端资源

用 @quan-erp/shared-frontend-core 的 PluginAssets.network(metadata, path) 为 UI 构建绝对 URL。保持相对路径稳定,并与打包内容对齐。

  • 从 @quan-erp/shared-frontend-core 导入 PluginAssets
  • 传入插件其他位置使用的同一 module.metadata.json
  • path 相对于栈所暴露的打包前端 / 资源树
TSXPluginAssets
1import { PluginAssets } from "@quan-erp/shared-frontend-core"; 2import metadata from "../module.metadata.json" with { type: "json" }; 3 4export function PluginLogo() { 5 const src = PluginAssets.network(metadata, "images/logo.png"); 6 return <img src={src} alt={metadata.name} />; 7}

4. 验证与规则

  • watch / build 后 available-plugins(Install 后 installed-plugins)中存在资源
  • getPluginAssetFolder 可解析,并对已知相对路径 readFile 成功
  • PluginAssets.network 在浏览器中返回可加载 URL
  • 资源随插件包版本 — 变更后需重建并重新安装
  • 持久生成文件放在 getPluginDataFolder,不是 assets
  • 保持相对路径稳定,使后端 AppFolder 与前端 PluginAssets 对齐
  • 隔离 — 只写入 getPluginDataFolder(yourPluginName);不要硬编码 /app-data/…

常见错误

  • 硬编码 APP_DATA_FOLDER 或 /app-data/... 而不是用 AppFolder
  • 运行时把导出 / 报告写入 asset 目录
  • 升级后向 getPluginAssetFolder 传入错误的 pluginVersion
  • 改资源后未 rebuild / reinstall(或 watch 刷新)
  • 前端 path 与打包资源相对路径不一致
  • 把持久数据存在应用临时目录