Skip to content

fix(core): Resolve Metro from project root for source map generation - #6625

Merged
antonis merged 10 commits into
mainfrom
fix/metro-resolve-project-root
Sep 2, 2026
Merged

fix(core): Resolve Metro from project root for source map generation#6625
antonis merged 10 commits into
mainfrom
fix/metro-resolve-project-root

Conversation

@antonis

@antonis antonis commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

📢 Type of change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring

📜 Description

The default Metro serializer resolved Metro's internal modules (baseJSBundle, bundleToString, sourceMapString) from the SDK's own location (__dirname). In a workspace/monorepo where a different Metro version is nested under @sentry/react-native than the one the app uses, that meant source maps were generated with a mismatched Metro version.

The serializer now resolves Metro internals preferring the app's options.projectRoot, falling back to the SDK-resolvable Metro. Resolution is deferred to the first serialization (when projectRoot is available) and memoized. In a normal single-Metro install both paths resolve to the same Metro, so behavior is unchanged.

💡 Motivation and Context

Surfaced while bumping the React Native sample to 0.87 (Metro 0.87) while packages/core pins Metro 0.84.4 as a devDependency: the nested SDK Metro shadowed the app's Metro 0.87 and broke production source-map generation. The fix also benefits real users who have multiple Metro versions resolvable in a monorepo.

Split out of #6617 so the sample bump carries no core changes.

💚 How did you test it?

  • Existing Metro serializer unit tests pass.
  • Local Android release build of the RN 0.87 sample produced a valid production bundle with a Debug ID via this serializer.

📝 Checklist

  • I added tests to verify changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • All tests passing.
  • Public API changes reviewed by another Mobile SDK team member or implemented according to the develop docs spec.
  • No breaking changes.

🔮 Next steps

  • Add a dedicated unit test that asserts Metro internals are resolved from options.projectRoot when a different Metro is nested under the SDK.

🤖 Generated with Claude Code

The vendored default serializer required Metro internals resolvable from
the SDK's own location. In this monorepo the core package has its own
Metro dev dependency (0.84.4) that shadows the app's Metro, so bundling
the RN 0.87 sample generated source maps with the mismatched older Metro
and threw `Unexpected module with full source map found` on the
metro-runtime require.js polyfill.

Resolve Metro internals from `options.projectRoot` (the app being
bundled) with a fallback to the previous SDK-local resolution. In a
normal install both resolve the same Metro instance, so behavior is
unchanged; in the monorepo the sample now bundles with its own 0.87
Metro. Resolution is deferred to the first serialization so
`options.projectRoot` is available, and memoized thereafter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@antonis
antonis force-pushed the fix/metro-resolve-project-root branch from d8893b4 to addbd1d Compare August 27, 2026 11:21
@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Semver Impact of This PR

None (no version bump detected)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


  • fix(core): Resolve Metro from project root for source map generation by antonis in #6625
  • chore(deps): update Maestro to v2.10.0 by github-actions in #6641
  • chore(deps): update JavaScript SDK to v10.73.0 by github-actions in #6642
  • test(ios): Speed up sentry-xcode-scripts tests by antonis in #6633
  • fix: Update recommended vscode extensions by antonis in #6640
  • chore(deps): bump actions/setup-java from 5.7.0 to 6.0.0 by dependabot in #6636
  • chore(deps): bump the codeql-action group with 3 updates by dependabot in #6635
  • chore(deps): update JavaScript SDK to v10.72.0 by github-actions in #6634
  • chore(e2e): Bump E2E tests to React Native 0.87.0 by antonis in #6616
  • chore(deps): update CLI to v3.7.0 by github-actions in #6632
  • feat(tracing): Copy app start vitals onto standalone children by buenaflor in #6631
  • chore(deps): update Cocoa SDK to v9.26.1 by github-actions in #6623
  • fix(ios): Force-load the Sentry static archive to keep ObjC category methods by antonis in #6615
  • feat(core): Forward feature flag evaluations to the native SDKs by antonis in #6613
  • chore(deps): update Android SDK to v8.54.0 by github-actions in #6624
  • chore(deps): update Sentry Android Gradle Plugin to v6.20.0 by github-actions in #6627

