Skip to content

Commit cac4a4f

Browse files
fix(core): Lazy-load Metro internal modules to prevent Expo 55 import errors (#5958)
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
1 parent 55778e7 commit cac4a4f

File tree

3 files changed

+74
-40
lines changed

3 files changed

+74
-40
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212

1313
- Enable "Open Sentry" button in Playground for Expo apps ([#5947](https://github.com/getsentry/sentry-react-native/pull/5947))
1414

15+
### Fixes
16+
17+
- Lazy-load Metro internal modules to prevent Expo 55 import errors ([#5958](https://github.com/getsentry/sentry-react-native/pull/5958))
18+
1519
### Dependencies
1620

1721
- Bump Cocoa SDK from v9.8.0 to v9.9.0 ([#5956](https://github.com/getsentry/sentry-react-native/pull/5956))

packages/core/src/js/tools/vendor/metro/utils.ts

Lines changed: 44 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -32,48 +32,9 @@ import type * as bundleToStringType from 'metro/private/lib/bundleToString';
3232

3333
import type { MetroSerializer } from '../../utils';
3434

35-
// oxlint-disable-next-line typescript-eslint(no-explicit-any)
36-
let baseJSBundleModule: any;
37-
try {
38-
baseJSBundleModule = require('metro/private/DeltaBundler/Serializers/baseJSBundle');
39-
} catch {
40-
baseJSBundleModule = require('metro/src/DeltaBundler/Serializers/baseJSBundle');
41-
}
42-
43-
const baseJSBundle: typeof baseJSBundleType =
44-
typeof baseJSBundleModule === 'function'
45-
? baseJSBundleModule
46-
: (baseJSBundleModule?.baseJSBundle ?? baseJSBundleModule?.default);
47-
48-
let sourceMapString: typeof sourceMapStringType;
49-
try {
50-
const sourceMapStringModule = require('metro/private/DeltaBundler/Serializers/sourceMapString');
51-
sourceMapString = (sourceMapStringModule as { sourceMapString: typeof sourceMapStringType }).sourceMapString;
52-
} catch (e) {
53-
sourceMapString = require('metro/src/DeltaBundler/Serializers/sourceMapString');
54-
if ('sourceMapString' in sourceMapString) {
55-
// Changed to named export in https://github.com/facebook/metro/commit/34148e61200a508923315fbe387b26d1da27bf4b
56-
// Metro 0.81.0 and 0.80.10 patch
57-
sourceMapString = (sourceMapString as { sourceMapString: typeof sourceMapStringType }).sourceMapString;
58-
}
59-
}
60-
61-
// oxlint-disable-next-line typescript-eslint(no-explicit-any)
62-
let bundleToStringModule: any;
63-
try {
64-
bundleToStringModule = require('metro/private/lib/bundleToString');
65-
} catch {
66-
bundleToStringModule = require('metro/src/lib/bundleToString');
67-
}
68-
69-
const bundleToString: typeof bundleToStringType =
70-
typeof bundleToStringModule === 'function'
71-
? bundleToStringModule
72-
: (bundleToStringModule?.bundleToString ?? bundleToStringModule?.default);
73-
7435
type NewSourceMapStringExport = {
7536
// Since Metro v0.80.10 https://github.com/facebook/metro/compare/v0.80.9...v0.80.10#diff-1b836d1729e527a725305eef0cec22e44605af2700fa413f4c2489ea1a03aebcL28
76-
sourceMapString: typeof sourceMapString;
37+
sourceMapString: typeof sourceMapStringType;
7738
};
7839

7940
/**
@@ -108,6 +69,49 @@ export const getSortedModules = (
10869
* https://github.com/facebook/metro/blob/9b85f83c9cc837d8cd897aa7723be7da5b296067/packages/metro/src/Server.js#L244-L277
10970
*/
11071
export const createDefaultMetroSerializer = (): MetroSerializer => {
72+
// Lazy-load Metro internals only when serializer is created
73+
// This defers requiring Metro modules until they're actually needed (during build),
74+
// avoiding import-time failures when Metro is only a transitive dependency
75+
76+
// oxlint-disable-next-line typescript-eslint(no-explicit-any)
77+
let baseJSBundleModule: any;
78+
try {
79+
baseJSBundleModule = require('metro/private/DeltaBundler/Serializers/baseJSBundle');
80+
} catch {
81+
baseJSBundleModule = require('metro/src/DeltaBundler/Serializers/baseJSBundle');
82+
}
83+
84+
const baseJSBundle: typeof baseJSBundleType =
85+
typeof baseJSBundleModule === 'function'
86+
? baseJSBundleModule
87+
: (baseJSBundleModule?.baseJSBundle ?? baseJSBundleModule?.default);
88+
89+
let sourceMapString: typeof sourceMapStringType;
90+
try {
91+
const sourceMapStringModule = require('metro/private/DeltaBundler/Serializers/sourceMapString');
92+
sourceMapString = (sourceMapStringModule as { sourceMapString: typeof sourceMapStringType }).sourceMapString;
93+
} catch (e) {
94+
sourceMapString = require('metro/src/DeltaBundler/Serializers/sourceMapString');
95+
if ('sourceMapString' in sourceMapString) {
96+
// Changed to named export in https://github.com/facebook/metro/commit/34148e61200a508923315fbe387b26d1da27bf4b
97+
// Metro 0.81.0 and 0.80.10 patch
98+
sourceMapString = (sourceMapString as { sourceMapString: typeof sourceMapStringType }).sourceMapString;
99+
}
100+
}
101+
102+
// oxlint-disable-next-line typescript-eslint(no-explicit-any)
103+
let bundleToStringModule: any;
104+
try {
105+
bundleToStringModule = require('metro/private/lib/bundleToString');
106+
} catch {
107+
bundleToStringModule = require('metro/src/lib/bundleToString');
108+
}
109+
110+
const bundleToString: typeof bundleToStringType =
111+
typeof bundleToStringModule === 'function'
112+
? bundleToStringModule
113+
: (bundleToStringModule?.bundleToString ?? bundleToStringModule?.default);
114+
111115
return (entryPoint, preModules, graph, options) => {
112116
// baseJSBundle assigns IDs to modules in a consistent order
113117
let bundle = baseJSBundle(entryPoint, preModules, graph, options);

packages/core/test/tools/sentryMetroSerializer.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,32 @@ describe('Sentry Metro Serializer', () => {
232232
expect(debugId).toMatch(/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/);
233233
});
234234
});
235+
236+
test('createDefaultMetroSerializer can be created without Metro internals being loaded at import time', () => {
237+
// This test verifies that the lazy-loading of Metro internals works correctly.
238+
// The createDefaultMetroSerializer function should be callable without triggering
239+
// module-level requires of Metro internals at import time.
240+
// See: https://github.com/getsentry/sentry-react-native/issues/5957
241+
242+
// Import the function
243+
const { createDefaultMetroSerializer: createSerializer } = require('../../src/js/tools/vendor/metro/utils');
244+
245+
// Create the serializer - this should succeed without loading Metro internals
246+
const serializer = createSerializer();
247+
expect(typeof serializer).toBe('function');
248+
249+
// Verify the serializer can be invoked with proper arguments and produces output
250+
const [entryPoint, preModules, graph, options] = mockMinSerializerArgs();
251+
const result = serializer(entryPoint, preModules, graph, options);
252+
253+
expect(result).toHaveProperty('code');
254+
expect(result).toHaveProperty('map');
255+
expect(typeof result.code).toBe('string');
256+
expect(typeof result.map).toBe('string');
257+
// Both code and map should exist (even if minimal for empty bundle)
258+
expect(result.code).toBeDefined();
259+
expect(result.map).toBeDefined();
260+
});
235261
});
236262

237263
function mockMinSerializerArgs(options?: {

0 commit comments

Comments
 (0)