From 94142c3b69a0d2326831fc32b51935732fdda878 Mon Sep 17 00:00:00 2001 From: Pleasurecruise <3196812536@qq.com> Date: Wed, 21 Oct 2026 22:58:59 +0100 Subject: [PATCH 1/3] fix(deploy): bundle framework Workers with Cloudflare module rules and Worker conditions The framework Worker bundle now keeps Wasm, data and text imports as Worker modules, as the Cloudflare Vite plugin does, and resolves dependencies with Worker conditions. --- packages/void/src/cli/cf-build-output.ts | 46 +++++++ packages/void/src/cli/worker-conditions.ts | 9 ++ packages/void/src/cli/wrapper.ts | 3 +- .../void/test/unit/cf-build-output.test.ts | 130 ++++++++++++++++++ 4 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 packages/void/src/cli/worker-conditions.ts diff --git a/packages/void/src/cli/cf-build-output.ts b/packages/void/src/cli/cf-build-output.ts index e67ee17e4..0e102b43c 100644 --- a/packages/void/src/cli/cf-build-output.ts +++ b/packages/void/src/cli/cf-build-output.ts @@ -26,9 +26,11 @@ import { import { type ModuleType, OutputContainerSchema } from '@cloudflare/config'; import ignore from 'ignore'; import { basename, dirname, extname, isAbsolute, join, relative, resolve } from 'pathslash'; +import type { Plugin } from 'vite'; import { migrateBuiltWranglerConfig, record } from './cf-config-migrate.ts'; import { buildContainerImage } from './container-image.ts'; import { readGeneratedWranglerConfigFile } from './generated-wrangler-config.ts'; +import { WORKER_CONDITIONS } from './worker-conditions.ts'; export interface CfBuildOutput { directory: string; @@ -321,6 +323,48 @@ async function prepareNativeBuildOutput( } } +// The additional module rules of the Cloudflare Vite plugin, which does not export them. +const ADDITIONAL_MODULE_RE = /\.(?:wasm(?:\?module)?|bin|txt|html|sql)$/; +const MODULE_REFERENCE_RE = /__VOID_MODULE__(.*?)__VOID_MODULE__/g; + +/** + * Keep Wasm, data and text imports out of the bundle and emit each file beside it, as + * `@cloudflare/vite-plugin` does, so `copyBundle` uploads them as Worker modules. + */ +function additionalModulesPlugin(): Plugin { + return { + name: 'void:additional-modules', + enforce: 'pre', + resolveId: { + filter: { id: [ADDITIONAL_MODULE_RE, /^#/] }, + async handler(source, importer, options) { + const resolved = await this.resolve(source, importer, options); + if (!resolved || !ADDITIONAL_MODULE_RE.test(resolved.id)) { + return resolved; + } + return { id: `__VOID_MODULE__${resolved.id.split('?')[0]}__VOID_MODULE__`, external: true }; + }, + }, + renderChunk: { + filter: { code: /__VOID_MODULE__/ }, + handler(code, chunk) { + return code.replace(MODULE_REFERENCE_RE, (_, path: string) => { + const fileName = this.getFileName( + this.emitFile({ + type: 'asset', + name: basename(path), + originalFileName: path, + source: readFileSync(path), + }), + ); + const importPath = relative(dirname(chunk.fileName), fileName); + return importPath.startsWith('.') ? importPath : `./${importPath}`; + }); + }, + }, + }; +} + /** Bundle a generated Worker entry with Vite, then hand the result to `prepareCfBuildOutput`. */ async function prepareBundledBuildOutput(options: { projectRoot: string; @@ -339,6 +383,8 @@ async function prepareBundledBuildOutput(options: { root: options.projectRoot, publicDir: false, logLevel: 'error', + plugins: [additionalModulesPlugin()], + resolve: { conditions: WORKER_CONDITIONS }, build: { outDir: scratch, emptyOutDir: true, diff --git a/packages/void/src/cli/worker-conditions.ts b/packages/void/src/cli/worker-conditions.ts new file mode 100644 index 000000000..05bd85c15 --- /dev/null +++ b/packages/void/src/cli/worker-conditions.ts @@ -0,0 +1,9 @@ +// workerd's conditions, then a browser build's, so packages pick their Worker builds. +export const WORKER_CONDITIONS: Array = [ + 'workerd', + 'worker', + 'browser', + 'module', + 'import', + 'default', +]; diff --git a/packages/void/src/cli/wrapper.ts b/packages/void/src/cli/wrapper.ts index a302c686b..4ca710ce4 100644 --- a/packages/void/src/cli/wrapper.ts +++ b/packages/void/src/cli/wrapper.ts @@ -18,6 +18,7 @@ import type { MigrationPlanBundle } from '../migrations/plan.ts'; import { voidDbBundlerPlugin } from '../plugin-drizzle.ts'; import { readProjectPaths } from '../project-paths.ts'; import { generateQueueResolverSource } from '../queues/dispatch.ts'; +import { WORKER_CONDITIONS } from './worker-conditions.ts'; import { transformWorkerRequireBundle } from './worker-require-transform.ts'; export type WrapperFeatures = { @@ -312,8 +313,6 @@ async function bundleMigrationHandler( } } -// workerd's conditions, then a browser build's, so packages pick their Worker builds. -const WORKER_CONDITIONS = ['workerd', 'worker', 'browser', 'module', 'import', 'default']; const SQL_WORKER_EXTERNAL = [/^cloudflare:/, /^node:/, /^pgpass$/]; /** pg's workerd socket and its CommonJS pool, resolved from the pg that `fromPath` loads. */ diff --git a/packages/void/test/unit/cf-build-output.test.ts b/packages/void/test/unit/cf-build-output.test.ts index fd3b4e4f3..92417b25f 100644 --- a/packages/void/test/unit/cf-build-output.test.ts +++ b/packages/void/test/unit/cf-build-output.test.ts @@ -562,6 +562,136 @@ it( CF_BUILD_TIMEOUT_MS, ); +it( + 'keeps framework Wasm, text and data imports as Worker modules', + async () => { + const root = mkdtempSync(join(tmpdir(), 'void-cf-framework-wasm-test-')); + roots.push(root); + const output = join(root, 'framework'); + mkdirSync(join(output, 'chunks'), { recursive: true }); + writeFileSync( + join(root, 'package.json'), + JSON.stringify({ type: 'module', imports: { '#plain': './framework/plain.wasm' } }), + ); + writeFileSync( + join(root, 'wrangler.json'), + JSON.stringify({ + name: 'void-framework-test', + account_id: 'account-id', + main: 'framework/_worker.js', + compatibility_date: '2026-09-29', + assets: { directory: 'framework', binding: 'ASSETS' }, + }), + ); + // Imports `wbg`, like wasm-bindgen output. + const bindgenWasm = Buffer.from([ + 0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, 0, 2, 9, 1, 3, 119, 98, 103, 1, 102, 0, 0, + ]); + const plainWasm = Buffer.from([0, 97, 115, 109, 1, 0, 0, 0]); + mkdirSync(join(root, 'node_modules/bindgen'), { recursive: true }); + writeFileSync(join(root, 'node_modules/bindgen/index_bg.wasm'), bindgenWasm); + writeFileSync(join(output, 'plain.wasm'), plainWasm); + writeFileSync(join(output, 'message.txt'), 'text module'); + writeFileSync(join(output, 'data.bin'), Buffer.from([0, 255])); + writeFileSync( + join(output, '_worker.js'), + "import plain from './plain.wasm'; import message from './message.txt'; import data from './data.bin';\n" + + "import { render } from './chunks/render.js';\n" + + 'export default { async fetch() { return new Response(String([plain, message, data, await render()])) } };\n', + ); + writeFileSync( + join(output, 'chunks/render.js'), + "import plain from '#plain';\n" + + "export async function render() { return String(plain) + String(await import('bindgen/index_bg.wasm')); }\n", + ); + writeFileSync(join(output, '.assetsignore'), '_worker.js\n'); + + await withPrepared( + prepareCfFrameworkBuildOutput({ + projectRoot: root, + configPath: join(root, 'wrangler.json'), + accountId: 'account-id', + }), + async (prepared) => { + const built = await readBuildOutput(prepared.directory); + const { modules } = built.workers.default.config.manifest!; + const bundle = getWorkerBundleDir(prepared.directory); + const additional = Object.keys(modules) + .filter((name) => modules[name].type !== 'esm') + .map((name) => [modules[name].type, readFileSync(join(bundle, name))]); + expect(additional).toHaveLength(4); + expect(additional).toEqual( + expect.arrayContaining([ + ['wasm', bindgenWasm], + ['wasm', plainWasm], + ['text', Buffer.from('text module')], + ['data', Buffer.from([0, 255])], + ]), + ); + expectCfDryRun(prepared.directory); + }, + ); + }, + CF_BUILD_TIMEOUT_MS, +); + +it( + 'resolves framework Worker dependencies with Worker conditions', + async () => { + const root = mkdtempSync(join(tmpdir(), 'void-cf-framework-conditions-test-')); + roots.push(root); + const output = join(root, 'framework'); + mkdirSync(output); + writeFileSync(join(root, 'package.json'), JSON.stringify({ type: 'module' })); + writeFileSync( + join(root, 'wrangler.json'), + JSON.stringify({ + name: 'void-framework-test', + account_id: 'account-id', + main: 'framework/_worker.js', + compatibility_date: '2026-09-29', + assets: { directory: 'framework', binding: 'ASSETS' }, + }), + ); + const dual = join(root, 'node_modules/dual'); + mkdirSync(dual, { recursive: true }); + writeFileSync( + join(dual, 'package.json'), + JSON.stringify({ + name: 'dual', + type: 'module', + exports: { workerd: './worker.js', browser: './browser.js', default: './node.js' }, + }), + ); + writeFileSync(join(dual, 'worker.js'), "export const target = 'dual-workerd';\n"); + writeFileSync(join(dual, 'browser.js'), "export const target = 'dual-browser';\n"); + writeFileSync(join(dual, 'node.js'), "export const target = 'dual-node';\n"); + writeFileSync( + join(output, '_worker.js'), + "import { target } from 'dual';\n" + + 'export default { fetch() { return new Response(target) } };\n', + ); + writeFileSync(join(output, '.assetsignore'), '_worker.js\n'); + + await withPrepared( + prepareCfFrameworkBuildOutput({ + projectRoot: root, + configPath: join(root, 'wrangler.json'), + accountId: 'account-id', + }), + async (prepared) => { + const bundle = readFileSync( + join(getWorkerBundleDir(prepared.directory), 'index.js'), + 'utf8', + ); + expect(bundle).toContain('dual-workerd'); + expect(bundle).not.toContain('dual-browser'); + }, + ); + }, + CF_BUILD_TIMEOUT_MS, +); + it( 'accepts a nested hashed Worker entry with a relative module import', async () => { From 21e826bd3f25e1d11c87353cee91744efbdc908c Mon Sep 17 00:00:00 2001 From: LongYinan Date: Thu, 22 Oct 2026 13:17:30 +0800 Subject: [PATCH 2/3] fix(cli): keep framework Worker modules distinct and production-resolved - Emit each Wasm, data or text module once per source file with its own name and extension, so files with the same bytes keep their module type. - Key the external id by a counter, and keep emitted names to safe characters, so quotes in a path no longer break the import string. - Resolve with the 'production' condition, like @cloudflare/vite-plugin, so packages such as esm-env fold their production build. - Replace the reference regex with a small indexOf loop. Co-Authored-By: Claude Opus 5.5 (1M context) --- packages/void/src/cli/cf-build-output.ts | 105 +++++++++++++++--- .../void/test/unit/cf-build-output.test.ts | 28 ++++- 2 files changed, 114 insertions(+), 19 deletions(-) diff --git a/packages/void/src/cli/cf-build-output.ts b/packages/void/src/cli/cf-build-output.ts index 0e102b43c..caabebbcf 100644 --- a/packages/void/src/cli/cf-build-output.ts +++ b/packages/void/src/cli/cf-build-output.ts @@ -324,42 +324,115 @@ async function prepareNativeBuildOutput( } // The additional module rules of the Cloudflare Vite plugin, which does not export them. -const ADDITIONAL_MODULE_RE = /\.(?:wasm(?:\?module)?|bin|txt|html|sql)$/; -const MODULE_REFERENCE_RE = /__VOID_MODULE__(.*?)__VOID_MODULE__/g; +// Hook filters need a RegExp; `additionalModulePath` applies the same rules. +const ADDITIONAL_MODULE_FILTER = /\.(?:wasm(?:\?module)?|bin|txt|html|sql)$/; +const ADDITIONAL_MODULE_EXTENSIONS = new Set(['.wasm', '.bin', '.txt', '.html', '.sql']); +const MODULE_MARKER = '__VOID_MODULE__'; + +/** Keep letters, digits, `.`, `_` and `-`: the name ends up inside a JS import string. */ +function safeFileStem(stem: string): string { + let safe = ''; + for (const char of stem) { + const code = char.charCodeAt(0); + const allowed = + (code >= 48 && code <= 57) || + (code >= 65 && code <= 90) || + (code >= 97 && code <= 122) || + char === '.' || + char === '_' || + char === '-'; + safe += allowed ? char : '_'; + } + return safe || 'module'; +} + +/** The file behind a resolved id that is uploaded as a Worker module, if it is one. */ +function additionalModulePath(id: string): string | undefined { + const query = id.indexOf('?'); + const path = query === -1 ? id : id.slice(0, query); + if (query !== -1 && !(id.slice(query) === '?module' && path.endsWith('.wasm'))) { + return undefined; + } + return ADDITIONAL_MODULE_EXTENSIONS.has(extname(path)) ? path : undefined; +} /** * Keep Wasm, data and text imports out of the bundle and emit each file beside it, as * `@cloudflare/vite-plugin` does, so `copyBundle` uploads them as Worker modules. */ function additionalModulesPlugin(): Plugin { + // The external id carries a key, not the path: Rolldown escapes ids in the output. + const paths: Array = []; + const keys = new Map(); + // Each file keeps its own name and extension. Assets with the same bytes would + // otherwise be merged, and the extension picks the module type. + const fileNames = new Map(); + const usedFileNames = new Set(); return { name: 'void:additional-modules', enforce: 'pre', resolveId: { - filter: { id: [ADDITIONAL_MODULE_RE, /^#/] }, + filter: { id: [ADDITIONAL_MODULE_FILTER, /^#/] }, async handler(source, importer, options) { const resolved = await this.resolve(source, importer, options); - if (!resolved || !ADDITIONAL_MODULE_RE.test(resolved.id)) { + const path = resolved ? additionalModulePath(resolved.id) : undefined; + if (!path) { return resolved; } - return { id: `__VOID_MODULE__${resolved.id.split('?')[0]}__VOID_MODULE__`, external: true }; + let key = keys.get(path); + if (key === undefined) { + key = paths.push(path) - 1; + keys.set(path, key); + } + return { id: `${MODULE_MARKER}${key}${MODULE_MARKER}`, external: true }; }, }, renderChunk: { - filter: { code: /__VOID_MODULE__/ }, + filter: { code: MODULE_MARKER }, handler(code, chunk) { - return code.replace(MODULE_REFERENCE_RE, (_, path: string) => { - const fileName = this.getFileName( + const emit = (path: string): string => { + let fileName = fileNames.get(path); + if (fileName === undefined) { + const extension = extname(path); + const stem = safeFileStem(basename(path, extension)); + fileName = stem + extension; + for (let count = 2; usedFileNames.has(fileName); count++) { + fileName = `${stem}${count}${extension}`; + } + usedFileNames.add(fileName); + fileNames.set(path, fileName); this.emitFile({ type: 'asset', - name: basename(path), + fileName, originalFileName: path, source: readFileSync(path), - }), - ); - const importPath = relative(dirname(chunk.fileName), fileName); - return importPath.startsWith('.') ? importPath : `./${importPath}`; - }); + }); + } + return fileName; + }; + let output = ''; + let cursor = 0; + let start = code.indexOf(MODULE_MARKER); + while (start !== -1) { + const keyStart = start + MODULE_MARKER.length; + const end = code.indexOf(MODULE_MARKER, keyStart); + if (end === -1) { + break; + } + const keyText = code.slice(keyStart, end); + const path = keyText === String(Number(keyText)) ? paths[Number(keyText)] : undefined; + if (path === undefined) { + start = code.indexOf(MODULE_MARKER, keyStart); + continue; + } + const importPath = relative(dirname(chunk.fileName), emit(path)); + output += + code.slice(cursor, start) + + (importPath.startsWith('.') ? importPath : `./${importPath}`); + cursor = end + MODULE_MARKER.length; + start = code.indexOf(MODULE_MARKER, cursor); + } + return output + code.slice(cursor); }, }, }; @@ -384,7 +457,9 @@ async function prepareBundledBuildOutput(options: { publicDir: false, logLevel: 'error', plugins: [additionalModulesPlugin()], - resolve: { conditions: WORKER_CONDITIONS }, + // Like @cloudflare/vite-plugin: Worker conditions plus the build mode. A deploy + // bundle is always a production build, whatever NODE_ENV says. + resolve: { conditions: [...WORKER_CONDITIONS, 'production'] }, build: { outDir: scratch, emptyOutDir: true, diff --git a/packages/void/test/unit/cf-build-output.test.ts b/packages/void/test/unit/cf-build-output.test.ts index 92417b25f..8874093d9 100644 --- a/packages/void/test/unit/cf-build-output.test.ts +++ b/packages/void/test/unit/cf-build-output.test.ts @@ -593,11 +593,15 @@ it( writeFileSync(join(output, 'plain.wasm'), plainWasm); writeFileSync(join(output, 'message.txt'), 'text module'); writeFileSync(join(output, 'data.bin'), Buffer.from([0, 255])); + // Same bytes, different module types; the quote must survive the module id. + writeFileSync(join(output, 'same.txt'), 'same'); + writeFileSync(join(output, 'sa"me.bin'), 'same'); writeFileSync( join(output, '_worker.js'), "import plain from './plain.wasm'; import message from './message.txt'; import data from './data.bin';\n" + + "import sameText from './same.txt'; import sameData from './sa\"me.bin';\n" + "import { render } from './chunks/render.js';\n" + - 'export default { async fetch() { return new Response(String([plain, message, data, await render()])) } };\n', + 'export default { async fetch() { return new Response(String([plain, message, data, sameText, sameData, await render()])) } };\n', ); writeFileSync( join(output, 'chunks/render.js'), @@ -619,13 +623,15 @@ it( const additional = Object.keys(modules) .filter((name) => modules[name].type !== 'esm') .map((name) => [modules[name].type, readFileSync(join(bundle, name))]); - expect(additional).toHaveLength(4); + expect(additional).toHaveLength(6); expect(additional).toEqual( expect.arrayContaining([ ['wasm', bindgenWasm], ['wasm', plainWasm], ['text', Buffer.from('text module')], ['data', Buffer.from([0, 255])], + ['text', Buffer.from('same')], + ['data', Buffer.from('same')], ]), ); expectCfDryRun(prepared.directory); @@ -663,13 +669,25 @@ it( exports: { workerd: './worker.js', browser: './browser.js', default: './node.js' }, }), ); + const modes = join(root, 'node_modules/modes'); + mkdirSync(modes, { recursive: true }); + writeFileSync( + join(modes, 'package.json'), + JSON.stringify({ + name: 'modes', + type: 'module', + exports: { development: './dev.js', production: './prod.js', default: './dev.js' }, + }), + ); + writeFileSync(join(modes, 'dev.js'), "export const mode = 'modes-development';\n"); + writeFileSync(join(modes, 'prod.js'), "export const mode = 'modes-production';\n"); writeFileSync(join(dual, 'worker.js'), "export const target = 'dual-workerd';\n"); writeFileSync(join(dual, 'browser.js'), "export const target = 'dual-browser';\n"); writeFileSync(join(dual, 'node.js'), "export const target = 'dual-node';\n"); writeFileSync( join(output, '_worker.js'), - "import { target } from 'dual';\n" + - 'export default { fetch() { return new Response(target) } };\n', + "import { target } from 'dual'; import { mode } from 'modes';\n" + + 'export default { fetch() { return new Response(target + mode) } };\n', ); writeFileSync(join(output, '.assetsignore'), '_worker.js\n'); @@ -686,6 +704,8 @@ it( ); expect(bundle).toContain('dual-workerd'); expect(bundle).not.toContain('dual-browser'); + expect(bundle).toContain('modes-production'); + expect(bundle).not.toContain('modes-development'); }, ); }, From 1cb2d9f3ad16ba0ca886b1194645776087ee7073 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Thu, 22 Oct 2026 13:27:39 +0800 Subject: [PATCH 3/3] fix(cli): use a random module marker and a Windows-safe test name - Each build picks a random marker for module ids, so app strings that look like an id are no longer rewritten. - The quoted-name test uses `'` on Windows, which does not allow `"`. Co-Authored-By: Claude Opus 5.5 (1M context) --- packages/void/src/cli/cf-build-output.ts | 20 ++++++++++--------- .../void/test/unit/cf-build-output.test.ts | 11 ++++++++-- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/packages/void/src/cli/cf-build-output.ts b/packages/void/src/cli/cf-build-output.ts index caabebbcf..4f2b805c7 100644 --- a/packages/void/src/cli/cf-build-output.ts +++ b/packages/void/src/cli/cf-build-output.ts @@ -1,3 +1,4 @@ +import { randomBytes } from 'node:crypto'; import { cpSync, copyFileSync, @@ -327,7 +328,6 @@ async function prepareNativeBuildOutput( // Hook filters need a RegExp; `additionalModulePath` applies the same rules. const ADDITIONAL_MODULE_FILTER = /\.(?:wasm(?:\?module)?|bin|txt|html|sql)$/; const ADDITIONAL_MODULE_EXTENSIONS = new Set(['.wasm', '.bin', '.txt', '.html', '.sql']); -const MODULE_MARKER = '__VOID_MODULE__'; /** Keep letters, digits, `.`, `_` and `-`: the name ends up inside a JS import string. */ function safeFileStem(stem: string): string { @@ -362,6 +362,8 @@ function additionalModulePath(id: string): string | undefined { */ function additionalModulesPlugin(): Plugin { // The external id carries a key, not the path: Rolldown escapes ids in the output. + // The random marker keeps app strings that look like an id from being rewritten. + const marker = `__VOID_MODULE_${randomBytes(8).toString('hex')}__`; const paths: Array = []; const keys = new Map(); // Each file keeps its own name and extension. Assets with the same bytes would @@ -384,11 +386,11 @@ function additionalModulesPlugin(): Plugin { key = paths.push(path) - 1; keys.set(path, key); } - return { id: `${MODULE_MARKER}${key}${MODULE_MARKER}`, external: true }; + return { id: `${marker}${key}${marker}`, external: true }; }, }, renderChunk: { - filter: { code: MODULE_MARKER }, + filter: { code: marker }, handler(code, chunk) { const emit = (path: string): string => { let fileName = fileNames.get(path); @@ -412,25 +414,25 @@ function additionalModulesPlugin(): Plugin { }; let output = ''; let cursor = 0; - let start = code.indexOf(MODULE_MARKER); + let start = code.indexOf(marker); while (start !== -1) { - const keyStart = start + MODULE_MARKER.length; - const end = code.indexOf(MODULE_MARKER, keyStart); + const keyStart = start + marker.length; + const end = code.indexOf(marker, keyStart); if (end === -1) { break; } const keyText = code.slice(keyStart, end); const path = keyText === String(Number(keyText)) ? paths[Number(keyText)] : undefined; if (path === undefined) { - start = code.indexOf(MODULE_MARKER, keyStart); + start = code.indexOf(marker, keyStart); continue; } const importPath = relative(dirname(chunk.fileName), emit(path)); output += code.slice(cursor, start) + (importPath.startsWith('.') ? importPath : `./${importPath}`); - cursor = end + MODULE_MARKER.length; - start = code.indexOf(MODULE_MARKER, cursor); + cursor = end + marker.length; + start = code.indexOf(marker, cursor); } return output + code.slice(cursor); }, diff --git a/packages/void/test/unit/cf-build-output.test.ts b/packages/void/test/unit/cf-build-output.test.ts index 8874093d9..effaa1b94 100644 --- a/packages/void/test/unit/cf-build-output.test.ts +++ b/packages/void/test/unit/cf-build-output.test.ts @@ -594,13 +594,17 @@ it( writeFileSync(join(output, 'message.txt'), 'text module'); writeFileSync(join(output, 'data.bin'), Buffer.from([0, 255])); // Same bytes, different module types; the quote must survive the module id. + // Windows does not allow `"` in file names. + const quoted = process.platform === 'win32' ? "sa'me.bin" : 'sa"me.bin'; writeFileSync(join(output, 'same.txt'), 'same'); - writeFileSync(join(output, 'sa"me.bin'), 'same'); + writeFileSync(join(output, quoted), 'same'); writeFileSync( join(output, '_worker.js'), "import plain from './plain.wasm'; import message from './message.txt'; import data from './data.bin';\n" + - "import sameText from './same.txt'; import sameData from './sa\"me.bin';\n" + + `import sameText from './same.txt'; import sameData from ${JSON.stringify(`./${quoted}`)};\n` + "import { render } from './chunks/render.js';\n" + + // App text that looks like the old module id stays as it is. + "export const looksLikeId = '__VOID_MODULE__0__VOID_MODULE__';\n" + 'export default { async fetch() { return new Response(String([plain, message, data, sameText, sameData, await render()])) } };\n', ); writeFileSync( @@ -624,6 +628,9 @@ it( .filter((name) => modules[name].type !== 'esm') .map((name) => [modules[name].type, readFileSync(join(bundle, name))]); expect(additional).toHaveLength(6); + expect(readFileSync(join(bundle, 'index.js'), 'utf8')).toContain( + '__VOID_MODULE__0__VOID_MODULE__', + ); expect(additional).toEqual( expect.arrayContaining([ ['wasm', bindgenWasm],