-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathbuild.mjs
More file actions
130 lines (116 loc) · 6.33 KB
/
Copy pathbuild.mjs
File metadata and controls
130 lines (116 loc) · 6.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#!/usr/bin/env node
/**
* Build script: esbuild + post-process patches for onnxruntime-web in Cloudflare Workers.
*
* Root problem: workerd (the Cloudflare Workers runtime) blocks WebAssembly.compile()
* and leaves import.meta.url empty, breaking both WASM loading paths in onnxruntime-web.
*
* Solution: three patches applied after esbuild bundles the code:
*
* Patch 1 — static WASM + model imports (preamble)
* Add `import __ORT_WASM__ from "./ort-wasm-simd-threaded.wasm"` at bundle top.
* Wrangler [[rules]] type="CompiledWasm" makes this resolve to a WebAssembly.Module
* that was compiled at *deploy time* (not at runtime), bypassing the runtime block.
* Same for the .onnx model (type="Data" → ArrayBuffer).
*
* Patch 2 — instantiateWasm callback
* ORT's Emscripten-compiled JS checks for `config.instantiateWasm` before trying
* WebAssembly.compile(). We inject this callback onto the Emscripten Module config
* variable (the one with `numThreads`) right before the factory call.
* The callback calls `new WebAssembly.Instance(module, imports)` directly —
* this works in workerd because the module is *already compiled*.
*
* Patch 3 — kill dynamic import()
* ORT contains `await import(variable)` for proxy worker loading. workerd rejects
* dynamic import specifiers at module analysis time (not runtime), so the entire
* Worker script would fail to load. We replace it with a rejected promise.
*
* Size budget (gzip):
* ort.wasm.bundle.min.mjs ~0.43 MB
* ort-wasm-simd-threaded.wasm ~2.98 MB
* silero_vad.onnx (2.3 MB) ~1.85 MB
* Total: ~5.26 MB (well under 10 MB Worker limit)
*/
import esbuild from "esbuild";
import { cpSync, readFileSync, writeFileSync, mkdirSync, rmSync, existsSync } from "node:fs";
import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const OUT_DIR = resolve(__dirname, ".worker");
const ORT_DIST = resolve(__dirname, "node_modules/onnxruntime-web/dist");
// ── Cleanup ──────────────────────────────────────────────────────────────────
rmSync(OUT_DIR, { recursive: true, force: true });
mkdirSync(OUT_DIR, { recursive: true });
// ── 1. esbuild ──────────────────────────────────────────────────────────────
console.log("[build] esbuild …");
await esbuild.build({
entryPoints: [resolve(__dirname, "src/index.ts")],
bundle: true,
format: "esm",
target: "es2022",
platform: "browser",
outfile: resolve(OUT_DIR, "index.js"),
sourcemap: true,
minify: false, // keep readable for patching; wrangler can minify after
external: ["node:async_hooks", "cloudflare:workers"],
alias: {
// Use the bundle variant: ORT's Emscripten glue + JS inlined, no external loader .mjs
"onnxruntime-web": resolve(ORT_DIST, "ort.wasm.bundle.min.mjs"),
},
});
// ── 2. Copy assets ──────────────────────────────────────────────────────────
console.log("[build] copying assets …");
cpSync(resolve(ORT_DIST, "ort-wasm-simd-threaded.wasm"), resolve(OUT_DIR, "ort-wasm-simd-threaded.wasm"));
cpSync(resolve(__dirname, "model.onnx"), resolve(OUT_DIR, "model.onnx"));
// ── 3. Post-process patches ──────────────────────────────────────────────────
console.log("[build] patching bundle …");
let code = readFileSync(resolve(OUT_DIR, "index.js"), "utf8");
// Patch 1: preamble — static imports for WASM module + model bytes
const preamble = [
`import __ORT_WASM__ from "./ort-wasm-simd-threaded.wasm";`,
`import __MODEL_BYTES__ from "./model.onnx";`,
`globalThis.__ORT_WASM__ = __ORT_WASM__;`,
`globalThis.__MODEL_BYTES__ = __MODEL_BYTES__;`,
].join("\n") + "\n";
code = preamble + code;
// Patch 2: inject instantiateWasm on Emscripten Module config
//
// esbuild preserves the ORT pattern: let CONFIG = { numThreads: N }
// ... (locateFile assignments) ...
// FACTORY(CONFIG).then(
//
// We inject instantiateWasm right before the FACTORY(CONFIG) call.
const configMatch = code.match(/let\s+(\w+)\s*=\s*\{\s*numThreads:\s*(\w+)\s*\}/);
if (!configMatch) {
throw new Error("Could not find Emscripten config `let X = { numThreads: Y }` — ORT version changed?");
}
const configVar = configMatch[1];
const factoryCallRe = new RegExp(`(\\w+)\\(${configVar}\\)\\.then\\(`);
const factoryMatch = code.match(factoryCallRe);
if (!factoryMatch) {
throw new Error(`Could not find factory call FUNC(${configVar}).then( — ORT version changed?`);
}
const instantiateWasmSnippet =
`${configVar}.instantiateWasm = (imports, cb) => {` +
` var inst = new WebAssembly.Instance(__ORT_WASM__, imports);` +
` cb(inst, __ORT_WASM__);` +
` return inst.exports; };`;
code = code.replace(factoryMatch[0], instantiateWasmSnippet + " " + factoryMatch[0]);
console.log(`[build] injected instantiateWasm on config var "${configVar}"`);
// Patch 3: kill variable dynamic import() — workerd rejects at module analysis time
let dynamicImportCount = 0;
code = code.replace(/await import\([\s\S]*?\)/g, (match) => {
if (/await import\(\s*["'`]/.test(match)) return match; // keep static string imports
dynamicImportCount++;
return 'await Promise.reject(new Error("dynamic import disabled in workerd"))';
});
console.log(`[build] patched ${dynamicImportCount} dynamic import() call(s)`);
// ── 4. Write + verify ────────────────────────────────────────────────────────
writeFileSync(resolve(OUT_DIR, "index.js"), code);
console.log(`[build] done — .worker/index.js (${(code.length / 1024).toFixed(0)} KB)`);
for (const f of ["index.js", "ort-wasm-simd-threaded.wasm", "model.onnx"]) {
if (!existsSync(resolve(OUT_DIR, f))) throw new Error(`Missing ${f} in .worker/`);
}
console.log("[build] all assets verified ✓");
console.log("\nDeploy with:");
console.log(" wrangler deploy .worker/index.js --no-bundle");