node-rs icon indicating copy to clipboard operation
node-rs copied to clipboard

"Failed to load native binding" with `@node-rs/bcrypt` + Expo Router API Routes (Metro bundler)

Open karlhorky opened this issue 1 year ago • 0 comments

Companion issue (expo/expo): https://github.com/expo/expo/issues/32350

Hi, first of all, thanks for these Rust libraries! Nice performance, and good options when the main bcrypt, etc packages don't work in a particular environment.

We are trying to use @node-rs/bcrypt in Expo Router API Routes (using Metro bundler), and we're getting some output that the native binding cannot be loaded (because it's "undefined", see further below):

Android Bundled 852ms node_modules/expo-router/entry.js (1049 modules)
λ Bundled 443ms app/hash+api.ts (4 modules)

Metro error: Failed to load native binding

  357 |     //  - The user may need to bundle the correct files
  358 |     //  - The user may need to re-install node_modules to get new packages
> 359 |     throw new Error('Failed to load native binding', { cause: loadErrors })
      |           ^
  360 |   }
  361 |   throw new Error(`Failed to load native binding`)
  362 | }

Call Stack
  factory (node_modules/@node-rs/bcrypt/binding.js:359:11)
  loadModuleImplementation (node_modules/metro-runtime/src/polyfills/require.js:277:5)
  guardedLoadModule (node_modules/metro-runtime/src/polyfills/require.js:184:12)
  require (node_modules/metro-runtime/src/polyfills/require.js:92:7)
  factory (node_modules/@node-rs/bcrypt/index.js:1:84)
  loadModuleImplementation (node_modules/metro-runtime/src/polyfills/require.js:277:5)
  guardedLoadModule (node_modules/metro-runtime/src/polyfills/require.js:184:12)
  require (node_modules/metro-runtime/src/polyfills/require.js:92:7)
  factory (app/hash+api.ts:1)
  loadModuleImplementation (node_modules/metro-runtime/src/polyfills/require.js:277:5)
 ERROR  [SyntaxError: JSON Parse error: Unexpected character: <]

Screenshot 2024-10-25 at 15 43 50

I think Metro is not logging out the error cause, so adapting the node_modules/@node-rs/bcrypt/binding.js to log out loadErrors shows that it is using "undefined" as the module specifier:

if (!nativeBinding) {
  if (loadErrors.length > 0) {
    // TODO Link to documentation with potential fixes
    //  - The package owner could build/publish bindings for this arch
    //  - The user may need to bundle the correct files
    //  - The user may need to re-install node_modules to get new packages
+   console.log(loadErrors[0])
+   console.log(loadErrors[1])
+   console.log(loadErrors[2])
+   console.log(loadErrors[3])
    throw new Error('Failed to load native binding', { cause: loadErrors })
  }
  throw new Error(`Failed to load native binding`)
}

This is repeated 4 times:

λ  LOG  Error: Requiring unknown module "undefined". If you are sure the module exists, try restarting Metro. You may also want to run `yarn` or `npm install`.
    at unknownModuleError (/Users/k/p/repro-expo-router-api-routes-node-rs-bcrypt/node_modules/metro-runtime/src/polyfills/require.js:322:10)
    at loadModuleImplementation (/Users/k/p/repro-expo-router-api-routes-node-rs-bcrypt/node_modules/metro-runtime/src/polyfills/require.js:244:11)
    at guardedLoadModule (/Users/k/p/repro-expo-router-api-routes-node-rs-bcrypt/node_modules/metro-runtime/src/polyfills/require.js:184:12)
    at require (/Users/k/p/repro-expo-router-api-routes-node-rs-bcrypt/node_modules/metro-runtime/src/polyfills/require.js:92:7)
    at requireNative (/Users/k/p/repro-expo-router-api-routes-node-rs-bcrypt/node_modules/@node-rs/bcrypt/binding.js:130:16)
    at factory (/Users/k/p/repro-expo-router-api-routes-node-rs-bcrypt/node_modules/@node-rs/bcrypt/binding.js:332:17)
    at loadModuleImplementation (/Users/k/p/repro-expo-router-api-routes-node-rs-bcrypt/node_modules/metro-runtime/src/polyfills/require.js:277:5)
    at guardedLoadModule (/Users/k/p/repro-expo-router-api-routes-node-rs-bcrypt/node_modules/metro-runtime/src/polyfills/require.js:184:12)
    at require (/Users/k/p/repro-expo-router-api-routes-node-rs-bcrypt/node_modules/metro-runtime/src/polyfills/require.js:92:7)
    at /Users/k/p/repro-expo-router-api-routes-node-rs-bcrypt/node_modules/@node-rs/bcrypt/index.js:1:84

Reproduction

  • https://github.com/karlhorky/repro-expo-router-api-routes-node-rs-bcrypt

Creation steps:

  1. mkdir repro-expo-router-api-routes-node-rs-bcrypt && cd repro-expo-router-api-routes-node-rs-bcrypt
  2. npx create-expo-app@latest .
  3. rm -r ./node_modules && npm install (for EMFILE error)
  4. Remove extra files
  5. Configure app.json with expo.web.output = "server" and expo.plugins[0][1] = { "origin": "http://localhost:8081" }
  6. Create a file app/hash+api.ts, with an Expo Router API Route, using hashSync from @node-rs/bcrypt
  7. Inside app/(tabs)/index.tsx, add a fetch() of the API Route inside useFocusEffect
  8. 💥 npm start and observe the error after API Route bundling

karlhorky avatar Oct 25 '24 13:10 karlhorky