React Nativeアプリで以下のエラーが発生しました。
Unable to resolve module 'crypto'
依存解決まわりの落とし穴だったので、原因と解決策をまとめます。
発生したエラー
error: Unable to resolve module crypto from node_modules/axios/dist/node/axios.cjs
ここがポイント:
axios/dist/node/axios.cjs
React Nativeなのに Node版のaxios が読み込まれています。
原因
axios の package.json には exports が定義されています。
"exports": {
".": {
"browser": "./dist/browser/axios.cjs",
"node": "./dist/node/axios.cjs",
"default": "./dist/node/axios.cjs"
}
}
本来 React Native では browser が選ばれるべきですが、
Metroの条件解決によって default → node版 が選ばれることがあります。
結果:
Node用コード読み込み
↓
crypto モジュールが必要
↓
React Nativeには無い
↓
ビルド失敗
問題の本質
これは
Metroがexportsをどう評価するか
の問題です。
条件が適切に指定されていないと、
default が選ばれて Node版が読み込まれてしまいます。
つまり原因は
- axiosの問題でも
- React Nativeのバグでもなく
パッケージexportsの条件解決順 にあります。
一時的に通った対処(採用しなかった)
axios の main を browser に変更:
"main": "./dist/browser/axios.cjs"
これは fallback を強制して回避しただけなので
パッケージ改変による非推奨対応のため却下しました。
最終的な解決策
Metroの exports 条件解決順を明示的に制御します。
metro.config.js
const { getDefaultConfig } = require("@react-native/metro-config");
const config = getDefaultConfig(__dirname);
config.resolver.unstable_enablePackageExports = true;
config.resolver.unstable_conditionNames = [
"browser",
"require",
"react-native",
];
module.exports = config;
なぜこれで直るのか
Metroは exports を条件名の順で評価します。
今回の設定で:
browser → require → react-native
が優先され、default に落ちる前に
browser版axiosが確定します。
結果:
- node版axiosが読まれない
- crypto依存が消える
- エラー解消
キャッシュの影響を除外する検証
設定後、以下を実施しても再発しなかったため
設定による解決が確定しました。
watchman watch-del-all
rm -rf node_modules ios/build ~/Library/Developer/Xcode/DerivedData
npm install
npx react-native start --reset-cache
結論
今回の問題は
React Native × axios × exports の条件解決不一致
が原因でした。
正しい対処
- ライブラリのmainを書き換えない
- resolveRequestハックに頼らない
- Metroの
unstable_conditionNamesで解決順を制御する
同じ症状の人へ
以下に当てはまるなら、この対応で解決する可能性が高いです。
- axios の
dist/node/axios.cjsがエラーに出る - crypto / url / http が解決できない
依存解決は「ファイルの有無」ではなく
「どのビルドが選ばれるか」 の問題でした。