diff --git a/lib/npm/browser.ts b/lib/npm/browser.ts index d59a6e3ccfb..b3ed9f14e25 100644 --- a/lib/npm/browser.ts +++ b/lib/npm/browser.ts @@ -60,7 +60,10 @@ export const initialize: typeof types.initialize = options => { let wasmURL = options.wasmURL; let useWorker = options.worker !== false; if (!wasmURL) throw new Error('Must provide the "wasmURL" option'); - wasmURL += ''; + wasmURL = new URL( + wasmURL + "", + `${location.origin}${wasmURL.startsWith('.') ? location.pathname : "/"}` + ).href if (initializePromise) throw new Error('Cannot call "initialize" more than once'); initializePromise = startRunningService(wasmURL, useWorker); initializePromise.catch(() => { @@ -71,11 +74,8 @@ export const initialize: typeof types.initialize = options => { } const startRunningService = async (wasmURL: string, useWorker: boolean): Promise => { - let res = await fetch(wasmURL); - if (!res.ok) throw new Error(`Failed to download ${JSON.stringify(wasmURL)}`); - let wasm = await res.arrayBuffer(); let code = `{` + - `let global={};` + + `let global={WASM_URL:"${wasmURL}"};` + `for(let o=self;o;o=Object.getPrototypeOf(o))` + `for(let k of Object.getOwnPropertyNames(o))` + `if(!(k in global))` + @@ -104,8 +104,13 @@ const startRunningService = async (wasmURL: string, useWorker: boolean): Promise } } - worker.postMessage(wasm) - worker.onmessage = ({ data }) => readFromStdout(data) + worker.onmessage = ({ data }) => { + if (data.type === 'done') { + worker.onmessage = ({ data }) => readFromStdout(data) + return + } + throw new Error(data?.error ?? 'Failed to compile wasm code') + } let { readFromStdout, service } = common.createChannel({ writeToStdin(bytes) { diff --git a/lib/npm/worker.ts b/lib/npm/worker.ts index 3772bc49a7b..9b40af8993a 100644 --- a/lib/npm/worker.ts +++ b/lib/npm/worker.ts @@ -1,65 +1,85 @@ // This file is part of the web worker source code declare const ESBUILD_VERSION: string; -declare function postMessage(message: any): void; -onmessage = ({ data: wasm }) => { - let decoder = new TextDecoder() - let fs = (global as any).fs +async function loadWasm( + url: string, + importObject?: WebAssembly.Imports +): Promise { + const response = await fetch(url) + if (!response.ok) + throw new Error(`Failed to download ${JSON.stringify(url)}`) - let stderr = '' - fs.writeSync = (fd: number, buffer: Uint8Array) => { - if (fd === 1) { - postMessage(buffer) - } else if (fd === 2) { - stderr += decoder.decode(buffer) - let parts = stderr.split('\n') - if (parts.length > 1) console.log(parts.slice(0, -1).join('\n')) - stderr = parts[parts.length - 1] - } else { - throw new Error('Bad write') - } - return buffer.length + const mimeType = response.headers.get("Content-Type") ?? ""; // case-insensitive + // wasm mime type header is required for instantiateStreaming api + // https://www.w3.org/TR/wasm-web-api-1/#streaming-modules + const isWasmMime = mimeType.includes("application/wasm") + if (typeof WebAssembly.instantiateStreaming === "function" && isWasmMime) { + return WebAssembly.instantiateStreaming(response, importObject) + } else { + return WebAssembly.instantiate(await response.arrayBuffer(), importObject) } +} - let stdin: Uint8Array[] = [] - let resumeStdin: () => void - let stdinPos = 0 +let decoder = new TextDecoder() +let fs = (global as any).fs - onmessage = ({ data }) => { - if (data.length > 0) { - stdin.push(data) - if (resumeStdin) resumeStdin() - } +let stderr = '' +fs.writeSync = (fd: number, buffer: Uint8Array) => { + if (fd === 1) { + postMessage(buffer) + } else if (fd === 2) { + stderr += decoder.decode(buffer) + let parts = stderr.split('\n') + if (parts.length > 1) console.log(parts.slice(0, -1).join('\n')) + stderr = parts[parts.length - 1] + } else { + throw new Error('Bad write') } + return buffer.length +} - fs.read = ( - fd: number, buffer: Uint8Array, offset: number, length: number, - position: null, callback: (err: Error | null, count?: number) => void, - ) => { - if (fd !== 0 || offset !== 0 || length !== buffer.length || position !== null) { - throw new Error('Bad read') - } +let stdin: Uint8Array[] = [] +let resumeStdin: () => void +let stdinPos = 0 - if (stdin.length === 0) { - resumeStdin = () => fs.read(fd, buffer, offset, length, position, callback) - return - } +onmessage = ({ data }) => { + if (data.length > 0) { + stdin.push(data) + if (resumeStdin) resumeStdin() + } +} - let first = stdin[0] - let count = Math.max(0, Math.min(length, first.length - stdinPos)) - buffer.set(first.subarray(stdinPos, stdinPos + count), offset) - stdinPos += count - if (stdinPos === first.length) { - stdin.shift() - stdinPos = 0 - } - callback(null, count) +fs.read = ( + fd: number, buffer: Uint8Array, offset: number, length: number, + position: null, callback: (err: Error | null, count?: number) => void, +) => { + if (fd !== 0 || offset !== 0 || length !== buffer.length || position !== null) { + throw new Error('Bad read') } - let go = new (global as any).Go() - go.argv = ['', `--service=${ESBUILD_VERSION}`] + if (stdin.length === 0) { + resumeStdin = () => fs.read(fd, buffer, offset, length, position, callback) + return + } - WebAssembly.instantiate(wasm, go.importObject) - .then(({ instance }) => go.run(instance)) + let first = stdin[0] + let count = Math.max(0, Math.min(length, first.length - stdinPos)) + buffer.set(first.subarray(stdinPos, stdinPos + count), offset) + stdinPos += count + if (stdinPos === first.length) { + stdin.shift() + stdinPos = 0 + } + callback(null, count) } + +let go = new (global as any).Go() +go.argv = ['', `--service=${ESBUILD_VERSION}`] + +loadWasm((global as any).WASM_URL, go.importObject) + .then(async ({ instance }) => { + postMessage({ type: 'done' }) + return go.run(instance) + }) + .catch(err => postMessage({ type: 'error', error: err?.message ?? err })) diff --git a/scripts/browser/browser-tests.js b/scripts/browser/browser-tests.js index 115cc598057..89804df6361 100644 --- a/scripts/browser/browser-tests.js +++ b/scripts/browser/browser-tests.js @@ -284,6 +284,10 @@ async function main() { async function runPage(key) { try { const page = await browser.newPage() + page.on('pageerror', error => { + console.error(`❌ page error: ${error?.message ?? error}`) + allTestsPassed = false + }) page.on('console', obj => console.log(`[console.${obj.type()}] ${obj.text()}`)) page.exposeFunction('testFail', error => { console.log(`❌ ${error}`)