Importing Node-API .node modules in Expo Router API Routes?
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 5.6k
- Forks
- 696
- Avg merge
- 8m
- Merged PRs (30d)
- 7
Description
Companion issue (expo/expo): https://github.com/expo/expo/issues/32350
Do you want to request a feature or report a bug?
Request a feature:
Documentation about Node-API native addons usage in Metro with platform: 'web', which Expo Router API Routes uses.
What is the current behavior?
Metro does not allow for importing Node-API native addons in platform: 'web' code, meaning that npm package containing native addons (such as @node-rs/bcrypt) cannot be used in Expo Router API Routes.
For example:
app/hash+api.ts
import { hashSync } from '@node-rs/bcrypt-darwin-arm64';
export async function GET(request: Request) {
const hash = hashSync('hello world');
return Response.json({ hash: 'hash' });
}
Reproduction (with Expo Router API Routes): https://github.com/karlhorky/repro-expo-router-api-routes-node-rs-bcrypt
Out of the box, Expo Router API Routes will not be able to bundle this, because the .node extension is not supported (as also mentioned by @EvanBacon here):
Android Bundled 10400ms node_modules/expo-router/entry.js (1132 modules)
λ Bundling failed 16ms app/hash+api.ts (1 module)
Metro error: Failed to bundle API Route: hash+api.ts
While trying to resolve module `@node-rs/bcrypt-darwin-arm64` from file `/Users/k/p/repro-expo-router-api-routes-node-rs-bcrypt-sha1-not-computed/app/hash+api.ts`, the package `/Users/k/p/repro-expo-router-api-routes-node-rs-bcrypt-sha1-not-computed/node_modules/@node-rs/bcrypt-darwin-arm64/package.json` was successfully found. However, this package itself specifies a `main` module field that could not be resolved (`/Users/k/p/repro-expo-router-api-routes-node-rs-bcrypt-sha1-not-computed/node_modules/@node-rs/bcrypt-darwin-arm64/bcrypt.darwin-arm64.node`. Indeed, none of these files exist:
* /Users/k/p/repro-expo-router-api-routes-node-rs-bcrypt-sha1-not-computed/node_modules/@node-rs/bcrypt-darwin-arm64/bcrypt.darwin-arm64.node(.web.ts|.ts|.web.tsx|.tsx|.web.js|.js|.web.jsx|.jsx|.web.json|.json|.web.cjs|.cjs|.web.mjs|.mjs|.web.scss|.scss|.web.sass|.sass|.web.css|.css)
* /Users/k/p/repro-expo-router-api-routes-node-rs-bcrypt-sha1-not-computed/node_modules/@node-rs/bcrypt-darwin-arm64/bcrypt.darwin-arm64.node/index(.web.ts|.ts|.web.tsx|.tsx|.web.js|.js|.web.jsx|.jsx|.web.json|.json|.web.cjs|.cjs|.web.mjs|.mjs|.web.scss|.scss|.web.sass|.sass|.web.css|.css)
Changing sourceExts to also contain .node (adding sourceExts.push('node') in node_modules/@expo/metro-config/build/ExpoMetroConfig.js) yields the following (trying to load the .node binary file):
Android Bundled 11972ms node_modules/expo-router/entry.js (1132 modules)
λ Bundling failed 191ms app/hash+api.ts (3 modules)
Metro error: Failed to bundle API Route: hash+api.ts
SyntaxError: /Users/k/p/repro-expo-router-api-routes-node-rs-bcrypt-sha1-not-computed/node_modules/@node-rs/bcrypt-darwin-arm64/bcrypt.darwin-arm64.node: Unexpected character '�'. (1:0)
> 1 | ����
���__TEX__text__TEXT�?��?�__stubs__TEXT�����
__stub_helper__TEXT������__const__TEXT���`��__gcc_except_tab__TEXT�8p�8__cstring__TEXTV*
V__unwind_info__TEXTDb�Db__eh_frame__TEXT@z��@z
h8__DATA_CONST@@__got__DATA_CONST0h__mod_init_func__DATA_CONST0X0 __const__DATA_CONST��4�x__DATA@�@@__la_symbol_ptr__DATA@@@n__data__DATA@C�@C__thread_vars__DATA�Z��Z__thread_data__DATA�[(�[__thread_bss__DATA�[A�[__bss__DATA@\�&__common__DATA�
| ^
2 | �H__LINKEDIT�
3 | @�x2
4 | �/Users/runner/work/node-rs/node-rs/target/aarch64-apple-darwin/release/deps/libnode_rs_bcrypt.dylib"�0����x�� ��(��p���
Pn���[�k@�q>����f"�(2
*
8/usr/lib/libiconv.2.dylib
8xA/usr/lib/libSystem.B.dylib&��)���������o
ERROR [SyntaxError: JSON Parse error: Unexpected character: <]
Unclear how to add "externals" in Metro
Thinking through this, one idea came up to use something similar to "externals" - code that Metro will not touch / bundle, but rather just load at runtime.
I can't see how I can modify the Metro configuration for Expo Router API Routes, to add something similar to webpack externals.
Maybe this feature doesn't exist in Metro?
If such a feature were to exist, this would also help with usage of the standard bcrypt package in Expo Router API Routes:
Metro Configuration
I believe this is the Metro configuration for Expo Router API Routes:
const opts: ExpoMetroOptions = {
// TODO: Possibly issues with using an absolute path here...
mainModuleName: convertPathToModuleSpecifier(filePath),
lazy: false,
asyncRoutes: false,
inlineSourceMap: false,
engine: 'hermes',
minify: false,
bytecode: false,
// Bundle in Node.js mode for SSR unless RSC is enabled.
environment: this.isReactServerComponentsEnabled ? 'react-server' : 'node',
platform: 'web',
mode: 'development',
//
...this.instanceMetroOptions,
// Mostly disable compiler in SSR bundles.
reactCompiler: false,
baseUrl,
routerRoot,
isExporting,
...specificOptions,
};
If the current behavior is a bug, please provide the steps to reproduce and a minimal repository on GitHub that we can yarn install and yarn test.
I'm not sure this is a bug, but just a gap in the documentation - that Node-API native addons are not intended to be supported in the platform: 'web' configuration.
What is the expected behavior?
It's documented (and a workaround is provided, if possible)
Please provide your exact Metro configuration and mention your Metro, node, yarn/npm version and operating system.
- Metro: 0.80.12
- Node.js: v20.18.0
- Yarn: 1.22.22
- npm: 10.8.2
- macOS 15.0.1
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the Metro configuration referenced in packages/@expo/cli/src/start/server/metro/MetroBundlerDevServer.ts and reproduce the failure using the linked Expo Router repository. Read how Metro handles platform:'web', .node resolution, and native addon loading; done means documenting the supported behavior and any confirmed workaround or limitation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, node.js, typescript
- Domain
- build-system, tooling
- Issue type
- Documentation
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100