🤖 This preview updates automatically when you update the PR.

@antonis antonis added the ready-to-merge Triggers the full CI test suite label Aug 27, 2026
Comment thread packages/core/src/js/tools/vendor/metro/utils.ts
@sentry

sentry Bot commented Aug 27, 2026

Copy link
Copy Markdown

📲 Install Builds

Android

🔗 App Name App ID Version Configuration
Sentry RN io.sentry.reactnative.sample 8.24.0 (104) Release

⚙️ sentry-react-native Build Distribution Settings

@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

iOS (legacy) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 3838.64 ms 1216.90 ms -2621.74 ms
Size 5.08 MiB 6.80 MiB 1.72 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
ecf47a2+dirty 3835.20 ms 1222.43 ms -2612.77 ms
3817909+dirty 1183.90 ms 1187.50 ms 3.60 ms
5ee78d6+dirty 3847.52 ms 1224.27 ms -2623.25 ms
890d145+dirty 1223.59 ms 1231.37 ms 7.78 ms
e763471+dirty 3841.16 ms 1218.06 ms -2623.09 ms
3d31fcf+dirty 3838.09 ms 1223.46 ms -2614.63 ms
9210ae6+dirty 3815.93 ms 1214.14 ms -2601.79 ms
26843eb+dirty 3861.60 ms 1238.77 ms -2622.82 ms
7a89652+dirty 3861.46 ms 1229.61 ms -2631.85 ms
9ad4522+dirty 3850.40 ms 1222.40 ms -2628.00 ms

App size

Revision Plain With Sentry Diff
ecf47a2+dirty 4.98 MiB 6.46 MiB 1.49 MiB
3817909+dirty 3.38 MiB 4.73 MiB 1.35 MiB
5ee78d6+dirty 5.15 MiB 6.69 MiB 1.53 MiB
890d145+dirty 3.38 MiB 4.77 MiB 1.38 MiB
e763471+dirty 4.98 MiB 6.51 MiB 1.53 MiB
3d31fcf+dirty 4.98 MiB 6.56 MiB 1.58 MiB
9210ae6+dirty 5.15 MiB 6.68 MiB 1.53 MiB
26843eb+dirty 4.98 MiB 6.53 MiB 1.55 MiB
7a89652+dirty 5.15 MiB 6.70 MiB 1.55 MiB
9ad4522+dirty 4.98 MiB 6.56 MiB 1.58 MiB

Previous results on branch: fix/metro-resolve-project-root

Startup times

Revision Plain With Sentry Diff
ade1508+dirty 3839.98 ms 1220.75 ms -2619.23 ms
a1cdaff+dirty 3856.69 ms 1221.58 ms -2635.11 ms

App size

Revision Plain With Sentry Diff
ade1508+dirty 5.08 MiB 6.72 MiB 1.64 MiB
a1cdaff+dirty 5.08 MiB 6.80 MiB 1.72 MiB

@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Android (legacy) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 497.21 ms 507.55 ms 10.33 ms
Size 49.74 MiB 55.66 MiB 5.91 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
f9c1ed4+dirty 431.00 ms 466.22 ms 35.22 ms
c2e182c+dirty 471.64 ms 553.59 ms 81.95 ms
41d6254+dirty 424.45 ms 474.34 ms 49.89 ms
af33f3b+dirty 434.90 ms 506.14 ms 71.24 ms
882f8ae+dirty 399.98 ms 427.06 ms 27.08 ms
0b5120f+dirty 503.22 ms 538.60 ms 35.38 ms
774257e+dirty 407.09 ms 447.46 ms 40.37 ms
bf168a4+dirty 418.21 ms 489.74 ms 71.53 ms
5569641+dirty 406.43 ms 428.51 ms 22.08 ms
c151573+dirty 530.34 ms 559.43 ms 29.09 ms

App size

