feat(cli): Implement experimental sticky native module Metro resolution
Why
expo-modules-autolinking resolves Expo modules and React Native modules which may be duplicated in the dependency tree. While this doesn't cause issues in native autolinking, since duplicates are detected and discarded, Metro itself isn't aware of this.
This introduces several challenges for our usual Metro resolution, since two installations of the same dependency may be bundled. This means that while the autolinking process may link a single instance of any given native module into the native app, the JS bundle may contain multiple versions of it. This is an error that's hard to detect for our users and leads to inconsistencies when making dependency changes.
Usually dependencies are duplicated due to:
- auto-installing peer dependencies, particularly with npm
- occasionally during dependency installation and upgrades with Yarn Classic
- due to hoisting errors with Yarn Berry
- due to pnpm isolated dependencies or other monorepo setups when no dedup has been run
While we can also add detection for this in expo install --check and offer deduplication, resolving dependencies in line with autolinking in Metro itself has other benefits:
- this matches the expectation that native modules can only be bundled exactly once
- prevents users from running unsupported deduplication tools
- increases trust in their lockfiles and gradual updates
- prevents many SDK upgrades from failing outright
How
The implementation is similar to the aliasResolver and the fallbackResolver. However, since expo-modules-autolinking is asynchronous and resolvers in Metro aren't, resolution is split up into two steps.
In the first step, we run Expo Modules autolinking (via findModulesAsync; expo-modules-autolinking search) and React Native autolinking (via createReactNativeConfigAsync; expo-modules-autolinking react-native-config) ahead of time. This creates a configuration per platform with a regular expression matching sticky modules and a mapping for their absolute paths.
In the second step, this is passed on to the sticky resolver itself, which uses it on module imports/requires and resolves them from the absolute path. This is similar to the requestAlias resolver and not an entirely new concept for our custom resolvers.
This logic is only enabled when EXPO_USE_STICKY_RESOLVER is enabled.
Follow-up opportunities
The expo-modules-autolinking logic could use some improvements:
- I'd love for it not to allow all autolinking options per platform. Allowing
searchPathsandignorePathsper platform seems counter-intuitive, sinceexcludesalready exists. This makes module discovery in the CLI more complicated than it needs to be - This enables us, in theory, to make autolinking consistent. We could update
react-native-configandsearchto be combined, i.e. for the former to use the same logic and to allow for transitive dependencies
Test Plan
- [x] tested against
bycedric/expo-monorepo-example - [x] tested against a freshly created Expo app
- [x] verified output from
DEBUG=expo:start:server:metro:sticky-resolvermanually - [x] unit test for the sticky resolver itself
- [ ] tested against a "broken" monorepo example
I did a test on a non-monorepo project with DEBUG=expo:start:server:metro:sticky-resolver. This provides output that verifies which native modules were detected and output for each sticky resolution as it happens.
The time it takes for exports to complete seems to not have changed.
We should test this against invalid projects. We currently don't have an example for this, but since this option is experimental, if we're confident it'll not break anything, this will be testable gradually. An initial test would be to create a monorepo, then manually add a pinned expo module dependency that duplicates another to a local package that's included in the app.
Checklist
- [x] I added a
changelog.mdentry and rebuilt the package sources according to this short guide - [ ] This diff will work correctly for
npx expo prebuild& EAS Build (eg: updated a module plugin). - [ ] Conforms with the Documentation Writing Style Guide
Subscribed to pull request
| File Patterns | Mentions |
|---|---|
| packages/@expo/cli/** | @EvanBacon, @bycedric |
Generated by CodeMention