fix(rolldown-plugin): inline wasm/bin/text content in dev SSR#67
Open
Cyberistic wants to merge 1 commit into
Open
fix(rolldown-plugin): inline wasm/bin/text content in dev SSR#67Cyberistic wants to merge 1 commit into
Cyberistic wants to merge 1 commit into
Conversation
The `additional-modules` plugin rewrites `import 'foo.wasm'` (and .bin, .txt, .html, .sql) to a virtual `__CLOUDFLARE_MODULE__CompiledWasm__/<abs> __CLOUDFLARE_MODULE__` URL so the bundler can inline the file's content as an asset at build time. In dev (no `renderChunk`), Vite's Node-side SSR module runner still resolves that virtual ID and then tries to load it. The 'file' portion is just a marker — vite's `transformRequest` runs, finds no real file on disk, and throws `Failed to load url __CLOUDFLARE_MODULE__...`. Fix: add a `load` hook that matches the virtual id, reads the .on-disk .wasm / .bin / .txt file here in the plugin (Vite Node main process), and inlines the bytes as a base64 string literal in the returned module source. Why base64: Vite evaluates the returned source inside `new AsyncFunction(...)`, where top-level `import` statements are illegal. The returned source has no imports — it builds the buffer from the inlined string with `Buffer.from(__b64, 'base64')`. Why CompiledWasm / Text / Data each get their own branch: `CompiledWasm` and `Data` should produce binary buffers; `Text` is utf-8 text. Each branch emits a default export of the correct shape for its consumer.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was broken
@distilled.cloud/cloudflare-rolldown-plugin'sadditional-modulesplugin rewritesimport "foo.wasm"(and .bin, .txt, .html, .sql) to a virtual URL like__CLOUDFLARE_MODULE__CompiledWasm__/abs/path/foo.wasm__CLOUDFLARE_MODULE__and marks itexternal: true. At build time therenderChunkhook reads the file and writes it as a real asset.In dev mode
renderChunknever runs, so when Vite's Node-side SSR module runner tries to load the virtual id, the file doesn't exist on disk and Vite throwsERR_LOAD_URLwithFailed to load url __CLOUDFLARE_MODULE__CompiledWasm__/.../wa-sqlite.wasm... Does the file exist?. The error propagates into the workerd module runner, h3/Nitro catches it, and the user only sees the opaque{"status":500,"unhandled":true,"message":"HTTPError"}JSON.That's why every dev request for any module that does
import "*.wasm", it fails in alchemy v2 even though the same consumer worked in alchemy v1 (where the dev path ran through workerd, not Vite's SSR module runner)What this PR changes
Adds a
loadhook toadditionalModulesPluginthat matches the virtual id, reads the on-disk file synchronously in the plugin (which runs in Vite's Node main process), and inlines the bytes as a base64 string literal in the returned module source. Vite's module runner then evaluates the returned source inside itsnew AsyncFunction(...)wrapper.Why base64 + no
importstatements: the wrapper is a function body, where top-levelimportis illegal. The returned source has no imports, it builds the binary withBuffer.from(__b64, 'base64')and exports it as default. Three branches (CompiledWasm/Text/Data) because each consumer expects a different export shape:CompiledWasmBuffer(binary)Textstring(utf-8)DataWhy this didn't happen in alchemy v1
v1 didn't ship a vite-plugin-based dev server, it spawned
vite devas a subprocess and read itswrangler.jsonfor the worker config. Thevite:react-babelplugin and thecloudflare-rolldown-pluginadditional-moduleschain were never loaded, so the virtual__CLOUDFLARE_MODULE__CompiledWasm__/...URL was never produced and the SSR load never tried to resolve a wasm through a virtual id. The dev server was workerd, not Vite's SSR module runner.This PR fixes the v2 path so the same example works locally again.