Revision Plain With Sentry Diff
f9c1ed4+dirty 49.74 MiB 54.86 MiB 5.12 MiB
c2e182c+dirty 49.74 MiB 54.85 MiB 5.11 MiB
41d6254+dirty 48.30 MiB 53.60 MiB 5.30 MiB
af33f3b+dirty 49.74 MiB 55.09 MiB 5.35 MiB
882f8ae+dirty 48.30 MiB 53.60 MiB 5.29 MiB
0b5120f+dirty 48.30 MiB 53.58 MiB 5.28 MiB
774257e+dirty 48.30 MiB 53.58 MiB 5.28 MiB
bf168a4+dirty 49.74 MiB 55.09 MiB 5.35 MiB
5569641+dirty 48.30 MiB 53.48 MiB 5.18 MiB
c151573+dirty 48.30 MiB 53.54 MiB 5.24 MiB

Previous results on branch: fix/metro-resolve-project-root

Startup times

Revision Plain With Sentry Diff
a1cdaff+dirty 421.02 ms 430.26 ms 9.24 ms
ade1508+dirty 485.55 ms 545.23 ms 59.68 ms

App size

Revision Plain With Sentry Diff
a1cdaff+dirty 49.74 MiB 55.66 MiB 5.91 MiB
ade1508+dirty 49.74 MiB 55.45 MiB 5.71 MiB

@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

iOS (new) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 3845.18 ms 1209.83 ms -2635.34 ms
Size 5.08 MiB 6.80 MiB 1.72 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
3b6e9f9+dirty 3822.77 ms 1208.00 ms -2614.77 ms
5a23c47+dirty 3843.54 ms 1226.05 ms -2617.50 ms
100ce80+dirty 3843.57 ms 1226.46 ms -2617.12 ms
0bd8916+dirty 3816.17 ms 1215.05 ms -2601.12 ms
a0d8cf8+dirty 3826.15 ms 1213.12 ms -2613.03 ms
0a9e622+dirty 3825.35 ms 1219.04 ms -2606.31 ms
21a1e70+dirty 3826.22 ms 1217.74 ms -2608.47 ms
9ad4522+dirty 3862.28 ms 1226.27 ms -2636.01 ms
88735e9+dirty 3837.80 ms 1216.39 ms -2621.41 ms
d0e3b3e+dirty 3846.64 ms 1226.27 ms -2620.38 ms

App size

Revision Plain With Sentry Diff
3b6e9f9+dirty 5.15 MiB 6.68 MiB 1.53 MiB
5a23c47+dirty 4.98 MiB 6.46 MiB 1.49 MiB
100ce80+dirty 5.15 MiB 6.67 MiB 1.51 MiB
0bd8916+dirty 5.15 MiB 6.69 MiB 1.53 MiB
a0d8cf8+dirty 5.15 MiB 6.67 MiB 1.51 MiB
0a9e622+dirty 4.98 MiB 6.51 MiB 1.53 MiB
21a1e70+dirty 4.98 MiB 6.46 MiB 1.49 MiB
9ad4522+dirty 4.98 MiB 6.56 MiB 1.58 MiB
88735e9+dirty 4.98 MiB 6.46 MiB 1.49 MiB
d0e3b3e+dirty 4.98 MiB 6.51 MiB 1.53 MiB

Previous results on branch: fix/metro-resolve-project-root

Startup times

Revision Plain With Sentry Diff
ade1508+dirty 3846.26 ms 1221.43 ms -2624.83 ms
a1cdaff+dirty 3841.65 ms 1224.58 ms -2617.07 ms

App size

Revision Plain With Sentry Diff
ade1508+dirty 5.08 MiB 6.72 MiB 1.64 MiB
a1cdaff+dirty 5.08 MiB 6.80 MiB 1.72 MiB

… over a nested SDK copy

