diff --git a/src/helper.ts b/src/helper.ts index b49e337..f6180c5 100644 --- a/src/helper.ts +++ b/src/helper.ts @@ -2,13 +2,9 @@ import type { TargetEnv } from './options.ts' export const HELPERS_ID: string = '\0wasm-helpers.js' -const nodeFilePath = ` +const nodeFetchFile = ` const { readFile } = process.getBuiltinModule('fs/promises') -const path = process.getBuiltinModule('path') - -return readFile(path.resolve(import.meta.dirname, filepath)).then( - (buffer) => instantiate(buffer, imports) -) +return readFile(fileUrl).then((buffer) => instantiate(buffer, imports)) ` const nodeDecode = ` @@ -16,8 +12,8 @@ const { Buffer } = process.getBuiltinModule('buffer') buf = Buffer.from(src, 'base64') ` -const browserFilePath = ` -return instantiate(fetch(new URL(filepath, import.meta.url)), imports, true); +const browserFetchFile = ` +return instantiate(fetch(fileUrl), imports, true); ` const browserDecode = ` @@ -33,10 +29,10 @@ const autoModule = ` let buf = null const isNode = typeof process !== 'undefined' && process.versions != null && process.versions.node != null -if (filepath && isNode) { - ${nodeFilePath} -} else if (filepath) { - ${browserFilePath} +if (fileUrl && isNode) { + ${nodeFetchFile} +} else if (fileUrl) { + ${browserFetchFile} } if (isNode) { @@ -48,8 +44,8 @@ if (isNode) { const nodeModule = ` let buf = null -if (filepath) { - ${nodeFilePath} +if (fileUrl) { + ${nodeFetchFile} } ${nodeDecode} @@ -57,8 +53,8 @@ ${nodeDecode} const browserModule = ` let buf = null -if (filepath) { - ${browserFilePath} +if (fileUrl) { + ${browserFetchFile} } ${browserDecode} @@ -90,7 +86,7 @@ function envModule(env: TargetEnv) { } export const getHelpersModule = (env: TargetEnv) => ` -export function loadWasmModule(sync, filepath, src, imports) { +export function loadWasmModule(sync, fileUrl, src, imports) { function instantiate(source, imports, stream) { const instantiate = stream ? WebAssembly.instantiateStreaming : WebAssembly.instantiate; return instantiate(source, imports).then(({ instance }) => instance) diff --git a/src/index.ts b/src/index.ts index 6f077c7..12488c1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,7 +24,6 @@ function parseId( export function wasm(options: Options = {}): Plugin { let { maxFileSize = 14 * 1024, - publicPath = '', targetEnv, fileName = '[hash][extname]', } = options @@ -33,7 +32,7 @@ export function wasm(options: Options = {}): Plugin { string, { filename: string - publicFilepath: string + ref: string buffer: Buffer } > = Object.create(null) @@ -115,7 +114,7 @@ export function wasm(options: Options = {}): Plugin { return buffer.length <= maxFileSize } - if (!shouldInline()) { + if (!shouldInline() && !copies[file]) { const hash = createHash('sha1') .update(buffer) .digest('hex') @@ -128,11 +127,16 @@ export function wasm(options: Options = {}): Plugin { .replaceAll('[extname]', ext) .replaceAll('[name]', name) - const publicFilepath = `${publicPath}${outputFileName}` + const ref = this.emitFile({ + type: 'asset', + source: buffer, + name: 'Rolldown WASM Asset', + fileName: outputFileName, + }) copies[file] = { filename: outputFileName, - publicFilepath, + ref, buffer, } } @@ -146,23 +150,23 @@ export function wasm(options: Options = {}): Plugin { handler(code, id) { const [file, , params] = parseId(id) - const publicFilepath = copies[file] - ? JSON.stringify(copies[file].publicFilepath) + const filepathPlaceholder = copies[file] + ? `new URL(${JSON.stringify(`_ROLLDOWN_WASM_PLACEHOLDER_${copies[file].ref}`)}, import.meta.url)` : null let src: string | null const isSync = params.has('sync') const isUrl = params.has('url') if (isUrl) { - if (!publicFilepath) { + if (!filepathPlaceholder) { this.error( '`url` parameter can only be used with non-inlined files.', ) } - return `export default ${publicFilepath}` + return `export default ${filepathPlaceholder}` } - if (publicFilepath === null) { + if (filepathPlaceholder === null) { src = `'${Buffer.from(code, 'binary').toString('base64')}'` } else { if (isSync) { @@ -174,7 +178,7 @@ export function wasm(options: Options = {}): Plugin { const isInit = params.has('init') let codegen = `import { loadWasmModule } from ${JSON.stringify(HELPERS_ID)} ${isInit ? 'export default ' : ''}function __wasm_init(imports) { - return loadWasmModule(${isSync}, ${publicFilepath}, ${src}, imports) + return loadWasmModule(${isSync}, ${filepathPlaceholder}, ${src}, imports) }\n` const mod = this.getModuleInfo(id)! @@ -211,14 +215,29 @@ ${isInit ? 'export default ' : ''}function __wasm_init(imports) { }, }, - generateBundle() { - for (const copy of Object.values(copies)) { - this.emitFile({ - type: 'asset', - source: copy.buffer, - name: 'Rolldown WASM Asset', - fileName: copy.filename, - }) + generateBundle(opt, bundle) { + for (const chunk of Object.values(bundle)) { + if (chunk.type === 'asset') continue + + chunk.code = chunk.code.replaceAll( + /['"]_ROLLDOWN_WASM_PLACEHOLDER_(\w+)['"]/g, + (_, ref) => { + const file = Object.keys(copies).find( + (file) => copies[file].ref === ref, + ) + if (!file) { + this.error( + `Could not find emitted WASM asset for reference ${ref}`, + ) + } + + const relative = path.relative( + path.dirname(chunk.fileName), + copies[file].filename, + ) + return JSON.stringify(relative) + }, + ) } }, } diff --git a/src/options.ts b/src/options.ts index 2870768..1e451b5 100644 --- a/src/options.ts +++ b/src/options.ts @@ -20,10 +20,6 @@ export interface Options { * @default '[hash][extname]' */ fileName?: string - /** - * A string which will be added in front of filenames when they are not inlined but are copied. - */ - publicPath?: string /** * Configures what code is emitted to instantiate the Wasm (both inline and separate) */ diff --git a/tests/__snapshots__/init/browser.snap b/tests/__snapshots__/init/browser.snap index 64dc46a..a52393a 100644 --- a/tests/__snapshots__/init/browser.snap +++ b/tests/__snapshots__/init/browser.snap @@ -2,12 +2,12 @@ [BINARY] // init.js //#region \0wasm-helpers.js -function loadWasmModule(sync, filepath, src, imports) { +function loadWasmModule(sync, fileUrl, src, imports) { function instantiate(source, imports, stream) { return (stream ? WebAssembly.instantiateStreaming : WebAssembly.instantiate)(source, imports).then(({ instance }) => instance); } let buf = null; - if (filepath) return instantiate(fetch(new URL(filepath, import.meta.url)), imports, true); + if (fileUrl) return instantiate(fetch(fileUrl), imports, true); const raw = globalThis.atob(src); const len = raw.length; buf = new Uint8Array(new ArrayBuffer(len)); @@ -20,7 +20,7 @@ function loadWasmModule(sync, filepath, src, imports) { //#endregion //#region tests/fixtures/add.wasm?init function __wasm_init(imports) { - return loadWasmModule(false, "assets/b62665e669ab1545.wasm", null, imports); + return loadWasmModule(false, new URL("assets/b62665e669ab1545.wasm", import.meta.url), null, imports); } //#endregion //#region tests/fixtures/init.js diff --git a/tests/__snapshots__/init/neutral.snap b/tests/__snapshots__/init/neutral.snap index 7d4c297..1dafe4b 100644 --- a/tests/__snapshots__/init/neutral.snap +++ b/tests/__snapshots__/init/neutral.snap @@ -2,16 +2,16 @@ [BINARY] // init.js //#region \0wasm-helpers.js -function loadWasmModule(sync, filepath, src, imports) { +function loadWasmModule(sync, fileUrl, src, imports) { function instantiate(source, imports, stream) { return (stream ? WebAssembly.instantiateStreaming : WebAssembly.instantiate)(source, imports).then(({ instance }) => instance); } let buf = null; const isNode = typeof process !== "undefined" && process.versions != null && process.versions.node != null; - if (filepath && isNode) { + if (fileUrl && isNode) { const { readFile } = process.getBuiltinModule("fs/promises"); - return readFile(process.getBuiltinModule("path").resolve(import.meta.dirname, filepath)).then((buffer) => instantiate(buffer, imports)); - } else if (filepath) return instantiate(fetch(new URL(filepath, import.meta.url)), imports, true); + return readFile(fileUrl).then((buffer) => instantiate(buffer, imports)); + } else if (fileUrl) return instantiate(fetch(fileUrl), imports, true); if (isNode) { const { Buffer } = process.getBuiltinModule("buffer"); buf = Buffer.from(src, "base64"); @@ -29,7 +29,7 @@ function loadWasmModule(sync, filepath, src, imports) { //#endregion //#region tests/fixtures/add.wasm?init function __wasm_init(imports) { - return loadWasmModule(false, "assets/b62665e669ab1545.wasm", null, imports); + return loadWasmModule(false, new URL("assets/b62665e669ab1545.wasm", import.meta.url), null, imports); } //#endregion //#region tests/fixtures/init.js diff --git a/tests/__snapshots__/init/node.snap b/tests/__snapshots__/init/node.snap index 277a9ed..df6e33f 100644 --- a/tests/__snapshots__/init/node.snap +++ b/tests/__snapshots__/init/node.snap @@ -5,14 +5,14 @@ import "node:module"; import.meta.url; //#endregion //#region \0wasm-helpers.js -function loadWasmModule(sync, filepath, src, imports) { +function loadWasmModule(sync, fileUrl, src, imports) { function instantiate(source, imports, stream) { return (stream ? WebAssembly.instantiateStreaming : WebAssembly.instantiate)(source, imports).then(({ instance }) => instance); } let buf = null; - if (filepath) { + if (fileUrl) { const { readFile } = process.getBuiltinModule("fs/promises"); - return readFile(process.getBuiltinModule("path").resolve(import.meta.dirname, filepath)).then((buffer) => instantiate(buffer, imports)); + return readFile(fileUrl).then((buffer) => instantiate(buffer, imports)); } const { Buffer } = process.getBuiltinModule("buffer"); buf = Buffer.from(src, "base64"); @@ -24,7 +24,7 @@ function loadWasmModule(sync, filepath, src, imports) { //#endregion //#region tests/fixtures/add.wasm?init function __wasm_init(imports) { - return loadWasmModule(false, "assets/b62665e669ab1545.wasm", null, imports); + return loadWasmModule(false, new URL("assets/b62665e669ab1545.wasm", import.meta.url), null, imports); } //#endregion //#region tests/fixtures/init.js diff --git a/tests/__snapshots__/rolldown.test.ts.snap b/tests/__snapshots__/rolldown.test.ts.snap index 327db37..df3bf97 100644 --- a/tests/__snapshots__/rolldown.test.ts.snap +++ b/tests/__snapshots__/rolldown.test.ts.snap @@ -21,7 +21,7 @@ exports[`rolldown > url.js 1`] = ` // url.js //#endregion //#region tests/fixtures/url.js -var url_default = "b62665e669ab1545.wasm"; +var url_default = new URL("b62665e669ab1545.wasm", import.meta.url); //#endregion export { url_default as default }; " @@ -50,7 +50,7 @@ var add_exports = /* @__PURE__ */ __exportAll({ memory: () => memory }); function __wasm_init(imports) { - return loadWasmModule(false, "b62665e669ab1545.wasm", null, imports); + return loadWasmModule(false, new URL("b62665e669ab1545.wasm", import.meta.url), null, imports); } const instance = await __wasm_init({}); const memory = instance.exports.memory; diff --git a/tests/e2e.test.ts b/tests/e2e.test.ts index b63d861..2c6a62b 100644 --- a/tests/e2e.test.ts +++ b/tests/e2e.test.ts @@ -4,7 +4,6 @@ import { Buffer } from 'node:buffer' import { readFile } from 'node:fs/promises' import path from 'node:path' import process from 'node:process' -import { pathToFileURL } from 'node:url' import { createContext, SourceTextModule } from 'node:vm' import { rolldownBuild, testFixtures } from '@sxzz/test-utils' import { describe, expect, test } from 'vitest' @@ -90,9 +89,9 @@ async function e2e( } : undefined, }), - initializeImportMeta: (meta) => { + initializeImportMeta(meta) { meta.dirname = process.cwd() - meta.url = pathToFileURL(`${process.cwd()}/`).href + meta.url = new URL('..', import.meta.url).href }, }) await mod.link((spec) => { @@ -106,8 +105,9 @@ async function e2e( const url = entry.includes('url') if (url) { - expect(exported).a('string') - expect(exported).match(/^assets[/\\]\w{16}\.wasm$/) + expect(exported).instanceOf(URL) + expect(exported.protocol).toBe('file:') + expect(exported.pathname).match(/^\/.+\/assets[/\\]\w{16}\.wasm$/) } else if (init) { const ret = exported() if (sync) {