expo 56.0.8 → 56.0.10
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/android/build.gradle +2 -2
- package/android/src/main/java/expo/modules/fetch/NativeRequest.kt +16 -11
- package/android/src/test/java/expo/modules/fetch/NativeRequestTest.kt +33 -0
- package/bundledNativeModules.json +37 -37
- package/ios/AppDelegates/ExpoReactNativeFactory.h +5 -0
- package/ios/AppDelegates/ExpoReactNativeFactory.mm +8 -0
- package/package.json +13 -13
- package/src/async-require/__tests__/asyncRequireModule.test.ts +80 -12
- package/src/async-require/asyncRequireModule.ts +30 -9
- package/src/async-require/setupHMR.ts +56 -1
- package/template.tgz +0 -0
package/android/build.gradle
CHANGED
|
@@ -10,7 +10,7 @@ buildscript {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
group = 'host.exp.exponent'
|
|
13
|
-
version = '56.0.
|
|
13
|
+
version = '56.0.10'
|
|
14
14
|
|
|
15
15
|
expoModule {
|
|
16
16
|
// We can't prebuild the module because it depends on the generated files.
|
|
@@ -21,7 +21,7 @@ android {
|
|
|
21
21
|
namespace "expo.core"
|
|
22
22
|
defaultConfig {
|
|
23
23
|
versionCode 1
|
|
24
|
-
versionName "56.0.
|
|
24
|
+
versionName "56.0.10"
|
|
25
25
|
consumerProguardFiles("proguard-rules.pro")
|
|
26
26
|
}
|
|
27
27
|
testOptions {
|
|
@@ -6,9 +6,11 @@ import expo.modules.kotlin.AppContext
|
|
|
6
6
|
import expo.modules.kotlin.sharedobjects.SharedObject
|
|
7
7
|
import okhttp3.Call
|
|
8
8
|
import okhttp3.CookieJar
|
|
9
|
+
import okhttp3.MediaType
|
|
9
10
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
|
10
11
|
import okhttp3.OkHttpClient
|
|
11
12
|
import okhttp3.Request
|
|
13
|
+
import okhttp3.RequestBody
|
|
12
14
|
import okhttp3.RequestBody.Companion.toRequestBody
|
|
13
15
|
import java.net.URL
|
|
14
16
|
|
|
@@ -16,6 +18,19 @@ private data class RequestHolder(var request: Request?)
|
|
|
16
18
|
|
|
17
19
|
internal val METHODS_REQUIRING_BODY = arrayOf("POST", "PUT", "PATCH")
|
|
18
20
|
|
|
21
|
+
internal fun buildRequestBody(method: String, requestBody: ByteArray?, mediaType: MediaType?): RequestBody? {
|
|
22
|
+
return requestBody?.toRequestBody(mediaType) ?: run {
|
|
23
|
+
// OkHttp requires a non-null body for POST/PATCH/PUT, unlike WinterTC fetch. Provide an empty
|
|
24
|
+
// (zero-length) body to match it, not `byteArrayOf(0)`, which sends a single 0x00 byte.
|
|
25
|
+
// Ref: https://github.com/expo/expo/issues/46668
|
|
26
|
+
if (method in METHODS_REQUIRING_BODY) {
|
|
27
|
+
ByteArray(0).toRequestBody(mediaType)
|
|
28
|
+
} else {
|
|
29
|
+
null
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
19
34
|
internal class NativeRequest(appContext: AppContext, internal val response: NativeResponse) :
|
|
20
35
|
SharedObject(appContext) {
|
|
21
36
|
private val requestHolder = RequestHolder(null)
|
|
@@ -36,17 +51,7 @@ internal class NativeRequest(appContext: AppContext, internal val response: Nati
|
|
|
36
51
|
|
|
37
52
|
val headers = requestInit.headers.toHeaders()
|
|
38
53
|
val mediaType = headers["Content-Type"]?.toMediaTypeOrNull()
|
|
39
|
-
val reqBody = requestBody
|
|
40
|
-
// OkHttp requires a non-null body for POST, PATCH, and PUT requests.
|
|
41
|
-
// WinterTC fetch, however, does not have this limitation.
|
|
42
|
-
// Provide an empty body to make OkHttp behave like WinterTC fetch.
|
|
43
|
-
// Ref: https://github.com/expo/expo/issues/35950#issuecomment-3245173248
|
|
44
|
-
if (requestInit.method in METHODS_REQUIRING_BODY) {
|
|
45
|
-
byteArrayOf(0).toRequestBody(mediaType)
|
|
46
|
-
} else {
|
|
47
|
-
null
|
|
48
|
-
}
|
|
49
|
-
}
|
|
54
|
+
val reqBody = buildRequestBody(requestInit.method, requestBody, mediaType)
|
|
50
55
|
val request = Request.Builder()
|
|
51
56
|
.headers(headers)
|
|
52
57
|
.method(requestInit.method, reqBody)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Copyright 2015-present 650 Industries. All rights reserved.
|
|
2
|
+
|
|
3
|
+
package expo.modules.fetch
|
|
4
|
+
|
|
5
|
+
import com.google.common.truth.Truth.assertThat
|
|
6
|
+
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
|
7
|
+
import org.junit.Test
|
|
8
|
+
|
|
9
|
+
class NativeRequestTest {
|
|
10
|
+
private val jsonMediaType = "application/json".toMediaTypeOrNull()
|
|
11
|
+
|
|
12
|
+
@Test
|
|
13
|
+
fun `null body on a method requiring a body produces an empty body, not a null byte`() {
|
|
14
|
+
val body = buildRequestBody("POST", null, jsonMediaType)
|
|
15
|
+
assertThat(body).isNotNull()
|
|
16
|
+
// Regression test for https://github.com/expo/expo/issues/46668:
|
|
17
|
+
// OkHttp requires a non-null body for POST/PUT/PATCH, but the body must be
|
|
18
|
+
// empty (Content-Length: 0) rather than a single 0x00 byte (Content-Length: 1).
|
|
19
|
+
assertThat(body!!.contentLength()).isEqualTo(0L)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@Test
|
|
23
|
+
fun `null body on a method not requiring a body produces no body`() {
|
|
24
|
+
assertThat(buildRequestBody("GET", null, jsonMediaType)).isNull()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@Test
|
|
28
|
+
fun `a provided body is preserved`() {
|
|
29
|
+
val body = buildRequestBody("POST", "hello".toByteArray(), jsonMediaType)
|
|
30
|
+
assertThat(body).isNotNull()
|
|
31
|
+
assertThat(body!!.contentLength()).isEqualTo(5L)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"@expo/fingerprint": "~0.19.
|
|
3
|
-
"@expo/metro-runtime": "~56.0.
|
|
2
|
+
"@expo/fingerprint": "~0.19.4",
|
|
3
|
+
"@expo/metro-runtime": "~56.0.15",
|
|
4
4
|
"@expo/vector-icons": "^15.0.2",
|
|
5
|
-
"@expo/ui": "~56.0.
|
|
5
|
+
"@expo/ui": "~56.0.17",
|
|
6
6
|
"@react-native-async-storage/async-storage": "2.2.0",
|
|
7
7
|
"@react-native-community/datetimepicker": "9.1.0",
|
|
8
8
|
"@react-native-masked-view/masked-view": "0.3.2",
|
|
@@ -16,28 +16,28 @@
|
|
|
16
16
|
"expo-analytics-amplitude": "~11.3.0",
|
|
17
17
|
"expo-app-auth": "~11.1.0",
|
|
18
18
|
"expo-app-loader-provider": "~8.0.0",
|
|
19
|
-
"expo-app-metrics": "~56.0.
|
|
19
|
+
"expo-app-metrics": "~56.0.18",
|
|
20
20
|
"expo-apple-authentication": "~56.0.4",
|
|
21
21
|
"expo-application": "~56.0.3",
|
|
22
|
-
"expo-asset": "~56.0.
|
|
23
|
-
"expo-audio": "~56.0.
|
|
24
|
-
"expo-auth-session": "~56.0.
|
|
25
|
-
"expo-background-fetch": "~56.0.
|
|
26
|
-
"expo-background-task": "~56.0.
|
|
22
|
+
"expo-asset": "~56.0.17",
|
|
23
|
+
"expo-audio": "~56.0.12",
|
|
24
|
+
"expo-auth-session": "~56.0.14",
|
|
25
|
+
"expo-background-fetch": "~56.0.18",
|
|
26
|
+
"expo-background-task": "~56.0.18",
|
|
27
27
|
"expo-battery": "~56.0.4",
|
|
28
28
|
"expo-blur": "~56.0.3",
|
|
29
29
|
"expo-brightness": "~56.0.5",
|
|
30
|
-
"expo-brownfield": "~56.0.
|
|
31
|
-
"expo-build-properties": "~56.0.
|
|
30
|
+
"expo-brownfield": "~56.0.19",
|
|
31
|
+
"expo-build-properties": "~56.0.18",
|
|
32
32
|
"expo-calendar": "~56.0.8",
|
|
33
33
|
"expo-camera": "~56.0.7",
|
|
34
34
|
"expo-cellular": "~56.0.5",
|
|
35
35
|
"expo-checkbox": "~56.0.1",
|
|
36
|
-
"expo-clipboard": "~56.0.
|
|
37
|
-
"expo-constants": "~56.0.
|
|
38
|
-
"expo-contacts": "~56.0.
|
|
36
|
+
"expo-clipboard": "~56.0.4",
|
|
37
|
+
"expo-constants": "~56.0.18",
|
|
38
|
+
"expo-contacts": "~56.0.8",
|
|
39
39
|
"expo-crypto": "~56.0.4",
|
|
40
|
-
"expo-dev-client": "~56.0.
|
|
40
|
+
"expo-dev-client": "~56.0.20",
|
|
41
41
|
"expo-device": "~56.0.4",
|
|
42
42
|
"expo-document-picker": "~56.0.4",
|
|
43
43
|
"expo-file-system": "~56.0.7",
|
|
@@ -46,55 +46,55 @@
|
|
|
46
46
|
"expo-glass-effect": "~56.0.4",
|
|
47
47
|
"expo-google-app-auth": "~8.3.0",
|
|
48
48
|
"expo-haptics": "~56.0.3",
|
|
49
|
-
"expo-image": "~56.0.
|
|
49
|
+
"expo-image": "~56.0.11",
|
|
50
50
|
"expo-image-loader": "~56.0.3",
|
|
51
|
-
"expo-image-manipulator": "~56.0.
|
|
52
|
-
"expo-image-picker": "~56.0.
|
|
51
|
+
"expo-image-manipulator": "~56.0.18",
|
|
52
|
+
"expo-image-picker": "~56.0.17",
|
|
53
53
|
"expo-intent-launcher": "~56.0.4",
|
|
54
|
-
"expo-insights": "~56.0.
|
|
54
|
+
"expo-insights": "~56.0.17",
|
|
55
55
|
"expo-keep-awake": "~56.0.3",
|
|
56
56
|
"expo-linear-gradient": "~56.0.4",
|
|
57
|
-
"expo-linking": "~56.0.
|
|
57
|
+
"expo-linking": "~56.0.14",
|
|
58
58
|
"expo-local-authentication": "~56.0.4",
|
|
59
59
|
"expo-localization": "~56.0.6",
|
|
60
|
-
"expo-location": "~56.0.
|
|
60
|
+
"expo-location": "~56.0.17",
|
|
61
61
|
"expo-mail-composer": "~56.0.4",
|
|
62
62
|
"expo-manifests": "~56.0.4",
|
|
63
63
|
"expo-maps": "~56.0.6",
|
|
64
64
|
"expo-mcp": "~0.2.1",
|
|
65
65
|
"expo-media-library": "~56.0.6",
|
|
66
66
|
"expo-mesh-gradient": "~56.0.3",
|
|
67
|
-
"expo-module-template": "~56.0.
|
|
68
|
-
"expo-modules-core": "~56.0.
|
|
67
|
+
"expo-module-template": "~56.0.13",
|
|
68
|
+
"expo-modules-core": "~56.0.16",
|
|
69
69
|
"expo-navigation-bar": "~56.0.3",
|
|
70
|
-
"expo-network": "~56.0.
|
|
71
|
-
"expo-notifications": "~56.0.
|
|
72
|
-
"expo-observe": "~56.0.
|
|
70
|
+
"expo-network": "~56.0.5",
|
|
71
|
+
"expo-notifications": "~56.0.17",
|
|
72
|
+
"expo-observe": "~56.0.20",
|
|
73
73
|
"expo-print": "~56.0.3",
|
|
74
74
|
"expo-live-photo": "~56.0.3",
|
|
75
|
-
"expo-router": "~56.2.
|
|
75
|
+
"expo-router": "~56.2.10",
|
|
76
76
|
"expo-screen-capture": "~56.0.4",
|
|
77
77
|
"expo-screen-orientation": "~56.0.5",
|
|
78
78
|
"expo-secure-store": "~56.0.4",
|
|
79
|
-
"expo-sensors": "~56.0.
|
|
80
|
-
"expo-server": "~56.0.
|
|
81
|
-
"expo-sharing": "~56.0.
|
|
79
|
+
"expo-sensors": "~56.0.6",
|
|
80
|
+
"expo-server": "~56.0.5",
|
|
81
|
+
"expo-sharing": "~56.0.17",
|
|
82
82
|
"expo-sms": "~56.0.3",
|
|
83
83
|
"expo-speech": "~56.0.3",
|
|
84
84
|
"expo-splash-screen": "~56.0.10",
|
|
85
|
-
"expo-sqlite": "~56.0.
|
|
85
|
+
"expo-sqlite": "~56.0.5",
|
|
86
86
|
"expo-status-bar": "~56.0.4",
|
|
87
87
|
"expo-store-review": "~56.0.3",
|
|
88
|
-
"expo-symbols": "~56.0.
|
|
88
|
+
"expo-symbols": "~56.0.6",
|
|
89
89
|
"expo-system-ui": "~56.0.5",
|
|
90
|
-
"expo-task-manager": "~56.0.
|
|
90
|
+
"expo-task-manager": "~56.0.18",
|
|
91
91
|
"expo-tracking-transparency": "~56.0.5",
|
|
92
|
-
"expo-updates": "~56.0.
|
|
92
|
+
"expo-updates": "~56.0.19",
|
|
93
93
|
"expo-video-thumbnails": "~56.0.3",
|
|
94
|
-
"expo-video": "~56.1.
|
|
94
|
+
"expo-video": "~56.1.3",
|
|
95
95
|
"expo-web-browser": "~56.0.5",
|
|
96
|
-
"expo-widgets": "~56.0.
|
|
97
|
-
"jest-expo": "~56.0.
|
|
96
|
+
"expo-widgets": "~56.0.18",
|
|
97
|
+
"jest-expo": "~56.0.5",
|
|
98
98
|
"lottie-react-native": "~7.3.4",
|
|
99
99
|
"react": "19.2.3",
|
|
100
100
|
"react-dom": "19.2.3",
|
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
@protocol RCTHostRuntimeDelegate;
|
|
8
8
|
|
|
9
9
|
NS_SWIFT_NAME(ExpoReactNativeFactoryObjC)
|
|
10
|
+
#if !TARGET_OS_OSX
|
|
10
11
|
@interface EXReactNativeFactory : RCTReactNativeFactory <RCTHostDelegate>
|
|
12
|
+
#else
|
|
13
|
+
// TODO: remove when bumping to react-native-macos 0.85
|
|
14
|
+
@interface EXReactNativeFactory : RCTReactNativeFactory <RCTHostDelegate, RCTHostRuntimeDelegate>
|
|
15
|
+
#endif
|
|
11
16
|
|
|
12
17
|
@end
|
|
@@ -28,6 +28,14 @@
|
|
|
28
28
|
|
|
29
29
|
#pragma mark - RCTHostDelegate
|
|
30
30
|
|
|
31
|
+
#if TARGET_OS_OSX
|
|
32
|
+
// TODO: remove when bumping react-native-macos to 0.85
|
|
33
|
+
- (void)hostDidStart:(nonnull RCTHost *)host
|
|
34
|
+
{
|
|
35
|
+
host.runtimeDelegate = self;
|
|
36
|
+
}
|
|
37
|
+
#endif
|
|
38
|
+
|
|
31
39
|
// [JS thread]
|
|
32
40
|
- (void)host:(nonnull RCTHost *)host didInitializeRuntime:(facebook::jsi::Runtime &)runtime
|
|
33
41
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo",
|
|
3
|
-
"version": "56.0.
|
|
3
|
+
"version": "56.0.10",
|
|
4
4
|
"description": "The Expo SDK",
|
|
5
5
|
"main": "src/Expo.ts",
|
|
6
6
|
"module": "src/Expo.ts",
|
|
@@ -68,25 +68,25 @@
|
|
|
68
68
|
"homepage": "https://github.com/expo/expo/tree/main/packages/expo",
|
|
69
69
|
"dependencies": {
|
|
70
70
|
"@babel/runtime": "^7.20.0",
|
|
71
|
-
"@expo/cli": "^56.1.
|
|
71
|
+
"@expo/cli": "^56.1.15",
|
|
72
72
|
"@expo/config": "~56.0.9",
|
|
73
73
|
"@expo/config-plugins": "~56.0.8",
|
|
74
74
|
"@expo/devtools": "~56.0.2",
|
|
75
75
|
"@expo/dom-webview": "~56.0.5",
|
|
76
|
-
"@expo/fingerprint": "^0.19.
|
|
76
|
+
"@expo/fingerprint": "^0.19.4",
|
|
77
77
|
"@expo/local-build-cache-provider": "^56.0.8",
|
|
78
|
-
"@expo/log-box": "^56.0.
|
|
78
|
+
"@expo/log-box": "^56.0.13",
|
|
79
79
|
"@expo/metro": "~56.0.0",
|
|
80
|
-
"@expo/metro-config": "~56.0.
|
|
80
|
+
"@expo/metro-config": "~56.0.14",
|
|
81
81
|
"@ungap/structured-clone": "^1.3.0",
|
|
82
|
-
"babel-preset-expo": "~56.0.
|
|
83
|
-
"expo-asset": "~56.0.
|
|
84
|
-
"expo-constants": "~56.0.
|
|
82
|
+
"babel-preset-expo": "~56.0.15",
|
|
83
|
+
"expo-asset": "~56.0.17",
|
|
84
|
+
"expo-constants": "~56.0.18",
|
|
85
85
|
"expo-file-system": "~56.0.7",
|
|
86
86
|
"expo-font": "~56.0.5",
|
|
87
87
|
"expo-keep-awake": "~56.0.3",
|
|
88
|
-
"expo-modules-autolinking": "~56.0.
|
|
89
|
-
"expo-modules-core": "~56.0.
|
|
88
|
+
"expo-modules-autolinking": "~56.0.15",
|
|
89
|
+
"expo-modules-core": "~56.0.16",
|
|
90
90
|
"pretty-format": "^29.7.0",
|
|
91
91
|
"react-refresh": "^0.14.2",
|
|
92
92
|
"whatwg-url-minimum": "^0.1.2"
|
|
@@ -101,8 +101,8 @@
|
|
|
101
101
|
"rimraf": "^6.1.2",
|
|
102
102
|
"web-streams-polyfill": "^3.3.2",
|
|
103
103
|
"@expo/dom-webview": "56.0.5",
|
|
104
|
-
"expo-
|
|
105
|
-
"
|
|
104
|
+
"@expo/metro-runtime": "56.0.15",
|
|
105
|
+
"expo-updates": "56.0.19"
|
|
106
106
|
},
|
|
107
107
|
"peerDependencies": {
|
|
108
108
|
"@expo/dom-webview": "*",
|
|
@@ -139,7 +139,7 @@
|
|
|
139
139
|
"scripts/resolveAppEntry.js"
|
|
140
140
|
]
|
|
141
141
|
},
|
|
142
|
-
"gitHead": "
|
|
142
|
+
"gitHead": "b1e94a5c1c5b19472a42ca25752a3533699bc46a",
|
|
143
143
|
"scripts": {
|
|
144
144
|
"build": "tsc",
|
|
145
145
|
"clean": "rimraf build",
|
|
@@ -63,18 +63,35 @@ describe('asyncRequireModule', () => {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
function asyncRequireImpl(moduleID, paths, moduleName) {
|
|
66
|
-
var maybeLoadBundlePromise = maybeLoadBundle(moduleID, paths);
|
|
67
66
|
var importAll = function() { return require.importAll(moduleID, moduleName); };
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
return
|
|
67
|
+
try {
|
|
68
|
+
// Try importing first to skip bundle loading when the bundle is already preloaded.
|
|
69
|
+
return importAll();
|
|
70
|
+
} catch (error) {
|
|
71
|
+
var maybeLoadBundlePromise = maybeLoadBundle(moduleID, paths);
|
|
72
|
+
if (maybeLoadBundlePromise != null) {
|
|
73
|
+
return maybeLoadBundlePromise.then(importAll);
|
|
74
|
+
}
|
|
75
|
+
throw error;
|
|
71
76
|
}
|
|
72
|
-
|
|
73
|
-
return importAll();
|
|
74
77
|
}
|
|
75
78
|
|
|
76
|
-
|
|
77
|
-
|
|
79
|
+
function asyncRequire(moduleID, paths, moduleName) {
|
|
80
|
+
var ret = asyncRequireImpl(moduleID, paths, moduleName);
|
|
81
|
+
if (!(ret instanceof Promise)) {
|
|
82
|
+
return {
|
|
83
|
+
_result: ret,
|
|
84
|
+
then: function(resolve, reject) {
|
|
85
|
+
return Promise.resolve(ret).then(resolve, reject);
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
_result: ret,
|
|
91
|
+
then: function(resolve, reject) {
|
|
92
|
+
return ret.then(resolve, reject);
|
|
93
|
+
},
|
|
94
|
+
};
|
|
78
95
|
}
|
|
79
96
|
|
|
80
97
|
asyncRequire.unstable_importMaybeSync = function(moduleID, paths) {
|
|
@@ -112,7 +129,13 @@ describe('asyncRequireModule', () => {
|
|
|
112
129
|
expect(result).toEqual({ default: 'module-42' });
|
|
113
130
|
});
|
|
114
131
|
|
|
115
|
-
it('
|
|
132
|
+
it('falls back to bundle load when importAll throws, then retries with moduleName', async () => {
|
|
133
|
+
mockImportAll
|
|
134
|
+
.mockImplementationOnce(() => {
|
|
135
|
+
throw new Error('Module not loaded');
|
|
136
|
+
})
|
|
137
|
+
.mockImplementationOnce(() => ({ default: 'module-42' }));
|
|
138
|
+
|
|
116
139
|
let resolveBundle!: () => void;
|
|
117
140
|
const bundlePromise = new Promise<void>((resolve) => {
|
|
118
141
|
resolveBundle = resolve;
|
|
@@ -123,17 +146,62 @@ describe('asyncRequireModule', () => {
|
|
|
123
146
|
const paths = { '42': '/bundles/my-module.bundle' };
|
|
124
147
|
const resultPromise = asyncRequire(42, paths, 'my-module');
|
|
125
148
|
|
|
126
|
-
// importAll
|
|
127
|
-
expect(mockImportAll).
|
|
149
|
+
// Initial importAll attempt happened; second is gated on the bundle promise.
|
|
150
|
+
expect(mockImportAll).toHaveBeenCalledTimes(1);
|
|
151
|
+
expect((globalThis as any).__loadBundleAsync).toHaveBeenCalledWith('/bundles/my-module.bundle');
|
|
128
152
|
|
|
129
|
-
// Resolve the bundle loading
|
|
130
153
|
resolveBundle();
|
|
131
154
|
const result = await resultPromise;
|
|
132
155
|
|
|
156
|
+
expect(mockImportAll).toHaveBeenCalledTimes(2);
|
|
157
|
+
expect(mockImportAll).toHaveBeenLastCalledWith(42, 'my-module');
|
|
158
|
+
expect(result).toEqual({ default: 'module-42' });
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('does not load the bundle when importAll succeeds (preloaded bundle case)', async () => {
|
|
162
|
+
(globalThis as any).__loadBundleAsync = jest.fn(() => Promise.resolve());
|
|
163
|
+
|
|
164
|
+
const paths = { '42': '/bundles/my-module.bundle' };
|
|
165
|
+
const result = await asyncRequire(42, paths, 'my-module');
|
|
166
|
+
|
|
133
167
|
expect(mockImportAll).toHaveBeenCalledWith(42, 'my-module');
|
|
168
|
+
expect((globalThis as any).__loadBundleAsync).not.toHaveBeenCalled();
|
|
134
169
|
expect(result).toEqual({ default: 'module-42' });
|
|
135
170
|
});
|
|
136
171
|
|
|
172
|
+
it('re-throws the import error when no bundle path is configured', () => {
|
|
173
|
+
mockImportAll.mockImplementationOnce(() => {
|
|
174
|
+
throw new Error('Module not loaded');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
expect(() => asyncRequire(42, null, 'my-module')).toThrow('Module not loaded');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe('thenable return value', () => {
|
|
181
|
+
it('exposes a synchronous _result when no bundle load was needed', () => {
|
|
182
|
+
const ret = asyncRequire(42, null, 'my-module');
|
|
183
|
+
|
|
184
|
+
expect(typeof ret.then).toBe('function');
|
|
185
|
+
expect(ret._result).toEqual({ default: 'module-42' });
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('exposes a Promise _result when a bundle load was needed', async () => {
|
|
189
|
+
mockImportAll
|
|
190
|
+
.mockImplementationOnce(() => {
|
|
191
|
+
throw new Error('Module not loaded');
|
|
192
|
+
})
|
|
193
|
+
.mockImplementationOnce(() => ({ default: 'module-42' }));
|
|
194
|
+
|
|
195
|
+
(globalThis as any).__loadBundleAsync = jest.fn(() => Promise.resolve());
|
|
196
|
+
|
|
197
|
+
const ret = asyncRequire(42, { '42': '/bundles/my-module.bundle' }, 'my-module');
|
|
198
|
+
|
|
199
|
+
expect(typeof ret.then).toBe('function');
|
|
200
|
+
expect(ret._result).toBeInstanceOf(Promise);
|
|
201
|
+
await expect(ret._result).resolves.toEqual({ default: 'module-42' });
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
137
205
|
describe('unstable_importMaybeSync', () => {
|
|
138
206
|
it('returns synchronously when no bundle loading needed', () => {
|
|
139
207
|
const result = asyncRequire.unstable_importMaybeSync(42, null);
|
|
@@ -67,22 +67,43 @@ function asyncRequireImpl<T>(
|
|
|
67
67
|
paths: DependencyMapPaths,
|
|
68
68
|
moduleName?: string
|
|
69
69
|
): Promise<T> | T {
|
|
70
|
-
const maybeLoadBundlePromise = maybeLoadBundle(moduleID, paths);
|
|
71
70
|
const importAll = () => (require as unknown as MetroRequire).importAll<T>(moduleID, moduleName);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return
|
|
71
|
+
try {
|
|
72
|
+
// Try importing first to prevent double-loading script when the page already preloaded it
|
|
73
|
+
return importAll();
|
|
74
|
+
} catch (error) {
|
|
75
|
+
const maybeLoadBundlePromise = maybeLoadBundle(moduleID, paths);
|
|
76
|
+
if (maybeLoadBundlePromise != null) {
|
|
77
|
+
return maybeLoadBundlePromise.then(importAll);
|
|
78
|
+
}
|
|
79
|
+
throw error;
|
|
75
80
|
}
|
|
76
|
-
|
|
77
|
-
return importAll();
|
|
78
81
|
}
|
|
79
82
|
|
|
80
|
-
|
|
83
|
+
function asyncRequire<T>(
|
|
81
84
|
moduleID: number,
|
|
82
85
|
paths: DependencyMapPaths,
|
|
83
86
|
moduleName?: string
|
|
84
|
-
): Promise<T> {
|
|
85
|
-
|
|
87
|
+
): PromiseLike<T> & { _result?: T | Promise<T> } {
|
|
88
|
+
const ret = asyncRequireImpl<T>(moduleID, paths, moduleName);
|
|
89
|
+
if (!(ret instanceof Promise)) {
|
|
90
|
+
// We return a thenable with an added `unstable_importMaybeSync`-like
|
|
91
|
+
// `_result` property to bypass this being force-converted to a promise
|
|
92
|
+
// for rehydration
|
|
93
|
+
return {
|
|
94
|
+
_result: ret,
|
|
95
|
+
then(resolve, reject) {
|
|
96
|
+
return Promise.resolve(ret).then(resolve, reject);
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
} else {
|
|
100
|
+
return {
|
|
101
|
+
_result: ret,
|
|
102
|
+
then(resolve, reject) {
|
|
103
|
+
return ret.then(resolve, reject);
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
86
107
|
}
|
|
87
108
|
|
|
88
109
|
// Synchronous version of asyncRequire, which can still return a promise
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type * as React from 'react';
|
|
2
|
+
|
|
1
3
|
import HMRClient from './hmr';
|
|
2
4
|
|
|
3
5
|
if (typeof window !== 'undefined') {
|
|
@@ -17,11 +19,64 @@ if (typeof window !== 'undefined') {
|
|
|
17
19
|
LEVELS.forEach((level) => {
|
|
18
20
|
const originalFunction = console[level];
|
|
19
21
|
console[level] = function (...args: any[]) {
|
|
20
|
-
HMRClient.log(level, args);
|
|
22
|
+
HMRClient.log(level, level === 'error' ? addErrorStacks(args, true) : args);
|
|
21
23
|
originalFunction.apply(console, args);
|
|
22
24
|
};
|
|
23
25
|
});
|
|
26
|
+
|
|
27
|
+
window.addEventListener('error', (event) => {
|
|
28
|
+
// Not capturing current stack as it would only point to this function,
|
|
29
|
+
// the stack chain is preserved by the browser.
|
|
30
|
+
HMRClient.log('error', addErrorStacks([event.error]));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
window.addEventListener('unhandledrejection', (event) => {
|
|
34
|
+
// Not capturing current stack as it would only point to this function,
|
|
35
|
+
// the stack chain is preserved by the browser.
|
|
36
|
+
HMRClient.log('error', addErrorStacks([event.reason]));
|
|
37
|
+
});
|
|
24
38
|
}
|
|
25
39
|
|
|
26
40
|
// This is called native on native platforms
|
|
27
41
|
HMRClient.setup({ isEnabled: true });
|
|
42
|
+
|
|
43
|
+
function addErrorStacks(data: unknown[], shouldCaptureCurrentStack = false) {
|
|
44
|
+
const dataWithStacks = [...data];
|
|
45
|
+
let hasStack = false;
|
|
46
|
+
data.forEach((item) => {
|
|
47
|
+
// on native handled in packages/@expo/metro-runtime/src/metroServerLogs.native.ts
|
|
48
|
+
// https://github.com/expo/expo/blob/118528654c982b6df2f4b3e73bbf2ae0b78d84a2/packages/%40expo/metro-runtime/src/metroServerLogs.native.ts#L30
|
|
49
|
+
// this differs from native implementation where error from native modules
|
|
50
|
+
// would not pass instanceof Error check
|
|
51
|
+
if (item instanceof Error && item.stack) {
|
|
52
|
+
hasStack = true;
|
|
53
|
+
dataWithStacks.push(item.stack);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (!hasStack && shouldCaptureCurrentStack) {
|
|
58
|
+
// for console.* to point to the call site
|
|
59
|
+
const stack = captureCurrentStack();
|
|
60
|
+
if (typeof stack === 'string') {
|
|
61
|
+
hasStack = true;
|
|
62
|
+
dataWithStacks.push(stack);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const react = require('react') as typeof React;
|
|
67
|
+
const componentStack = react.captureOwnerStack?.();
|
|
68
|
+
if (componentStack) {
|
|
69
|
+
dataWithStacks.push(componentStack);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return dataWithStacks;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
class NamelessError extends Error {
|
|
76
|
+
name = '';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function captureCurrentStack() {
|
|
80
|
+
// If you're reading this, look deeper into the call stack to find the actual error source.
|
|
81
|
+
return new NamelessError().stack;
|
|
82
|
+
}
|
package/template.tgz
CHANGED
|
Binary file
|