expo 55.0.0-canary-20260119-17896bf → 55.0.0-canary-20260120-bb71700

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/Expo.podspec +1 -0
  2. package/android/build.gradle +5 -5
  3. package/android/src/main/java/expo/modules/ExpoReactHostFactory.kt +67 -32
  4. package/android/src/main/java/expo/modules/ReactActivityDelegateWrapper.kt +6 -9
  5. package/android/src/main/java/expo/modules/fetch/ExpoFetchModule.kt +4 -3
  6. package/android/src/main/java/expo/modules/fetch/NativeResponse.kt +3 -3
  7. package/android/src/main/java/expo/modules/fetch/ResponseSink.kt +7 -3
  8. package/android/src/test/java/expo/modules/ReactActivityDelegateWrapperDelayLoadTest.kt +2 -3
  9. package/build/Expo.d.ts +1 -1
  10. package/build/Expo.d.ts.map +1 -1
  11. package/build/async-require/buildErrors.d.ts +5 -0
  12. package/build/async-require/buildErrors.d.ts.map +1 -0
  13. package/build/async-require/getDevServer.d.ts.map +1 -1
  14. package/build/async-require/getFullBundlerUrl.d.ts +2 -0
  15. package/build/async-require/getFullBundlerUrl.d.ts.map +1 -0
  16. package/build/async-require/hmr.d.ts +8 -8
  17. package/build/async-require/hmr.d.ts.map +1 -1
  18. package/build/async-require/hmrUtils.d.ts +13 -0
  19. package/build/async-require/hmrUtils.d.ts.map +1 -0
  20. package/build/async-require/hmrUtils.native.d.ts +13 -0
  21. package/build/async-require/hmrUtils.native.d.ts.map +1 -0
  22. package/build/dom/dom-entry.d.ts.map +1 -1
  23. package/build/dom/dom-internal.types.d.ts +11 -0
  24. package/build/dom/dom-internal.types.d.ts.map +1 -0
  25. package/build/dom/dom.types.d.ts +5 -0
  26. package/build/dom/dom.types.d.ts.map +1 -1
  27. package/build/dom/internal.d.ts +1 -0
  28. package/build/dom/internal.d.ts.map +1 -1
  29. package/build/dom/webview-wrapper.d.ts +2 -2
  30. package/build/dom/webview-wrapper.d.ts.map +1 -1
  31. package/build/hooks/useEvent.d.ts +2 -2
  32. package/bundledNativeModules.json +99 -101
  33. package/ios/AppDelegates/AppDelegatesLoaderDelegate.swift +2 -0
  34. package/ios/AppDelegates/EXReactRootViewFactory.h +8 -1
  35. package/ios/AppDelegates/EXReactRootViewFactory.mm +26 -0
  36. package/ios/AppDelegates/ExpoAppDelegate.swift +14 -55
  37. package/ios/AppDelegates/ExpoReactNativeFactory.h +12 -0
  38. package/ios/AppDelegates/ExpoReactNativeFactory.mm +45 -0
  39. package/ios/AppDelegates/ExpoReactNativeFactory.swift +22 -1
  40. package/ios/Expo.h +2 -1
  41. package/ios/Fetch/ExpoFetchModule.swift +2 -2
  42. package/local-build-cache-provider.d.ts +1 -0
  43. package/local-build-cache-provider.js +1 -0
  44. package/package.json +29 -23
  45. package/src/Expo.fx.tsx +1 -25
  46. package/src/Expo.fx.web.tsx +2 -2
  47. package/src/Expo.ts +3 -0
  48. package/src/__tests__/__fbBatchedBridgeConfig-test.ts +7 -3
  49. package/src/async-require/asyncRequireModule.ts +2 -2
  50. package/src/async-require/buildErrors.ts +14 -0
  51. package/src/async-require/getDevServer.ts +3 -9
  52. package/src/async-require/getFullBundlerUrl.ts +13 -0
  53. package/src/async-require/hmr.ts +118 -103
  54. package/src/async-require/hmrUtils.native.ts +97 -0
  55. package/src/async-require/hmrUtils.ts +54 -0
  56. package/src/async-require/index.ts +1 -1
  57. package/src/async-require/setupFastRefresh.ts +3 -2
  58. package/src/dom/dom-entry.tsx +15 -8
  59. package/src/dom/dom-internal.types.ts +9 -0
  60. package/src/dom/dom.types.ts +6 -0
  61. package/src/dom/internal.ts +2 -0
  62. package/src/dom/webview-wrapper.tsx +14 -6
  63. package/src/hooks/useEvent.ts +2 -2
  64. package/template.tgz +0 -0
  65. package/types/react-native-web.d.ts +1 -1
  66. package/android/src/main/java/expo/modules/ReactNativeHostWrapper.kt +0 -51
  67. package/android/src/main/java/expo/modules/ReactNativeHostWrapperBase.kt +0 -107
  68. package/build/async-require/hmr.native.d.ts +0 -3
  69. package/build/async-require/hmr.native.d.ts.map +0 -1
  70. package/ios/AppDelegates/EXAppDelegateWrapper.h +0 -30
  71. package/ios/AppDelegates/EXAppDelegateWrapper.mm +0 -112
  72. package/src/async-require/hmr.native.ts +0 -3
