-
Notifications
You must be signed in to change notification settings - Fork 182
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Javascript code does not work in ESM context #256
Comments
We have run into a bunch of issues like this before (see #214 #224 #234). If you (or anyone else) have an improvement for our code, we would be happy to accept a PR. The current logic for selecting the implementation is here: Line 59 in 9e2c896
We have CI tests that make sure our code works w/ Web and NodeJS, so I'm confused as to why you are having issues. The code you have above will only be run in a Node environment. That function will not be called if you are running on a web browser. |
Oh wait I think this issue was fixed in #234 as the generated JS bindings should now look like:
not
Can you make sure you are using the latest version of |
The code works in web and nodejs, so long as I compile separate wasm modules for each context. And thus, publish separate npm packages for the web and for nodejs. But I hate that - javascript code works on the web and nodejs (thats kind of the whole point of nodejs). Ideally I'd like to be able to just compile my wasm code once and run it in both a nodejs and web context. And I almost can do that - but I just can't get getrandom to work in both contexts. I'm not sure what the right answer is here from a library design perspective. The current steps I'm taking: lib.rs: use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn foo() -> u32 {
console_error_panic_hook::set_once();
let mut bytes = [0; 4];
getrandom::getrandom(&mut bytes[..]).expect("getrandom failed");
u32::from_be_bytes(bytes)
} Then:
Unfortunately I need to add foo.mjs: import {default as init} from "./pkg/rand_wasm.js"
import {readFileSync} from 'fs'
;(async () => {
let bytes = readFileSync("pkg/rand_wasm_bg.wasm")
let module = await init(bytes)
console.log(module.foo())
})() All my rust code runs fine - but the getrandom call fails:
The reason is that the nodejs codepath is trying to use For now I've given up, and I'm just compiling my wasm module twice (once for node, once for the web). But ideally I'd like to get this fixed. Having a single wasm module which works everywhere would be lovely. Any ideas on how we can make this work? |
Maybe in the long term I could target WASI instead, which provides an API for random. Thats probably a long ways out though :/ Another approach is to detect nodejs then use a dynamic import if we're in ESM mode - let crypto = await import('node:crypto').webcrypto
crypto.getRandomValues(buffer) ... But dynamic imports return a promise, and thats really hard to cope with. :/ |
A cheap workaround is to add this to the end of the generated bindings file: // workaround for https://github.com/rust-random/getrandom/issues/256
import * as crypto from "crypto"
const module = {
require: (string) => {
if(string !== "crypto") throw new Error("Unexpected require " + string)
return crypto
}
} |
So it seems like we cannot support both ES6 modules running on Node.js and Basically, as the object returned by So if we want to support this use-case, we would have to add an additional
The main issue here is that Node and the Web really are different targets, even if they are superficially similar. We've had a ton of issues targeting Node.js in general (targeting the Web has been bad but not as bad). This library attempts to support every Rust target possible, and Node.js has easily has the highest maintenance cost, due to:
|
I've had a bit of a think about it, and I want to suggest another approach: So right now, the logic is something like this: if (process.versions.node != null) {
require('crypto').randomFillSync(...)
} else {
globalThis.crypto.getRandomValues(...)
} There are essentially 3 different environments:
If we invert the logic to do capability detection instead, it would look like this: if (globalThis.crypto.getRandomValues != null) {
globalThis.crypto.getRandomValues(...)
} else if (globalThis.require != null) { // Or keep the existing check for process.version.node
require('crypto').randomFillSync(...)
} Then I could make my code work with a simple shim: import {webcrypto} from 'crypto' // only available from node 16+, but thats not a problem for me.
globalThis.crypto = webcrypto
// ...
wasm.init() This would also work in deno, and any other javascript context which follows web standards. And if nodejs ever implements the web's JS crypto standard, everything will just work. |
Now we look for the standard Web Cryptography API before attempting to check for Node.js support. This allows Node.js ES6 module users to add a polyfill like: ```js import {webcrypto} from 'crypto' globalThis.crypto = webcrypto ``` as described in #256 (comment) Signed-off-by: Joe Richey <joerichey@google.com>
Now we look for the standard Web Cryptography API before attempting to check for Node.js support. This allows Node.js ES6 module users to add a polyfill like: ```js import {webcrypto} from 'crypto' globalThis.crypto = webcrypto ``` as described in #256 (comment) Signed-off-by: Joe Richey <joerichey@google.com>
I've opened #284 which tires to address some of the issues discussed here and implements the detection logic in #256 (comment) @josephg can take a look and let me know if that addresses your conerns? |
Fantastic thanks! I'll take a look in the next couple days |
Looks great - lovely work. I really enjoy the updates to the documentation. Everything looks great when building using For completeness with wasm-pack, I tried building in "bundler" mode. When I do that, it tries to execute this method: export function __wbg_require_b3fbb8db36a15c55() { return logError(function () {
const ret = module.require; // <--- this crashes
return addHeapObject(ret);
}, arguments) }; And this fails with an error:
Personally I'm not concerned, since I fail to see the point of
I'd be happy to ship the change as-is. Thanks for giving this some attention! |
As a follow-up, I just tested the library in wasm-pack's 'web' mode in deno, and it works great. Much cleaner than nodejs, because deno supports the webcrypto API directly, and supports file:/// URLs with fetch. Rust: use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn foo() -> u32 {
let mut bytes = [0; 4];
getrandom::getrandom(&mut bytes[..]).expect("getrandom failed");
u32::from_be_bytes(bytes)
} Built with foo.js: import {default as init, foo} from './pkg/rand_wasm.js'
;(async () => {
let module = await init()
console.log(module.foo()) // Works great
console.log(foo()) // Also works
})()
|
Currently wasm-pack has two common build options:
nodejs
produces a commonjs module for nodeweb
produces an es6 module for the webI'd like to use the same wasm module (published in npm) isomorphically for both nodejs and the web. It almost works - but unfortunately getrandom (run from nodejs) doesn't work at all in an es6 context. It produces this code in a esm file:
... Which errors, because
module
is not defined in this context.I'm not sure how getrandom should work around this. Any thoughts?
The text was updated successfully, but these errors were encountered: