vite icon indicating copy to clipboard operation
vite copied to clipboard

Support wasm in SSR

Open boyeln opened this issue 2 years ago • 5 comments

Description

Currently, the wasm plugin usually get the .wasm file using fetch. This approach doesn't work in SSR. Even if fetch were available, fetching a relative path would not work. This results in errors such as ERR_INVALID_URL when in SSR.

Suggested solution

By adding an extra if cause to the wasm helper, we could check the import.meta.env.SSR to see if we're in SSR mode. If we are, then we could load the file from disk instead of fetching it over the network. Something like this:

const wasmHelper = async (opts = {}, url: string) => {
  if (url.startsWith('data:')) {
    // load from inline base64 string
  } else if (import.meta.env.SSR) {
    // load from disk
  } else {
    // fetch over the network
  }
}

However, there might be problems to using import.meta that I'm not aware of. It seems to work fine for me when I'm testing it, but I've only tested it with SvelteKit.

If using the SSR environment variable isn't an option, then we could of course use some typeof window === "undefined" or perhaps process would be better. But there might be several pitfalls with that, mainly other plugins that polyfills window, process or whatever thing we might use. Perhaps a better approach would be to add the file system logic in a .catch block on the fetch itself, something like:

const response = await fetch(url).catch(async (err) => {
  if (!(err && err.code === "ERR_INVALID_URL")) throw err;
  // load from disk and return new Response(fileBuffer)
}

Another problem with loading from disk is that the URL provided is a URL, and not a file path. Fortunately it contains the file path, but I'm not sure if that would always be the case, or if it might be a better way of resolving the file path. Using the provided url string, it is possible to decode the path using something like url.replace("/@fs", "").

Alternative

A workaround is to use another wasm plugin. I haven't found a vite wasm plugin that works with SSR, so currently we have created our own just to make it work (using the import.meta.env.SSR technique mentioned above). That works great with SvelteKit, but might not work with everything. If it's of any interest to someone else, just let me know and I can see if we could push it to NPM.

I've also raised an issue (https://github.com/Menci/vite-plugin-wasm/issues/4) in the vite-pugin-wasm repo to make SSR work, but it has some challenges regarding ESM to utilize import.meta.

Additional context

No response

Validations

boyeln avatar Jul 01 '22 13:07 boyeln

If anyone finds this issue and needs a quick fix, we've created a vite plugin to use your WASM packages as regular ES modules that also supports SSR. Only supports wasm-pack generated modules at the moment.

boyeln avatar Jan 09 '23 09:01 boyeln

I found this issue because I was trying to understand a regression I found that's blocking me updating from Vite 4.5.0 to Vite 5.2.11. I have a WASM library I built as an ES module with Emscripten, so I have manifold.js and manifold.wasm in my built directory. In a worker I call it like import Module from './built/manifold';

In Vite 4 this was working just fine both in the browser and in my node-based testing with npm test (what I believe you're referring to as SSR). In Vite 5, everything in the browser still works just fine, but npm test gives:

stderr | worker.test.js > Examples > Rounded Frame
TypeError: Invalid URL
    at new URL (node:internal/url:787:36)
    at Module.default (/Users/elalish/Code/manifold/bindings/wasm/examples/built/manifold.js:8:20330)
    at /Users/elalish/Code/manifold/bindings/wasm/examples/worker.ts:17:16
    at InlineWorkerRunner.runModule (file:///Users/elalish/Code/manifold/bindings/wasm/examples/node_modules/vite-node/dist/client.mjs:362:5)
    at InlineWorkerRunner.directRequest (file:///Users/elalish/Code/manifold/bindings/wasm/examples/node_modules/vite-node/dist/client.mjs:346:5)
    at InlineWorkerRunner.cachedRequest (file:///Users/elalish/Code/manifold/bindings/wasm/examples/node_modules/vite-node/dist/client.mjs:189:14)
    at InlineWorkerRunner.executeFile (file:///Users/elalish/Code/manifold/bindings/wasm/examples/node_modules/vite-node/dist/client.mjs:161:12) {
  code: 'ERR_INVALID_URL',
  input: '/built/manifold.wasm'
}

So even though manifold.js is calling manifold.wasm in its own directory, it can't seem to resolve the URL. But this used to work, which makes me wonder if it's actually related to this issue after all. I don't see anything obviously related in the Vite 5 migration guide. Any thoughts?

Also, the Vite docs seem to say a lot about needing plugins for loading WASM, but I'm not using any plugins with Vite 4. Has functionality been removed from core?

elalish avatar May 10 '24 16:05 elalish

in my node-based testing with npm test (what I believe you're referring to as SSR)

@elalish from the error message, it looks like you're referring to Vitest with @vitest/web-worker and that's way more moving parts than just Vite SSR. Can you report on Vitest with https://github.com/vitest-dev/vitest with a reproduction? It's also possible that you're getting a new error due to some change on Vitest.

hi-ogawa avatar May 10 '24 22:05 hi-ogawa

Ah, correct you are! Okay, Vite 5.2.11 is working fine - it must have been the Vitest update that did it. I'll report over there instead - had sort of thought that was all part of the same project.

elalish avatar May 10 '24 23:05 elalish

Done: https://github.com/vitest-dev/vitest/issues/5704

elalish avatar May 11 '24 00:05 elalish