nrwl / nx

Smart Monorepos · Fast CI
https://nx.dev
MIT License
23.39k stars 2.33k forks source link

[react-native] Unable to use React.lazy of packages libraries, because of "Metro has encountered an error" #22365

Open whalemare opened 6 months ago

whalemare commented 6 months ago

Current Behavior

Unable to use lazy-loading React feature with react-native

This line will cause error

const SharedLayoutLazy = React.lazy(() => import('@happynrwl/shared-ui-layout'));
Error: Metro has encountered an error: Cannot resolve ./packages/shared-ui-layout/src/index: /Users/whalemare/dev/whalemare/temp/happynrwl/node_modules/@nx/react-native/plugins/metro-resolver.js

Expected Behavior

Lazy imports from packages resolved correctly

GitHub Repo

https://github.com/whalemare/nx-react-native-lazy-reproduce.git

Steps to Reproduce

  1. Setup reproducible demo

    git clone https://github.com/whalemare/nx-react-native-lazy-reproduce.git
    cd nx-react-native-lazy-reproduce
    yarn
  2. Run android, be sure that app run without error

    yarn nx run mobile:run-android
  3. Uncomment 18 line and comment 17 line in example

React-native unable to resolve imports now

Nx Report

NX   Report complete - copy this into the issue template

Node   : 18.19.0
OS     : darwin-arm64
npm    : 10.2.3

nx                 : 18.1.1
@nx/js             : 18.1.1
@nx/jest           : 18.1.1
@nx/linter         : 18.1.1
@nx/eslint         : 18.1.1
@nx/workspace      : 18.1.1
@nx/detox          : 18.1.1
@nx/devkit         : 18.1.1
@nx/eslint-plugin  : 18.1.1
@nx/react          : 18.1.1
@nx/react-native   : 18.1.1
@nrwl/tao          : 18.1.1
@nx/web            : 18.1.1
@nx/webpack        : 18.1.1
typescript         : 5.3.3

Failure Logs

Error: Metro has encountered an error: Cannot resolve ./packages/shared-ui-layout/src/index: /Users/whalemare/dev/whalemare/temp/happynrwl/node_modules/@nx/react-native/plugins/metro-resolver.js

Package Manager Version

No response

Operating System

Additional Information

Also, I apply fix from https://github.com/nrwl/nx/issues/19329#issuecomment-1747506283

Blendfoul commented 5 months ago

I'm currently facing the same issue, is there any update on this?

marcovoliveira commented 5 months ago

I had a similar issue, any news on this ?

whalemare commented 4 months ago

We can't update because of no Lazy support, any estimate on this?

david-cahill commented 3 months ago

Is there any update on this?

Blendfoul commented 3 months ago

@david-cahill just to save you countless hours of debugging the "fix" I came up with was to create a pre run task that creates a symlink to the packages and node_modules folder much like the old ensure-symlink task would do for node_modules this has circumvented the problem for now but it's not at all a proper fix.

valeriobelli commented 1 month ago

We are reproducing this issue on React Native 0.73, just on Android. iOS development builds work as expected.

Our observations make us hypothesize that dynamic imports are somehow handled differently for the two platforms (we haven't dug out the real motivation, sorry 🥲). On Android, the requested import is relative to the workspace's root and contains no file extension. This happens when the application requests .bundle files from Metro. Might Hermes be involved here?

We worked around the issue with a local patch applied to @nx/react-native@19.6.0 (it might also work for other versions, but we haven't tested it) with yarn patch @nx/react-native. This patch introduces file resolution starting from the workspace's root by trying to resolve the file with every handled file extension. A brute force approach. 😄

It's not the final solution here, though. I hope this can speed up the development for some of you while searching for a resolution.

diff --git a/plugins/metro-resolver.js b/plugins/metro-resolver.js
index e4777edbf20ec73ff9f1e964f00e316c5f8fffd9..c8003341d57e0a272924f73b82cb16f0fe212589 100644
--- a/plugins/metro-resolver.js
+++ b/plugins/metro-resolver.js
@@ -21,7 +21,8 @@ function getResolveRequest(extensions, exportsConditionNames = [], mainFields =
         const resolvedPath = resolveRequestFromContext(resolveRequest, _context, realModuleName, platform, debug) ??
             defaultMetroResolver(context, realModuleName, platform, debug) ??
             tsconfigPathsResolver(context, extensions, realModuleName, platform, debug) ??
-            pnpmResolver(extensions, context, realModuleName, debug, exportsConditionNames, mainFields);
+            pnpmResolver(extensions, context, realModuleName, debug, exportsConditionNames, mainFields) ??
+            workspaceRootRelativeResolver(context, extensions, realModuleName, platform, debug);
         if (resolvedPath) {
             return resolvedPath;
         }
@@ -31,6 +32,21 @@ function getResolveRequest(extensions, exportsConditionNames = [], mainFields =
         throw new Error(`Cannot resolve ${chalk.bold(realModuleName)}`);
     };
 }
+function workspaceRootRelativeResolver(context, extensions, realModuleName, platform, debug) {
+    try {
+        return extensions
+            .map((extension) => {
+                try {
+                    return metroResolver.resolve(context, path_1.join(devkit_1.workspaceRoot, `${realModuleName}.${extension}`), platform)
+                } catch {}
+            })
+            .find(Boolean);
+    } catch {
+        if (debug) {
+            console.log(chalk.cyan(`[Nx] Unable to resolve with Workspace root relative resolver: ${realModuleName}`));
+        }
+    }
+}
 function resolveRequestFromContext(resolveRequest, context, realModuleName, platform, debug) {
     try {
         return resolveRequest(context, realModuleName, platform);