package/src/Expo.fx.tsx CHANGED
@@ -4,8 +4,7 @@ import './async-require';
4
4
  import 'expo-asset';
5
5
  import 'expo/virtual/rsc';
6
6
 
7
- import Constants from 'expo-constants';
8
- import { AppRegistry, NativeModules, LogBox, Platform } from 'react-native';
7
+ import { AppRegistry, NativeModules, LogBox } from 'react-native';
9
8
 
10
9
  import { isRunningInExpoGo } from './environment/ExpoGo';
11
10
  import { AppEntryNotFound } from './errors/AppEntryNotFound';
@@ -26,29 +25,6 @@ if (isRunningInExpoGo()) {
26
25
  ErrorUtils.setGlobalHandler(createErrorHandler(globalHandler));
27
26
  }
28
27
 
29
- // Warn if the New Architecture is not explicitly enabled in the app config and we are running in Expo Go.
30
- // This could be problematic because you will be developing your app with the New Architecture enabled and
31
- // but your builds will have the New Architecture disabled.
32
- if (__DEV__ && isRunningInExpoGo() && process.env.NODE_ENV === 'development') {
33
- (['android', 'ios'] as const).forEach((platform) => {
34
- const newArchPlatformConfig = Constants.expoConfig?.[platform]?.newArchEnabled;
35
- const newArchRootConfig = Constants.expoConfig?.newArchEnabled;
36
-
37
- const isNewArchExplicitlyDisabled =
38
- newArchPlatformConfig === false ||
39
- (newArchPlatformConfig === undefined && newArchRootConfig === false);
40
-
41
- if (Platform.OS === platform && isNewArchExplicitlyDisabled) {
42
- // Wrap it in rAF to show the warning after the React Native DevTools message
43
- requestAnimationFrame(() => {
44
- console.warn(
45
- `🚨 React Native's New Architecture is always enabled in Expo Go, but it is explicitly disabled in your project's app config. This may lead to unexpected behavior when creating a production or development build. Remove "newArchEnabled": false from your app.json.\nLearn more: https://docs.expo.dev/guides/new-architecture/`
46
- );
47
- });
48
- }
49
- });
50
- }
51
-
52
28
  // Disable the "Open debugger to view warnings" React Native DevTools warning in
53
29
  // Expo Go and expo-dev-client, because launching the debugger from there will not
54
30
  // get the correct JS target.
