WebAssembly threads
Use this when the page needs SharedArrayBuffer: threaded WebAssembly, or a Worker that works on memory the page also reads.
Browsers expose SharedArrayBuffer only to a cross-origin isolated page. The page earns that from two response headers on the HTML document, Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp. Ecopages sets headers from the headers option, and builds a Worker whose URL is written the way the Web Workers guide describes.
Serve the headers
A rule is a source pattern and the headers to add. /** matches every path, so the page, its scripts and the Worker script all carry them.
headers: [
{
source: '/**',
headers: {
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp',
},
},
],The headers option reference lists the pattern syntax and how rules combine: Response headers.
Start the Worker
The page script allocates shared memory, checks that the page is isolated, and hands the memory to a Worker. The memory is a WebAssembly.Memory created with shared: true, the same object a threaded WebAssembly module imports; its buffer is a SharedArrayBuffer.
import type { CounterRequest } from './counter.worker';
const output = document.querySelector<HTMLElement>('[data-result]');
function show(text: string): void {
if (output) output.textContent = text;
}
if (!self.crossOriginIsolated) {
show('not isolated');
} else {
const memory = new WebAssembly.Memory({ initial: 1, maximum: 1, shared: true });
const worker = new Worker(new URL('./counter.worker.ts', import.meta.url), { type: 'module' });
worker.addEventListener('message', () => {
show(`isolated, counter ${Atomics.load(new Int32Array(memory.buffer), 0)}`);
});
worker.addEventListener('error', (event) => show(`worker error ${event.message}`));
worker.postMessage({ memory, increments: 1000 } satisfies CounterRequest);
}Declare the script on the Page so the build bundles it:
import { eco } from '@ecopages/core';
export default eco.page({
metadata: () => ({ title: 'WebAssembly threads', description: 'Shared memory between the page and a Worker.' }),
dependencies: { scripts: ['../lib/start-counter.ts'] },
render: () => (
<main>
<h1>WebAssembly threads</h1>
<output data-result>waiting</output>
</main>
),
});The Worker adds to the counter with Atomics.add, which is safe while other threads touch the same cell:
export type CounterRequest = { memory: WebAssembly.Memory; increments: number };
addEventListener('message', (event: MessageEvent<CounterRequest>) => {
const counter = new Int32Array(event.data.memory.buffer);
for (let index = 0; index < event.data.increments; index += 1) {
Atomics.add(counter, 0, 1);
}
postMessage('done');
});ecopages build writes the Worker as its own hashed file under dist/client/assets and rewrites the URL to it, and ecopages dev serves it through the dev transform server.
For a real threaded module, instantiate it in each Worker with this memory as its imported env.memory. The fixture stops short of shipping a .wasm binary so it stays free of a toolchain.
What the test checks
The fixture's end-to-end test opens the page under ecopages dev and under ecopages preview, in a browser. It asserts that the page response carries both headers, that self.crossOriginIsolated is true, that the Worker script response carries Cross-Origin-Embedder-Policy as well, and that the page shows a counter of 1000 written by the Worker.
Caveats
- Without isolation the code does not fall back.
self.crossOriginIsolatedisfalsewhen a header is missing, andnew SharedArrayBufferthrows. The fixture showsnot isolatedin that case. - Cross-origin resources break. With
require-corp, every cross-origin image, script, font or iframe the page loads must sendCross-Origin-Resource-Policy: cross-originor a matching CORS response, or the browser blocks it. Same-origin files are unaffected. - Scope the rule wider than the page. The Worker script is loaded under the page's policy, so it needs the embedder header too. A rule on
/**covers it; a rule limited to HTML paths would not. - A static export carries no headers.
ecopages buildoutput served by another host has none of this. Set the same two headers on the host, for example in a_headersfile or the platform settings. See Deployment. - The Worker URL has rules. It must be a string literal starting with
./or../inside thenew Worker(...)call, with{ type: 'module' }, as the Web Workers guide lists.