diff --git a/docs/adr/0001-piper-es-module-worker.md b/docs/adr/0001-piper-es-module-worker.md new file mode 100644 index 0000000..c7d79a2 --- /dev/null +++ b/docs/adr/0001-piper-es-module-worker.md @@ -0,0 +1,13 @@ +# ADR 0001: Run Piper in an ES module worker + +## Decision + +Bundle the Piper worker as an ES module and start it with `new Worker(url, { type: 'module' })`. + +## Context + +Piper loads ONNX Runtime Web and its WebAssembly loader dynamically. The current IIFE/classic worker bundle reports `TypeError: "undefined" cannot be parsed as a URL` during WASM backend initialization, even though the pinned same-origin ONNX loader and binary are available. This is consistent with module-relative URL metadata being lost in the classic bundle. + +## Consequences + +The browser must support module workers. Current supported browser targets already require modern WebAssembly and worker support. CI asserts that the worker is built and started in module mode; production acceptance remains the real deployed browser flow. diff --git a/piper-runtime.js b/piper-runtime.js index 82e606d..dfd9cee 100644 --- a/piper-runtime.js +++ b/piper-runtime.js @@ -6,7 +6,9 @@ const pending = new Map(); function getWorker() { if (worker) return worker; - worker = new Worker(workerUrl); + // ONNX Runtime resolves its WASM loader relative to the worker module URL. + // Keep the worker as ESM so import.meta.url remains available at runtime. + worker = new Worker(workerUrl, {type: 'module'}); worker.onmessage = event => { const message = event.data || {}; const request = pending.get(message.id); diff --git a/scripts/build-piper.mjs b/scripts/build-piper.mjs index 5b00a01..9357367 100644 --- a/scripts/build-piper.mjs +++ b/scripts/build-piper.mjs @@ -25,7 +25,7 @@ await build({ entryPoints: [resolve(root, 'piper-worker.js')], outfile: join(workerRoot, 'piper-worker.js'), bundle: true, - format: 'iife', + format: 'esm', platform: 'browser', target: ['es2022'], external: ['fs', 'path'], diff --git a/scripts/check-frontend.sh b/scripts/check-frontend.sh index 6df01a2..5ddce64 100755 --- a/scripts/check-frontend.sh +++ b/scripts/check-frontend.sh @@ -45,7 +45,7 @@ grep -q 'COPY public/tts/piper-wasm-v1/' Dockerfile grep -q 'build:piper' .gitea/workflows/publish.yaml grep -q 'Piper' piper-runtime.js grep -q 'sv_SE-nst-medium' piper-worker.js -grep -q 'new Worker(workerUrl)' piper-runtime.js +grep -q "new Worker(workerUrl, {type: 'module'})" piper-runtime.js grep -q 'worker.js?v=' piper-runtime.js grep -q 'worker.onmessageerror' piper-runtime.js grep -q 'localPiperModelUrl' piper-worker.js diff --git a/tests/piper-worker-format.test.cjs b/tests/piper-worker-format.test.cjs new file mode 100644 index 0000000..0768694 --- /dev/null +++ b/tests/piper-worker-format.test.cjs @@ -0,0 +1,13 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const {readFile} = require('node:fs/promises'); +const {join} = require('node:path'); + +test('Piper worker is built and started as an ES module', async () => { + const [buildScript, runtime] = await Promise.all([ + readFile(join(__dirname, '..', 'scripts', 'build-piper.mjs'), 'utf8'), + readFile(join(__dirname, '..', 'piper-runtime.js'), 'utf8'), + ]); + assert.match(buildScript, /outfile: join\(workerRoot, 'piper-worker\.js'\),\s*bundle: true,\s*format: 'esm'/); + assert.match(runtime, /new Worker\(workerUrl, \{type: 'module'\}\)/); +});