requireMetroModule now tries each candidate path shape against the app
(projectRoot) before the SDK's own location, so the app's Metro wins even
when the two copies expose internals via different subpaths (app on
metro/src/*, SDK on metro/private/*). Adds projectRoot-preference and
ordering-regression tests using on-disk fake-Metro fixtures.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Android (new) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 487.50 ms 545.33 ms 57.83 ms
Size 49.74 MiB 55.66 MiB 5.91 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
15d4514+dirty 413.63 ms 449.62 ms 35.99 ms
26843eb+dirty 442.02 ms 506.29 ms 64.27 ms
ca9d079+dirty 460.67 ms 512.54 ms 51.87 ms
f9c1ed4+dirty 520.57 ms 594.84 ms 74.27 ms
a0d8cf8+dirty 533.71 ms 564.25 ms 30.54 ms
20fbd51+dirty 594.38 ms 655.35 ms 60.97 ms
0b0f006+dirty 446.31 ms 458.31 ms 12.00 ms
1d3572b+dirty 444.48 ms 478.42 ms 33.94 ms
4953e94+dirty 398.80 ms 431.81 ms 33.01 ms
4e0b819+dirty 599.31 ms 654.31 ms 55.00 ms

App size

Revision Plain With Sentry Diff
15d4514+dirty 48.30 MiB 53.60 MiB 5.30 MiB
26843eb+dirty 49.74 MiB 55.26 MiB 5.52 MiB
ca9d079+dirty 48.30 MiB 53.58 MiB 5.28 MiB
f9c1ed4+dirty 49.74 MiB 54.86 MiB 5.12 MiB
a0d8cf8+dirty 48.30 MiB 53.49 MiB 5.19 MiB
20fbd51+dirty 49.74 MiB 54.81 MiB 5.07 MiB
0b0f006+dirty 49.74 MiB 55.66 MiB 5.91 MiB
1d3572b+dirty 49.74 MiB 55.38 MiB 5.63 MiB
4953e94+dirty 43.94 MiB 48.94 MiB 5.00 MiB
4e0b819+dirty 49.74 MiB 54.81 MiB 5.07 MiB

Previous results on branch: fix/metro-resolve-project-root

Startup times

Revision Plain With Sentry Diff
a1cdaff+dirty 428.06 ms 458.04 ms 29.98 ms
ade1508+dirty 491.94 ms 520.06 ms 28.12 ms

App size

Revision Plain With Sentry Diff
a1cdaff+dirty 49.74 MiB 55.66 MiB 5.91 MiB
ade1508+dirty 49.74 MiB 55.45 MiB 5.71 MiB

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 0645154. Configure here.

@antonis
antonis marked this pull request as ready for review August 27, 2026 13:41
*/
// oxlint-disable-next-line typescript-eslint(no-explicit-any)
function requireMetroModule(candidates: string[], projectRoot: string | undefined): any {
const roots = projectRoot ? [projectRoot, __dirname] : [__dirname];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could projectRoot be an empty string here? it may silently fail if thats the case

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should work since an empty string should fallback to [__dirname]

Comment thread packages/core/src/js/tools/vendor/metro/utils.ts Outdated
Comment thread packages/core/src/js/tools/vendor/metro/utils.ts
);
if (typeof sourceMapString !== 'function') {
throw new Error(`
[@sentry/react-native/metro] Cannot find sourceMapString function in 'metro/src/DeltaBundler/Serializers/sourceMapString'.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it also required on metro/private/DeltaBundler/Serializers/sourceMapString ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point 👍 Update with 79ea609 to be path agnostic

sourceMapString = (sourceMapString as { sourceMapString: typeof sourceMapStringType }).sourceMapString;
return (entryPoint, preModules, graph, options) => {
if (!internals) {
internals = resolveMetroInternals(options.projectRoot);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like an edge-case but local AI complained about the following:

resolveMetroInternals() (and its sourceMapString shape check/throw) now runs before isHot is 
computed. Previously the throw only fired on non-hot builds; now it can break the dev server too.
 Now it fires unconditionally on the very first bundle serialized, hot or not, so a bad Metro shape that used to only break production/export builds can now break yarn start itself.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch 👍 Updated with b585061

@lucas-zimerman lucas-zimerman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overall looks good, there are some bits to be polished, and after that we can merge it :D

@antonis antonis removed the ready-to-merge Triggers the full CI test suite label Sep 1, 2026
@antonis antonis added the ready-to-merge Triggers the full CI test suite label Sep 1, 2026

@lucas-zimerman lucas-zimerman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@antonis
antonis merged commit 77b7b23 into main Sep 2, 2026
123 of 141 checks passed
@antonis
antonis deleted the fix/metro-resolve-project-root branch September 2, 2026 06:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge Triggers the full CI test suite

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants