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.
- package/Expo.podspec +1 -0
- package/android/build.gradle +5 -5
- package/android/src/main/java/expo/modules/ExpoReactHostFactory.kt +67 -32
- package/android/src/main/java/expo/modules/ReactActivityDelegateWrapper.kt +6 -9
- package/android/src/main/java/expo/modules/fetch/ExpoFetchModule.kt +4 -3
- package/android/src/main/java/expo/modules/fetch/NativeResponse.kt +3 -3
- package/android/src/main/java/expo/modules/fetch/ResponseSink.kt +7 -3
- package/android/src/test/java/expo/modules/ReactActivityDelegateWrapperDelayLoadTest.kt +2 -3
- package/build/Expo.d.ts +1 -1
- package/build/Expo.d.ts.map +1 -1
- package/build/async-require/buildErrors.d.ts +5 -0
- package/build/async-require/buildErrors.d.ts.map +1 -0
- package/build/async-require/getDevServer.d.ts.map +1 -1
- package/build/async-require/getFullBundlerUrl.d.ts +2 -0
- package/build/async-require/getFullBundlerUrl.d.ts.map +1 -0
- package/build/async-require/hmr.d.ts +8 -8
- package/build/async-require/hmr.d.ts.map +1 -1
- package/build/async-require/hmrUtils.d.ts +13 -0
- package/build/async-require/hmrUtils.d.ts.map +1 -0
- package/build/async-require/hmrUtils.native.d.ts +13 -0
- package/build/async-require/hmrUtils.native.d.ts.map +1 -0
- package/build/dom/dom-entry.d.ts.map +1 -1
- package/build/dom/dom-internal.types.d.ts +11 -0
- package/build/dom/dom-internal.types.d.ts.map +1 -0
- package/build/dom/dom.types.d.ts +5 -0
- package/build/dom/dom.types.d.ts.map +1 -1
- package/build/dom/internal.d.ts +1 -0
- package/build/dom/internal.d.ts.map +1 -1
- package/build/dom/webview-wrapper.d.ts +2 -2
- package/build/dom/webview-wrapper.d.ts.map +1 -1
- package/build/hooks/useEvent.d.ts +2 -2
- package/bundledNativeModules.json +99 -101
- package/ios/AppDelegates/AppDelegatesLoaderDelegate.swift +2 -0
- package/ios/AppDelegates/EXReactRootViewFactory.h +8 -1
- package/ios/AppDelegates/EXReactRootViewFactory.mm +26 -0
- package/ios/AppDelegates/ExpoAppDelegate.swift +14 -55
- package/ios/AppDelegates/ExpoReactNativeFactory.h +12 -0
- package/ios/AppDelegates/ExpoReactNativeFactory.mm +45 -0
- package/ios/AppDelegates/ExpoReactNativeFactory.swift +22 -1
- package/ios/Expo.h +2 -1
- package/ios/Fetch/ExpoFetchModule.swift +2 -2
- package/local-build-cache-provider.d.ts +1 -0
- package/local-build-cache-provider.js +1 -0
- package/package.json +29 -23
- package/src/Expo.fx.tsx +1 -25
- package/src/Expo.fx.web.tsx +2 -2
- package/src/Expo.ts +3 -0
- package/src/__tests__/__fbBatchedBridgeConfig-test.ts +7 -3
- package/src/async-require/asyncRequireModule.ts +2 -2
- package/src/async-require/buildErrors.ts +14 -0
- package/src/async-require/getDevServer.ts +3 -9
- package/src/async-require/getFullBundlerUrl.ts +13 -0
- package/src/async-require/hmr.ts +118 -103
- package/src/async-require/hmrUtils.native.ts +97 -0
- package/src/async-require/hmrUtils.ts +54 -0
- package/src/async-require/index.ts +1 -1
- package/src/async-require/setupFastRefresh.ts +3 -2
- package/src/dom/dom-entry.tsx +15 -8
- package/src/dom/dom-internal.types.ts +9 -0
- package/src/dom/dom.types.ts +6 -0
- package/src/dom/internal.ts +2 -0
- package/src/dom/webview-wrapper.tsx +14 -6
- package/src/hooks/useEvent.ts +2 -2
- package/template.tgz +0 -0
- package/types/react-native-web.d.ts +1 -1
- package/android/src/main/java/expo/modules/ReactNativeHostWrapper.kt +0 -51
- package/android/src/main/java/expo/modules/ReactNativeHostWrapperBase.kt +0 -107
- package/build/async-require/hmr.native.d.ts +0 -3
- package/build/async-require/hmr.native.d.ts.map +0 -1
- package/ios/AppDelegates/EXAppDelegateWrapper.h +0 -30
- package/ios/AppDelegates/EXAppDelegateWrapper.mm +0 -112
- 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
|
|
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.
|
package/src/Expo.fx.web.tsx
CHANGED
|
@@ -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
|
|
12
|
+
!('__fbBatchedBridgeConfig' in globalThis)
|
|
13
13
|
) {
|
|
14
|
-
Object.defineProperty(
|
|
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
|
@@ -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
|
-
|
|
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> = (
|
|
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
|
-
|
|
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
|
+
}
|
package/src/async-require/hmr.ts
CHANGED
|
@@ -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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
176
|
+
serverHost = port !== null && port !== '' ? `${host}:${port}` : host;
|
|
177
|
+
serverScheme = scheme;
|
|
178
|
+
}
|
|
174
179
|
|
|
175
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
|
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:
|
|
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 (
|
|
347
|
+
if (buildErrorQueue.size === 0) {
|
|
323
348
|
return;
|
|
324
349
|
}
|
|
325
350
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
30
|
+
// @ts-ignore: ignore the global which may not always be defined in jest environments.
|
|
31
|
+
globalThis[(globalThis.__METRO_GLOBAL_PREFIX__ ?? '') + '__ReactRefresh'] = Refresh;
|