javascriptmongodbvue.jsrealmvite

Web Assembly (WASM) errors in a Vite + Vue app using Realm Web SDK


I'm using MongoDB Realm / App Services to make a web front end for an existing iOS app.

The standard Realm Web SDK only permits authentication and backend functions, so I am trying to use the preview release of the Web Assembly version (realm@12.0.0-browser.2) so that I can use device sync and interact with a Realm object in the way I'm used to.

Details at: Get Started with Realm Web & Atlas Device Sync (Preview)

I am just using the basic app scaffold created by Vite, and then importing Realm in App.vue. I am not (yet) using Realm anywhere else in the code.

import Realm, { App } from "realm";

As advised in the Realm SDK documentation, for the Web Assembly version I had to enable top-level await in vite.config.js:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  esbuild: {
    supported: {
      'top-level-await': true
    },
  },
  optimizeDeps: {
    esbuildOptions: {
      supported: {
        "top-level-await": true
      },
    },
  },
})

So this works without errors when I build and preview (vite build followed by vite preview).

However, when I do npm run dev (vite), the server starts as expected, but there are the following errors in the browser console:

Console errors

[Error] wasm streaming compile failed: TypeError: Unexpected response MIME type. Expected 'application/wasm'
    (anonymous function) (realm.js:726)
[Error] falling back to ArrayBuffer instantiation
    (anonymous function) (realm.js:727)
[Error] failed to asynchronously prepare wasm: CompileError: WebAssembly.Module doesn't parse at byte 0: module doesn't start with '\0asm'
    (anonymous function) (realm.js:717)
[Error] Aborted(CompileError: WebAssembly.Module doesn't parse at byte 0: module doesn't start with '\0asm')
    abort (realm.js:661)
    (anonymous function) (realm.js:718)

This doesn't happen if I build and preview, so I hope it won't actually be a problem, but I don't understand what is going on here and I'm curious about how to fix it for working during the development process.


Edit

Thanks to VonC for the answer (now deleted?). I tried using the custom middleware, but got errors that the file cannot be found.

The .wasm files are in node_modules/realm/dist, but it seems to be looking in node_modules/.vite instead.

This is the error if I use const wasmPath = path.join(__dirname, req.url); (i.e. without the public path component).

ENOENT: no such file or directory, open '<my_local_path>/vite-project/node_modules/.vite/deps/realm-js-wasm.wasm'

In the end I got it working by using the middleware solution and redirecting it to the Realm folder directly:

const wasmPath = path.join(__dirname, 'node_modules/realm/dist', path.basename(req.url));


Solution

  • I got this working by adapting the answer by @VonC. I had to use custom middleware to redirect to the correct location of .wasm files.

    The .wasm files are in node_modules/realm/dist. I got it working by using the middleware solution and redirecting it to the Realm folder directly:

    const wasmPath = path.join(__dirname, 'node_modules/realm/dist', path.basename(req.url));
    

    My final vite.config file:

    import { defineConfig } from 'vite'
    import vue from '@vitejs/plugin-vue'
    import fs from 'fs'
    import path from 'path'
    
    // Custom middleware to serve wasm files with the correct MIME type
    const wasmMiddleware = () => {
      return {
        name: 'wasm-middleware',
        configureServer(server) {
          server.middlewares.use((req, res, next) => {
            if (req.url.endsWith('.wasm')) {
              const wasmPath = path.join(__dirname, 'node_modules/realm/dist', path.basename(req.url));
              const wasmFile = fs.readFileSync(wasmPath);
              res.setHeader('Content-Type', 'application/wasm');
              res.end(wasmFile);
              return;
            }
            next();
          });
        },
      };
    };
    
    export default defineConfig({
      plugins: [vue(), wasmMiddleware()],
      esbuild: {
        supported: {
          'top-level-await': true
        },
      },
      optimizeDeps: {
        esbuildOptions: {
          supported: {
            "top-level-await": true
          },
        },
      },
    })