资源与 WASM
插件不只是一段 HTML。你可以把它需要的 CSS、图片、外部 JS、WASM 一起打进 zip,运行时按相对路径加载。
zip 结构
my-plugin.zip
├── plugin.html # 入口(必需)
├── logo.png # 图标(可选)
├── README.md # 说明(可选)
├── style.css
├── helper.js
└── demo.wasm安装时,zip 内除入口外的文件都会作为插件资产保存,并按原相对路径提供访问。
资源 URL
所有资产统一挂在:
/v1/plugins/<pluginId>/file/<相对路径>例如 demo.wasm:
/v1/plugins/my-plugin/file/demo.wasm逻辑层 / 渲染层:用 wad.asset
const url = wad.asset('demo.wasm');
// → http://127.0.0.1:9400/v1/plugins/my-plugin/file/demo.wasm绝对 URL(https://、//)会原样返回,不会被拼接。
面板:手动拼
面板 SDK 没有 wad.asset,用 wad.id 自己拼:
const url = '/v1/plugins/' + encodeURIComponent(wad.id) + '/file/demo.wasm';不要用相对路径
面板地址是 /v1/plugins/<id>/panel,./demo.wasm 会解析成 /v1/plugins/<id>/demo.wasm,这个路径不存在。必须用上面的 /file/ 形式。
加载 WASM
三个上下文都能跑 WebAssembly,区别只在「字节怎么拿回来」。
渲染层 / 逻辑层:wad.loadWasm
<script type="text/wad-render">
wad.loadWasm('demo.wasm').then(function (mod) {
var sum = mod.exports.add(1, 2);
wad.log('wasm add = ' + sum);
}).catch(function (e) {
wad.log('wasm 加载失败: ' + e.message);
});
</script>wad.loadWasm(path, imports):
- 自动拼资源 URL(走
wad.asset); - 按 URL 缓存,重复调用返回同一个 Promise;
- 返回
{ module, instance, exports }; imports可选,原样传给WebAssembly.instantiate。
两个上下文取字节的方式不同,最后都交给 WebAssembly.instantiate:
- 渲染层:
fetch - 逻辑层:
wx.request({ responseType: 'arraybuffer' })
逻辑层用的是运行时 JS 引擎的 WebAssembly。SDK 里对这个能力做了探测:万一某个运行时没把全局 WebAssembly 暴露出来,Promise 会 reject WebAssembly unavailable,这种情况下把同一段计算放到渲染层或面板即可。
面板:直接用浏览器 API
面板 SDK 没有 wad.loadWasm,但面板本身就是 WebView,WebAssembly 是原生的:
const url = '/v1/plugins/' + wad.id + '/file/demo.wasm';
const bytes = await (await fetch(url)).arrayBuffer();
const { instance } = await WebAssembly.instantiate(bytes, {});
instance.exports.add(1, 2);官方示例 gateway-capture 就是这么干的:面板里 fetch('/v1/plugins/gateway-capture/file/assets/gw.wasm') → WebAssembly.instantiate → 调 Rust 导出的解码函数解云网关密文,算法全部在 wasm 里,面板 JS 只负责调用和渲染。
放哪一层
| 需求 | 建议 |
|---|---|
| 纯计算、要拿返回值 | 渲染层或逻辑层,wad.loadWasm |
| 面板要展示算出来的东西 | 面板,原生 WebAssembly |
| 重计算 + 高频调用 | 面板 / 渲染层,逻辑层只做调度,避免卡住小程序 |
官方示例插件「WASM 算力演示」走的就是这套流程:Rust 编译出的 demo.wasm 导出 wad_add / wad_sq / wad_fib / wad_hello,插件里用 wad.loadWasm('demo.wasm') 逐项自检,连从导出的 memory 里读回字符串都演示了。
加载外部脚本
渲染层
wad.loadScript('helper.js').then(function (exports) {
wad.log('helper loaded', exports);
});渲染层用 fetch + eval,脚本里的 return 值会作为结果。
逻辑层
wad.loadScript('helper.js').catch(function (e) {
wad.log(e.message); // E_EVAL_SANDBOX: ...
});逻辑层沙箱可能禁用 eval,这时会返回 E_EVAL_SANDBOX。远程 JS 只在渲染层可靠。
加载 CSS
只有渲染层有:
wad.loadCss('style.css').then(function () {
wad.log('样式已注入');
});会往页面 <head> 里插一个 <link>,按 URL 缓存。
面板里加载资源
面板是普通 WebView,可以直接用浏览器 API:
// 图片
img.src = '/v1/plugins/' + wad.id + '/file/logo.png';
// 文本
const css = await (await fetch('/v1/plugins/' + wad.id + '/file/style.css')).text();
// WASM
const bytes = await (await fetch('/v1/plugins/' + wad.id + '/file/demo.wasm')).arrayBuffer();
const { instance } = await WebAssembly.instantiate(bytes, {});限制
| 项 | 限制 |
|---|---|
| 单次下载 / zip 解压后总量 | 8 MB |
| 单个资产 | 4 MB |
| 图标 | 128 KB |
| README | 512 KB |
| 入口 HTML | 2 MB |
| 逻辑 / 渲染脚本 | 各 256 KB |
路径会做穿越校验,../、绝对路径、保留名都会被拒绝。
一个完整的例子
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="wad:package" content="wasm-demo">
<meta name="wad:name" content="WASM 演示">
</head>
<body>
<div id="out">加载中…</div>
<script src="/ui/plugin-sdk.js"></script>
<script type="text/wad-render">
wad.loadWasm('demo.wasm').then(function (m) {
document.getElementById('out').textContent =
'1 + 2 = ' + m.exports.add(1, 2);
}).catch(function (e) {
document.getElementById('out').textContent = e.message;
});
</script>
</body>
</html>把 demo.wasm 和 plugin.html 一起打成 zip,安装即可。