ctx.extensionResource 将当前扩展发行目录里的文件映射成当前宿主可以访问的 URL。扩展不需要知道 Studio 使用的随机端口、安装目录或 Player 的加载位置。
使用此接口的扩展应在 extension.json 中声明 "sdkVersion": ">=1.9.9"。
ctx.asset 访问当前游戏项目的素材;ctx.extensionResource 访问当前扩展包自己的文件。两者的路径空间不同。
发行目录
需要在运行时读取的文件应放在以下目录:
my-extension/
├── extension.json
├── assets/
│ ├── cover.png
│ ├── theme.css
│ └── data.json
└── dist/
├── index.js
├── calculator.js
├── parser-worker.js
└── runtime.wasm
assets/:图片、CSS、JSON、字体等静态文件;
dist/:构建生成的 JavaScript、Worker、WASM 和关联分块。
Studio 保存项目发行物和构建 Player 时会带上 extension.json、assets/、ui/ 和 dist/。如果 entry 指向其他顶层构建目录,例如 build/runtime/index.js,该顶层目录也会作为构建产物带上。除此之外,不要把运行时文件放在普通的自定义顶层目录:它可能在本机开发预览中可用,但不会进入项目发行物。
API
| 方法 | 返回值 | 说明 |
|---|
url(path) | string | 生成扩展资源的绝对 URL,适合图片、CSS、fetch() 和 WASM |
importModule<T>(path) | Promise<T> | 动态导入扩展内的 ESM 模块 |
createWorker(path, options?) | Worker | 创建扩展内的 Worker;默认使用 module Worker |
路径规则
路径始终相对扩展发行根目录:
// 正确
ctx.extensionResource.url("assets/data.json");
ctx.extensionResource.url("dist/parser-worker.js");
// 错误:不要添加 ./、前导 /、协议或 ..
ctx.extensionResource.url("./assets/data.json");
ctx.extensionResource.url("/assets/data.json");
ctx.extensionResource.url("../data.json");
ctx.extensionResource.url("file:///tmp/data.json");
路径中的中文和空格会自动编码。Studio 还会自动附加当前扩展版本,用于开发时刷新缓存;不要拼接或缓存随机端口和版本参数。
url() 只校验路径写法并生成地址,不会发起网络请求,因此不能证明文件存在。需要检查文件时,请使用 fetch() 并判断 response.ok。
图片和 CSS
图片 URL 可以直接交给浏览器元素:
function Cover() {
const ctx = useExtensionContext();
const coverUrl = ctx.extensionResource.url("assets/cover.png");
return <img src={coverUrl} alt="扩展封面" />;
}
动态挂载样式表时,在组件卸载时移除 <link>:
function ThemedPanel() {
const ctx = useExtensionContext();
useEffect(() => {
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = ctx.extensionResource.url("assets/theme.css");
document.head.append(link);
return () => link.remove();
}, [ctx]);
return <section className="extension-panel">内容</section>;
}
JSON 和 WASM
使用 fetch() 读取 JSON,并显式处理 404 等响应:
const url = ctx.extensionResource.url("assets/data.json");
const response = await fetch(url);
if (!response.ok) {
throw new Error(`读取扩展资源失败:${response.status} ${url}`);
}
const data = await response.json();
WASM 响应会使用 application/wasm MIME,可以直接流式实例化:
const wasmUrl = ctx.extensionResource.url("dist/runtime.wasm");
const { instance } = await WebAssembly.instantiateStreaming(
fetch(wasmUrl),
{},
);
动态导入 JavaScript
importModule() 用于加载扩展构建产物中的 ESM 模块:
type CalculatorModule = {
calculate(input: number): number;
};
const calculator =
await ctx.extensionResource.importModule<CalculatorModule>(
"dist/calculator.js",
);
const result = calculator.calculate(21);
目标文件必须是浏览器可执行的 ESM。第三方依赖应由扩展的构建流程打进 dist/,不要在这里填写 npm 包名或电脑上的文件路径。
Worker
使用 createWorker(),不要把 url() 的结果直接传给 new Worker():
function ParserPanel() {
const ctx = useExtensionContext();
useEffect(() => {
const worker = ctx.extensionResource.createWorker(
"dist/parser-worker.js",
{ name: "parser" },
);
worker.postMessage({ text: "待分析文本" });
worker.addEventListener("message", (event) => {
console.log(event.data);
});
return () => worker.terminate();
}, [ctx]);
return null;
}
默认 type 为 "module"。确实需要旧式 Worker 时可以传入 { type: "classic" }。
Electron 的 renderer 仍执行浏览器的同源与 Worker 加载规则。createWorker() 会通过宿主提供的同源引导脚本加载真实入口,避免扩展作者依赖 Studio 的端口或 file: 地址。
错误排查
| 现象 | 检查项 |
|---|
提示“路径不需要 ./ 前缀” | 把 ./assets/data.json 改为 assets/data.json |
fetch() 返回 404 | 确认文件存在,且大小写与路径完全一致 |
| Studio 可用,打包后找不到 | 把文件移动到 assets/,或让构建流程输出到 dist/ |
| 动态模块导入失败 | 确认目标是 ESM,并且它的相对依赖也已输出 |
| Worker 启动失败 | 监听 Worker 的 error 事件,并确认入口没有依赖 Node.js API |
Studio 的程序预览会优先显示可操作的错误摘要;完整的 React 和宿主堆栈可以在“技术堆栈”中展开或复制。