@@ -9,9 +9,9 @@ import 'expo/virtual/rsc';
9
9
  if (__DEV__) {
10
10
  if (
11
11
  // Skip mocking if someone is shimming this value out.
12
- !('__fbBatchedBridgeConfig' in global)
12
+ !('__fbBatchedBridgeConfig' in globalThis)
13
13
  ) {
14
- Object.defineProperty(global, '__fbBatchedBridgeConfig', {
14
+ Object.defineProperty(globalThis, '__fbBatchedBridgeConfig', {
15
15
  get() {
16
16
  throw new Error(
17
17
  "Your web project is importing a module from 'react-native' instead of 'react-native-web'. Learn more: https://expo.fyi/fb-batched-bridge-config-web"
package/src/Expo.ts CHANGED
@@ -18,6 +18,9 @@ export {
18
18
  requireNativeViewManager as requireNativeView,
19
19
  registerWebModule,
20
20
  reloadAppAsync,
21
+
22
+ // Worklets
23
+ installOnUIRuntime,
21
24
  } from 'expo-modules-core';
22
25
 
23
26
  export type {
@@ -6,14 +6,18 @@ jest.mock('../async-require/getDevServer');
6
6
 
7
7
  if (Platform.OS === 'web') {
8
8
  it('provides a helpful error message on web', () => {
9
+ const error =
10
+ /Your web project is importing a module from 'react-native' instead of 'react-native-web'/;
9
11
  // @ts-ignore
10
- expect(() => global.__fbBatchedBridgeConfig).toThrow(
11
- /Your web project is importing a module from 'react-native' instead of 'react-native-web'/
12
- );
12
+ expect(() => global.__fbBatchedBridgeConfig).toThrow(error);
13
+ // @ts-ignore
14
+ expect(() => globalThis.__fbBatchedBridgeConfig).toThrow(error);
13
15
  });
14
16
  } else {
15
17
  it(`does not change the functionality of __fbBatchedBridgeConfig on native`, () => {
16
18
  // @ts-ignore
17
19
  expect(() => global.__fbBatchedBridgeConfig).not.toThrow();
20
+ // @ts-ignore
21
+ expect(() => globalThis.__fbBatchedBridgeConfig).not.toThrow();
18
22
  });
19
23
  }
@@ -20,8 +20,8 @@ type DependencyMapPaths = { [moduleID: number | string]: unknown } | null;
20
20
  declare let __METRO_GLOBAL_PREFIX__: string;
21
21
 
22
22
  function maybeLoadBundle(moduleID: number, paths: DependencyMapPaths): void | Promise<void> {
23
- const loadBundle: (bundlePath: unknown) => Promise<void> = (global as any)[
24
- `${__METRO_GLOBAL_PREFIX__}__loadBundleAsync`
23
+ const loadBundle: (bundlePath: unknown) => Promise<void> = (globalThis as any)[
24
+ `${__METRO_GLOBAL_PREFIX__ ?? ''}__loadBundleAsync`
25
25
  ];
26
26
 
27
27
  if (loadBundle != null) {
@@ -0,0 +1,14 @@
1
+ import { withoutANSIColorStyles } from '@expo/log-box/utils';
2
+
3
+ export class HMRMetroBuildError extends Error {
4
+ public originalMessage: string;
5
+
6
+ constructor(message: string = 'Unknown Metro Error', type?: string, cause?: Error) {
7
+ super(message);
8
+ this.name = type || 'BuildError';
9
+ this.cause = cause;
10
+ this.originalMessage = [type, message].filter(Boolean).join(': ');
11
+ this.message = withoutANSIColorStyles(message);
12
+ this.stack = '';
13
+ }
14
+ }
@@ -1,3 +1,5 @@
1
+ import { getFullBundlerUrl } from './getFullBundlerUrl';
2
+
1
3
  const getDevServer = () => {
2
4
  // Disable for SSR
3
5
  if (typeof window === 'undefined') {
@@ -14,15 +16,7 @@ const getDevServer = () => {
14
16
 
15
17
  /** URL but ensures that platform query param is added. */
16
18
  get fullBundleUrl() {
17
- if (document?.currentScript && 'src' in document.currentScript) {
18
- return document.currentScript.src;
19
- }
20
-
21
- const bundleUrl = new URL(location.href);
22
-
23
- bundleUrl.searchParams.set('platform', 'web');
24
-
25
- return bundleUrl.toString();
19
+ return getFullBundlerUrl();
26
20
  },
27
21
  url: location.origin + location.pathname,
28
22
  };
@@ -0,0 +1,13 @@
1
+ export function getFullBundlerUrl(): string {
2
+ const currentScript = document?.currentScript;
3
+ const bundleUrl = new URL(
4
+ currentScript && 'src' in currentScript ? currentScript.src : location.href,
5
+ location.href
6
+ );
7
+
8
+ if (!bundleUrl.searchParams.has('platform')) {
9
+ bundleUrl.searchParams.set('platform', process.env.EXPO_OS ?? 'web');
10
+ }
11
+
12
+ return bundleUrl.toString();
13
+ }
@@ -10,35 +10,24 @@
10
10
  */
11
11
  import MetroHMRClient from '@expo/metro/metro-runtime/modules/HMRClient';
12
12
  import prettyFormat, { plugins } from 'pretty-format';
13
- import { DeviceEventEmitter } from 'react-native';
14
13
 
15
- // Ensure events are sent so custom Fast Refresh views are shown.
16
- function showLoading(message: string, _type: 'load' | 'refresh') {
17
- DeviceEventEmitter.emit('devLoadingView:showMessage', {
18
- message,
19
- });
20
- }
21
-
22
- function hideLoading() {
23
- DeviceEventEmitter.emit('devLoadingView:hide', {});
24
- }
14
+ import {
15
+ getConnectionError,
16
+ getFullBundlerUrl,
17
+ handleCompileError,
18
+ hideLoading,
19
+ resetErrorOverlay,
20
+ showLoading,
21
+ } from './hmrUtils';
25
22
 
26
23
  const pendingEntryPoints: string[] = [];
27
24
 
28
25
  // @ts-expect-error: Account for multiple versions of pretty-format inside of a monorepo.
29
26
  const prettyFormatFunc = typeof prettyFormat === 'function' ? prettyFormat : prettyFormat.default;
30
27
 
31
- type HMRClientType = {
32
- send: (msg: string) => void;
33
- isEnabled: () => boolean;
34
- disable: () => void;
35
- enable: () => void;
36
- hasPendingUpdates: () => boolean;
37
- };
38
-
39
- let hmrClient: HMRClientType | null = null;
28
+ let hmrClient: MetroHMRClient | null = null;
40
29
  let hmrUnavailableReason: string | null = null;
41
- let currentCompileErrorMessage: string | null = null;
30
+ const buildErrorQueue = new Set<unknown>();
42
31
  let didConnect: boolean = false;
43
32
  const pendingLogs: [LogLevel, any[]][] = [];
44
33
 
@@ -53,14 +42,6 @@ type LogLevel =
53
42
  | 'groupEnd'
54
43
  | 'debug';
55
44
 
56
- export type HMRClientNativeInterface = {
57
- enable(): void;
58
- disable(): void;
59
- registerBundle(requestUrl: string): void;
60
- log(level: LogLevel, data: any[]): void;
61
- setup(props: { isEnabled: boolean }): void;
62
- };
63
-
64
45
  function assert(foo: any, msg: string): asserts foo {
65
46
  if (!foo) throw new Error(msg);
66
47
  }
@@ -69,7 +50,7 @@ function assert(foo: any, msg: string): asserts foo {
69
50
  * HMR Client that receives from the server HMR updates and propagates them
70
51
  * runtime to reflects those changes.
71
52
  */
72
- const HMRClient: HMRClientNativeInterface = {
53
+ const HMRClient = {
73
54
  enable() {
74
55
  if (hmrUnavailableReason !== null) {
75
56
  // If HMR became unavailable while you weren't using it,
@@ -127,12 +108,19 @@ const HMRClient: HMRClientNativeInterface = {
127
108
  return;
128
109
  }
129
110
  try {
111
+ const webMetadata =
112
+ process.env.EXPO_OS === 'web'
113
+ ? {
114
+ platform: 'web',
115
+ mode: 'BRIDGE',
116
+ }
117
+ : undefined;
130
118
  hmrClient.send(
131
119
  JSON.stringify({
120
+ // TODO: Type this properly.
121
+ ...webMetadata,
132
122
  type: 'log',
133
123
  level,
134
- platform: 'web',
135
- mode: 'BRIDGE',
136
124
  data: data.map((item) =>
137
125
  typeof item === 'string'
138
126
  ? item
@@ -154,50 +142,63 @@ const HMRClient: HMRClientNativeInterface = {
154
142
 
155
143
  // Called once by the bridge on startup, even if Fast Refresh is off.
156
144
  // It creates the HMR client but doesn't actually set up the socket yet.
157
- setup({ isEnabled }: { isEnabled: boolean }) {
158
- assert(!hmrClient, 'Cannot initialize hmrClient twice');
159
-
160
- const serverScheme = window.location.protocol === 'https:' ? 'wss' : 'ws';
161
- const client = new MetroHMRClient(`${serverScheme}://${window.location.host}/hot`);
162
- hmrClient = client;
145
+ setup(
146
+ platformOrOptions: string | { isEnabled: boolean },
147
+ bundleEntry?: string,
148
+ host?: string,
149
+ port?: number | string,
150
+ isEnabledOrUndefined?: boolean,
151
+ scheme: string = 'http'
152
+ ) {
153
+ let isEnabled = !!isEnabledOrUndefined;
154
+ let serverScheme: string;
155
+ let serverHost: string;
156
+
157
+ if (process.env.EXPO_OS === 'web') {
158
+ assert(
159
+ platformOrOptions && typeof platformOrOptions === 'object',
160
+ 'Expected platformOrOptions to be an options object on web.'
161
+ );
162
+ assert(!hmrClient, 'Cannot initialize hmrClient twice');
163
+ isEnabled = platformOrOptions.isEnabled;
163
164
 
164
- const fullBundleUrl = (() => {
165
- const currentScript = document?.currentScript;
166
- const bundleUrl = new URL(
167
- currentScript && 'src' in currentScript ? currentScript.src : location.href,
168
- location.href
165
+ serverScheme = window.location.protocol === 'https:' ? 'wss' : 'ws';
166
+ serverHost = window.location.host;
167
+ } else {
168
+ assert(
169
+ platformOrOptions && typeof platformOrOptions === 'string',
170
+ 'Missing required parameter `platform`'
169
171
  );
172
+ assert(bundleEntry, 'Missing required parameter `bundleEntry`');
173
+ assert(host, 'Missing required parameter `host`');
174
+ assert(!hmrClient, 'Cannot initialize hmrClient twice');
170
175
 
171
- if (!bundleUrl.searchParams.has('platform')) {
172
- bundleUrl.searchParams.set('platform', process.env.EXPO_OS ?? 'web');
173
- }
176
+ serverHost = port !== null && port !== '' ? `${host}:${port}` : host;
177
+ serverScheme = scheme;
178
+ }
174
179
 
175
- return bundleUrl.toString();
176
- })();
180
+ const origin = `${serverScheme}://${serverHost}`;
181
+ const client = new MetroHMRClient(`${origin}/hot`);
182
+ hmrClient = client;
177
183
 
178
184
  pendingEntryPoints.push(
179
185
  // HMRServer understands regular bundle URLs, so prefer that in case
180
186
  // there are any important URL parameters we can't reconstruct from
181
187
  // `setup()`'s arguments.
182
- fullBundleUrl
188
+ getFullBundlerUrl({
189
+ serverScheme,
190
+ serverHost,
191
+ bundleEntry,
192
+ platform: typeof platformOrOptions === 'string' ? platformOrOptions : undefined,
193
+ })
183
194
  );
184
195
 
185
196
  client.on('connection-error', (e: Error) => {
186
- let error = `Cannot connect to Metro.
187
-
188
- Try the following to fix the issue:
189
- - Ensure the Metro dev server is running and available on the same network as this device`;
190
- error += `
191
-
192
- URL: ${window.location.host}
193
-
194
- Error: ${e.message}`;
195
-
196
- setHMRUnavailableReason(error);
197
+ setHMRUnavailableReason(getConnectionError(serverHost, e));
197
198
  });
198
199
 
199
200
  client.on('update-start', ({ isInitialUpdate }: { isInitialUpdate?: boolean }) => {
200
- currentCompileErrorMessage = null;
201
+ buildErrorQueue.clear();
201
202
  didConnect = true;
202
203
 
203
204
  if (client.isEnabled() && !isInitialUpdate) {
@@ -205,35 +206,32 @@ const HMRClient: HMRClientNativeInterface = {
205
206
  }
206
207
  });
207
208
 
208
- client.on('update', ({ isInitialUpdate }: { isInitialUpdate?: boolean }) => {
209
- if (client.isEnabled() && !isInitialUpdate) {
210
- dismissRedbox();
211
- // @ts-expect-error
212
- globalThis.__expo_dev_resetErrors?.();
213
- // LogBox.clearAllLogs();
209
+ client.on(
210
+ 'update',
211
+ ({
212
+ isInitialUpdate,
213
+ added,
214
+ modified,
215
+ deleted,
216
+ }: {
217
+ isInitialUpdate?: boolean;
218
+ added: unknown[];
219
+ modified: unknown[];
220
+ deleted: unknown[];
221
+ }) => {
222
+ // NOTE(@krystofwoldrich): I don't know why sometimes empty updates are sent. But they should not reset the overlay.
223
+ const isEmpty = added.length === 0 && modified.length === 0 && deleted.length === 0;
224
+ if (client.isEnabled() && !isInitialUpdate && !isEmpty) {
225
+ resetErrorOverlay();
226
+ }
214
227
  }
215
- });
228
+ );
216
229
 
217
230
  client.on('update-done', () => {
218
231
  hideLoading();
219
232
  });
220
233
 
221
- client.on('error', (data: { type: string; message: string }) => {
222
- hideLoading();
223
-
224
- if (data.type === 'GraphNotFoundError') {
225
- client.close();
226
- setHMRUnavailableReason('Metro has restarted since the last edit. Reload to reconnect.');
227
- } else if (data.type === 'RevisionNotFoundError') {
228
- client.close();
229
- setHMRUnavailableReason('Metro and the client are out of sync. Reload to reconnect.');
230
- } else {
231
- currentCompileErrorMessage = `${data.type} ${data.message}`;
232
- if (client.isEnabled()) {
233
- showCompileError();
234
- }
235
- }
236
- });
234
+ client.on('error', (data) => this._onMetroError(data));
237
235
 
238
236
  client.on('close', (closeEvent?: { code: number; reason: string }) => {
239
237
  hideLoading();
@@ -262,6 +260,37 @@ const HMRClient: HMRClientNativeInterface = {
262
260
  registerBundleEntryPoints(hmrClient);
263
261
  flushEarlyLogs();
264
262
  },
263
+
264
+ // Related Metro error's formatting
265
+ // https://github.com/facebook/metro/blob/34bb8913ec4b5b02690b39d2246599faf094f721/packages/metro/src/lib/formatBundlingError.js#L36
266
+ _onMetroError(error: unknown) {
267
+ if (!hmrClient) {
268
+ return;
269
+ }
270
+
271
+ assert(typeof error === 'object' && error != null, 'Expected data to be an object');
272
+
273
+ hideLoading();
274
+
275
+ if ('type' in error) {
276
+ if (error.type === 'GraphNotFoundError') {
277
+ hmrClient.close();
278
+ setHMRUnavailableReason('Expo CLI has restarted since the last edit. Reload to reconnect.');
279
+ return;
280
+ } else if (error.type === 'RevisionNotFoundError') {
281
+ hmrClient.close();
282
+ setHMRUnavailableReason(
283
+ `Expo CLI and the ${process.env.EXPO_OS} client are out of sync. Reload to reconnect.`
284
+ );
285
+ return;
286
+ }
287
+ }
288
+
289
+ buildErrorQueue.add(error);
290
+ if (hmrClient.isEnabled()) {
291
+ showCompileError();
292
+ }
293
+ },
265
294
  };
266
295
 
267
296
  function setHMRUnavailableReason(reason: string) {
@@ -286,7 +315,7 @@ function setHMRUnavailableReason(reason: string) {
286
315
  }
287
316
  }
288
317
 
289
- function registerBundleEntryPoints(client: HMRClientType | null) {
318
+ function registerBundleEntryPoints(client: MetroHMRClient | null) {
290
319
  if (hmrUnavailableReason != null) {
291
320
  // "Bundle Splitting – Metro disconnected"
292
321
  window.location.reload();
@@ -314,28 +343,14 @@ function flushEarlyLogs() {
314
343
  }
315
344
  }
316
345
 
317
- function dismissRedbox() {
318
- // TODO(EvanBacon): Error overlay for web.
319
- }
320
-
321
346
  function showCompileError() {
322
- if (currentCompileErrorMessage === null) {
347
+ if (buildErrorQueue.size === 0) {
323
348
  return;
324
349
  }
325
350
 
326
- // Even if there is already a redbox, syntax errors are more important.
327
- // Otherwise you risk seeing a stale runtime error while a syntax error is more recent.
328
- dismissRedbox();
329
-
330
- const message = currentCompileErrorMessage;
331
- currentCompileErrorMessage = null;
332
-
333
- const error = new Error(message);
334
- // Symbolicating compile errors is wasted effort
335
- // because the stack trace is meaningless:
336
- // @ts-expect-error
337
- error.preventSymbolication = true;
338
- throw error;
351
+ const cause: any = buildErrorQueue.values().next().value;
352
+ buildErrorQueue.clear();
353
+ handleCompileError(cause);
339
354
  }
340
355
 
341
356
  export default HMRClient;
@@ -0,0 +1,97 @@
1
+ // Based on https://github.com/facebook/react-native/blob/9ab95dd2b5746e8323ad1d65591d5a4ec7718790/packages/react-native/Libraries/Utilities/HMRClient.js
2
+
3
+ // @ts-expect-error missing types
4
+ import getDevServer from 'react-native/Libraries/Core/Devtools/getDevServer';
5
+ import LogBox from 'react-native/Libraries/LogBox/LogBox';
6
+ // @ts-expect-error missing types
7
+ import NativeRedBox from 'react-native/Libraries/NativeModules/specs/NativeRedBox';
8
+ import DevSettings from 'react-native/Libraries/Utilities/DevSettings';
9
+
10
+ import { HMRMetroBuildError } from './buildErrors';
11
+
12
+ export function showLoading(message: string, type: 'load' | 'refresh') {
13
+ const DevLoadingView = require('react-native/Libraries/Utilities/DevLoadingView').default;
14
+ DevLoadingView.showMessage(message, type);
15
+ }
16
+
17
+ export function hideLoading() {
18
+ const DevLoadingView = require('react-native/Libraries/Utilities/DevLoadingView').default;
19
+ DevLoadingView.hide();
20
+ }
21
+
22
+ export function resetErrorOverlay() {
23
+ dismissRedbox();
24
+ // @ts-expect-error clearAllLogs exists, but ts types are missing
25
+ LogBox.clearAllLogs();
26
+ }
27
+
28
+ export function reload() {
29
+ // @ts-expect-error missing types
30
+ DevSettings.reload('Bundle Splitting – Metro disconnected');
31
+ }
32
+
33
+ export function getFullBundlerUrl({
34
+ serverScheme,
35
+ serverHost,
36
+ bundleEntry,
37
+ platform,
38
+ }: {
39
+ serverScheme: string;
40
+ serverHost: string;
41
+ bundleEntry: string;
42
+ platform: string;
43
+ }): string {
44
+ return (
45
+ getDevServer().fullBundleUrl ??
46
+ `${serverScheme}://${serverHost}/hot?bundleEntry=${bundleEntry}&platform=${platform}`
47
+ );
48
+ }
49
+
50
+ export function getConnectionError(serverHost: string, e: Error): string {
51
+ let error = `Cannot connect to Expo CLI.
52
+
53
+ Try the following to fix the issue:
54
+ - Ensure that Expo dev server is running and available on the same network`;
55
+
56
+ if (process.env.EXPO_OS === 'android') {
57
+ error += `
58
+ - Ensure that your device/emulator is connected to your machine and has USB debugging enabled - run 'adb devices' to see a list of connected devices
59
+ - If you're on a physical device connected to the same machine, run 'adb reverse tcp:8081 tcp:8081' to forward requests from your device`;
60
+ }
61
+
62
+ error += `
63
+
64
+ URL: ${serverHost}
65
+
66
+ Error: ${e.message}`;
67
+ return error;
68
+ }
69
+
70
+ function dismissRedbox() {
71
+ if (process.env.EXPO_OS === 'ios' && NativeRedBox != null && NativeRedBox.dismiss != null) {
72
+ NativeRedBox.dismiss();
73
+ } else {
74
+ const NativeExceptionsManager =
75
+ require('react-native/Libraries/Core/NativeExceptionsManager').default;
76
+ NativeExceptionsManager &&
77
+ NativeExceptionsManager.dismissRedbox &&
78
+ NativeExceptionsManager.dismissRedbox();
79
+ }
80
+ }
81
+
82
+ export function handleCompileError(cause: any) {
83
+ if (cause === null) {
84
+ return;
85
+ }
86
+
87
+ // Even if there is already a redbox, syntax errors are more important.
88
+ // Otherwise you risk seeing a stale runtime error while a syntax error is more recent.
89
+ dismissRedbox();
90
+
91
+ const LogBox = require('react-native/Libraries/LogBox/LogBox').default;
92
+ // The error is passed thru LogBox APIs directly to the parsing function.
93
+ // Won't log the error in devtools console
94
+ // (using throw would mangle the error message and print with ANSI
95
+ // because throw on native is processed as console.error)
96
+ LogBox.addException(new HMRMetroBuildError(cause.message, cause.type, cause.cause));
97
+ }
@@ -0,0 +1,54 @@
1
+ import { DeviceEventEmitter } from 'react-native';
2
+
3
+ import { HMRMetroBuildError } from './buildErrors';
4
+ import { getFullBundlerUrl as getFullBundlerUrlHelper } from './getFullBundlerUrl';
5
+
6
+ export function getFullBundlerUrl(_: {
7
+ serverScheme?: string;
8
+ serverHost?: string;
9
+ bundleEntry?: string;
10
+ platform?: string;
11
+ }): string {
12
+ return getFullBundlerUrlHelper();
13
+ }
14
+
15
+ export function showLoading(message: string, _type: 'load' | 'refresh') {
16
+ // Ensure events are sent so custom Fast Refresh views are shown.
17
+ DeviceEventEmitter.emit('devLoadingView:showMessage', {
18
+ message,
19
+ });
20
+ }
21
+
22
+ export function hideLoading() {
23
+ DeviceEventEmitter.emit('devLoadingView:hide', {});
24
+ }
25
+
26
+ export function resetErrorOverlay() {
27
+ // @ts-expect-error
28
+ globalThis.__expo_dev_resetErrors?.();
29
+ }
30
+
31
+ export function reload() {
32
+ // "Bundle Splitting – Metro disconnected"
33
+ window.location.reload();
34
+ }
35
+
36
+ export function getConnectionError(serverHost: string, e: Error): string {
37
+ return `
38
+ Cannot connect to Expo CLI.
39
+
40
+ Try the following to fix the issue:
41
+ - Ensure the Expo dev server is running and available on the same network as this device
42
+
43
+ URL: ${serverHost}
44
+
45
+ Error: ${e.message}
46
+ `.trim();
47
+ }
48
+
49
+ export function handleCompileError(cause: any) {
50
+ if (!cause) {
51
+ return;
52
+ }
53
+ throw new HMRMetroBuildError(cause.message, cause.type, cause.cause);
54
+ }
@@ -7,4 +7,4 @@
7
7
  import { buildAsyncRequire } from './buildAsyncRequire';
8
8
 
9
9
  // @ts-ignore: ignore the global which may not always be defined in jest environments.
10
- global[`${global.__METRO_GLOBAL_PREFIX__ ?? ''}__loadBundleAsync`] = buildAsyncRequire();
10
+ globalThis[`${globalThis.__METRO_GLOBAL_PREFIX__ ?? ''}__loadBundleAsync`] = buildAsyncRequire();
@@ -1,7 +1,7 @@
1
1
  // This needs to run before the renderer initializes.
2
2
 
3
3
  const ReactRefreshRuntime = require('react-refresh/runtime');
4
- ReactRefreshRuntime.injectIntoGlobalHook(global);
4
+ ReactRefreshRuntime.injectIntoGlobalHook(globalThis);
5
5
 
6
6
  const Refresh = {
7
7
  performFullRefresh() {
@@ -27,4 +27,5 @@ const Refresh = {
27
27
 
28
28
  // The metro require polyfill can not have dependencies (applies for all polyfills).
29
29
  // Expose `Refresh` by assigning it to global to make it available in the polyfill.
30
- global[(global.__METRO_GLOBAL_PREFIX__ || '') + '__ReactRefresh'] = Refresh;
30
+ // @ts-ignore: ignore the global which may not always be defined in jest environments.
31
+ globalThis[(globalThis.__METRO_GLOBAL_PREFIX__ ?? '') + '__ReactRefresh'] = Refresh;