fix: component-loader was calling wasm-executor with wrong signature

Rewrote createComponentLoader to directly use createWasiShim inline
instead of calling executeWasm(componentId, buffer, ctx) which doesn't
match wasm-executor's actual signature of executeWasm(input, options).
Also adds Module caching to avoid recompiling WASM on every request.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-16 16:18:50 +08:00
parent 9590083851
commit 5534d60b60
+46 -20
View File
@@ -1,6 +1,10 @@
import { BUILTIN_COMPONENTS } from './constants';
import { createWasiShim } from './wasi-shim';
import type { Bindings, ComponentRunner } from '../types';
// Worker 記憶體快取:componentId → WebAssembly.Module(跨請求共享,避免重複編譯)
const moduleCache = new Map<string, WebAssembly.Module>();
/**
* 建立零件載入器
*
@@ -15,35 +19,57 @@ export function createComponentLoader(env: Bindings) {
const builtin = BUILTIN_COMPONENTS.get(componentId);
if (builtin) return builtin;
// 層 2:從 WASM_BUCKET R2 讀取
// 層 2:從 WASM_BUCKET R2 讀取(快取 Module 避免重複編譯)
const wasmKey = `${componentId}/${componentId}.wasm`;
const wasmObj = await env.WASM_BUCKET.get(wasmKey);
if (wasmObj) {
const wasmBuffer = await wasmObj.arrayBuffer();
return createWasmRunner(componentId, wasmBuffer, env);
}
// 層 3:找不到
let wasmModule = moduleCache.get(componentId);
if (!wasmModule) {
const wasmObj = await env.WASM_BUCKET.get(wasmKey);
if (!wasmObj) {
throw new Error(
`零件 ${componentId} 不存在。\n` +
`請確認 ${wasmKey} 已上傳至 WASM_BUCKET。\n` +
`修復:執行 acr parts 查看可用零件清單。`
);
};
}
const buffer = await wasmObj.arrayBuffer();
wasmModule = await WebAssembly.compile(buffer);
moduleCache.set(componentId, wasmModule);
}
/**
* 建立 WASM 零件執行器
* 使用 WASI preview1 stdin/stdout JSON I/O 模型
*/
function createWasmRunner(
componentId: string,
wasmBuffer: ArrayBuffer,
_env: Bindings,
): ComponentRunner {
const compiledModule = wasmModule;
return async (ctx: unknown): Promise<unknown> => {
// 動態 import wasm-executor(避免頂層 import 造成 Worker 啟動問題)
const { executeWasm } = await import('./wasm-executor');
return executeWasm(componentId, wasmBuffer, ctx);
const stdinJson = JSON.stringify(ctx);
const shim = createWasiShim(stdinJson);
const instance = await WebAssembly.instantiate(compiledModule, shim.imports);
const memory = instance.exports.memory as WebAssembly.Memory | undefined;
if (memory) shim.setMemory(memory);
const exports = instance.exports as Record<string, unknown>;
const entryFn = (exports._start ?? exports.main) as (() => void) | undefined;
if (typeof entryFn !== 'function') {
throw new Error(`WASM 零件缺少 _start 或 main export${componentId}`);
}
try {
entryFn();
} catch (e) {
// proc_exit(0) 拋出 "wasm exit: 0",視為正常結束
if (!(e instanceof Error && e.message === 'wasm exit: 0')) {
throw e;
}
}
const stdout = shim.getStdout().trim();
if (!stdout) throw new Error(`WASM 零件沒有輸出(stdout 為空):${componentId}`);
try {
return JSON.parse(stdout);
} catch {
throw new Error(`WASM 零件輸出不是合法 JSON${stdout.slice(0, 200)}`);
}
};
};
}