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

173 Views Asked by At

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 ([email protected]) 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. 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));

1

There are 1 best solutions below

3
VonC On BEST ANSWER

The errors indicate a problem with the MIME type expected for WASM modules and the way the WebAssembly module is being loaded or parsed, as in this issue: the Vite development server is not correctly serving the .wasm file with the application/wasm MIME type.

You can try and configure Vite to properly serve WASM files with the correct MIME type, modifying your vite.config.js file to include custom middleware for handling .wasm files, as in mycelial/mycelial-js issue 25:

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, 'public', 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
      },
    },
  },
})

That custom middleware checks for requests ending in .wasm and serves them with the correct Content-Type header. That assumes the .wasm files are located in a public directory at the root of your project.
And that would be tailored for development with Vite's dev server and assumes your production environment already correctly serves .wasm files with the appropriate MIME type, as the issue does not manifest when using vite build followed by vite preview.


In the end I got it working by using the middleware option, but redirecting it to the Realm folder directly:

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

True: instead of serving .wasm files from a generic or incorrect path (like the public folder or the .vite cache), directly point to the actual location of these files within the node_modules directory. Adjust the path in the middleware function:

const wasmMiddleware = () => {
  return {
    name: 'wasm-middleware',
    configureServer(server) {
      server.middlewares.use((req, res, next) => {
        if (req.url.endsWith('.wasm')) {
          // Directly point to the .wasm file within the realm/dist directory
          const wasmPath = path.join(__dirname, 'node_modules/realm/dist', path.basename(req.url));
          try {
            const wasmFile = fs.readFileSync(wasmPath);
            res.setHeader('Content-Type', 'application/wasm');
            res.end(wasmFile);
          } catch (error) {
            console.error('Failed to load WASM file:', error);
            next();
          }
          return;
        }
        next();
      });
    },
  };
};