How to set Webpack 5 in Next.js to use a WASM library compiled for Node.js?

1.3k views Asked by At

Context

I developed a library called wasmetric-crypto in Rust using wasm-bindgen and wasm-pack. On the other hand, I developed a web app with Next.js 12.1 and installed locally via npm wasmetric-crypto. I compiled wasmetric-crypto to the Node.js target because I’d like to use this library into the API Routes of my web app and a dependency of this library (rand crate) imports the module crypto from Node.js.

Problem

In the wasmetric-crypto source code a function called encrypt calls to the function rand::thread_rng() and internally it imports the module crypto from Node.js to perform some operations. But when I execute the Next.js app the WASM file is not found by its JS file and my app crashes. Note that compile wasmetric-crypto to the bundler target is not a solution because the previous error is not reproduced anymore but when I call to the wasmetric-crypto function encrypt, the web app crashes with the error could not initialize thread_rng: Node.js crypto module is unavailable because the rand crate tries to import crypto but it wasn't compiled for Node.js so the module is not available.

Expected solution

Configure Webpack to copy the WASM file (keeping the original filename) next to the JS file that reads it.

Attachments

wasmetric-crypto/Cargo.toml

[package]
name = "wasmetric-crypto"
version = "0.1.0"
authors = ["David Ignacio Espinoza Saavedra"]
edition = "2021"

[lib]
crate-type = ["cdylib", "rlib"]

[features]
default = ["console_error_panic_hook"]

[dependencies]
# Enable JS feature for getrandom to support wasm32-unknown-unknown
getrandom = { version = "^0.2.5", features = ["js"] }
js-sys = "^0.3.56"
rsa = "^0.5.0"
rand = { version = "^0.8.5", features = ["getrandom"] }
sha2 = "^0.10.2"
wasm-bindgen = "^0.2.79"
console_error_panic_hook = { version = "^0.1.7", optional = true }
wee_alloc = { version = "^0.4.5", optional = true }

[dev-dependencies]
wasm-bindgen-test = "^0.3.29"

[profile.release]
opt-level = "s"

wasmetric-crypto/lib.rs

pub fn encrypt(&self, data: &Uint8Array) -> JsResult<ArrayBuffer> {
    match &self.public_key {
        Some(public_key) => {
            let mut rng = rand::thread_rng(); // Error is raised here
            // Some code
        }
        None => Err(JsError::new("The public key was not set to this instance")),
    }
}

wasmetric-crypto/pkg/wasmetric_crypto.js

// Snippet of code generated by wasm-pack where the WASM file is read
const path = require('path').join(__dirname, 'wasmetric_crypto_bg.wasm');
const bytes = require('fs').readFileSync(path);

const wasmModule = new WebAssembly.Module(bytes);
const wasmInstance = new WebAssembly.Instance(wasmModule, imports);
wasm = wasmInstance.exports;
module.exports.__wasm = wasm;

app/package.json

{
  "name": "ferrisdrive_file-repository",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "next": "^12.1.0",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "wasmetric-crypto": "file:../vendor/wasmetric-crypto/pkg",
  }

app/next.config.js

module.exports = {
  reactStrictMode: true,
  webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
    config.experiments = {
      asyncWebAssembly: true,
      layers: true,
    };
    return config;
  },
};

app/pages/api/files/index.js

import { WASMetricCrypto } from "wasmetric-crypto";
async function encryptSignature(signature) {
  const publicKeyBuffer = await fs.readFile("secrets/key.pub");
  const crypto = WASMetricCrypto.withPublicKey(
    bufferToUint8Array(publicKeyBuffer, publicKeyBuffer.byteOffset)
  );
  return crypto.encrypt(bufferToUint8Array(signature, signature.byteOffset)); // Call to encrypt from WASM
}

This is the .next folder generated by Webpack and wasmetric_crypto_bg.wasm should be located next to files.js because that JS file reads the WASM file when the app is bundled.

Some other ideas to resolve my problem are welcome

0

There are 0 answers