@expo/metro-runtime 3.0.1 → 3.0.2

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 (112) hide show
  1. package/build/LoadingView.d.ts +6 -0
  2. package/build/LoadingView.d.ts.map +1 -1
  3. package/build/LoadingView.js +9 -3
  4. package/build/LoadingView.js.map +1 -1
  5. package/build/async-require/fetchAsync.d.ts +6 -0
  6. package/build/async-require/fetchAsync.d.ts.map +1 -1
  7. package/build/async-require/fetchAsync.js +1 -2
  8. package/build/async-require/fetchAsync.js.map +1 -1
  9. package/build/async-require/fetchThenEval.d.ts +1 -6
  10. package/build/async-require/fetchThenEval.d.ts.map +1 -1
  11. package/build/async-require/fetchThenEval.js +2 -32
  12. package/build/async-require/fetchThenEval.js.map +1 -1
  13. package/build/async-require/fetchThenEval.web.js +1 -1
  14. package/build/async-require/fetchThenEval.web.js.map +1 -1
  15. package/build/async-require/fetchThenEvalJs.d.ts +7 -0
  16. package/build/async-require/fetchThenEvalJs.d.ts.map +1 -0
  17. package/build/async-require/fetchThenEvalJs.js +36 -0
  18. package/build/async-require/fetchThenEvalJs.js.map +1 -0
  19. package/build/async-require/index.native.d.ts +7 -0
  20. package/build/async-require/index.native.d.ts.map +1 -0
  21. package/build/async-require/index.native.js +14 -0
  22. package/build/async-require/index.native.js.map +1 -0
  23. package/build/effects.d.ts +0 -1
  24. package/build/effects.js +1 -6
  25. package/build/effects.js.map +1 -1
  26. package/build/error-overlay/Data/parseLogBoxLog.d.ts.map +1 -1
  27. package/build/error-overlay/Data/parseLogBoxLog.js +1 -2
  28. package/build/error-overlay/Data/parseLogBoxLog.js.map +1 -1
  29. package/build/error-overlay/LogBox.web.d.ts.map +1 -1
  30. package/build/error-overlay/LogBox.web.js +1 -2
  31. package/build/error-overlay/LogBox.web.js.map +1 -1
  32. package/build/error-overlay/index.d.ts.map +1 -1
  33. package/build/error-overlay/index.js +1 -0
  34. package/build/error-overlay/index.js.map +1 -1
  35. package/build/getDevServer.d.ts.map +1 -1
  36. package/build/getDevServer.js +2 -6
  37. package/build/getDevServer.js.map +1 -1
  38. package/build/index.d.ts +8 -0
  39. package/build/index.d.ts.map +1 -1
  40. package/build/index.js +10 -8
  41. package/build/index.js.map +1 -1
  42. package/build/setupHMR.js +21 -24
  43. package/build/setupHMR.js.map +1 -1
  44. package/package.json +5 -2
  45. package/src/HMRClient.native.ts +3 -0
  46. package/src/HMRClient.ts +316 -0
  47. package/src/LoadingView.native.ts +3 -0
  48. package/src/LoadingView.ts +24 -0
  49. package/src/__mocks__/LoadingView.ts +4 -0
  50. package/src/async-require/buildAsyncRequire.ts +34 -0
  51. package/src/async-require/buildUrlForBundle.native.ts +28 -0
  52. package/src/async-require/buildUrlForBundle.ts +18 -0
  53. package/src/async-require/fetchAsync.native.ts +72 -0
  54. package/src/async-require/fetchAsync.ts +19 -0
  55. package/src/async-require/fetchThenEval.ts +1 -0
  56. package/src/async-require/fetchThenEval.web.ts +70 -0
  57. package/src/async-require/fetchThenEvalJs.ts +39 -0
  58. package/src/async-require/index.native.ts +15 -0
  59. package/src/async-require/index.ts +10 -0
  60. package/src/async-require/loadBundle.ts +46 -0
  61. package/src/effects.native.ts +0 -0
  62. package/src/effects.ts +11 -0
  63. package/src/error-overlay/Data/LogBoxData.tsx +438 -0
  64. package/src/error-overlay/Data/LogBoxLog.ts +221 -0
  65. package/src/error-overlay/Data/LogBoxSymbolication.tsx +64 -0
  66. package/src/error-overlay/Data/LogContext.tsx +41 -0
  67. package/src/error-overlay/Data/parseLogBoxLog.tsx +342 -0
  68. package/src/error-overlay/ErrorOverlay.tsx +191 -0
  69. package/src/error-overlay/LogBox.ts +51 -0
  70. package/src/error-overlay/LogBox.web.ts +174 -0
  71. package/src/error-overlay/UI/AnsiHighlight.tsx +96 -0
  72. package/src/error-overlay/UI/LogBoxButton.tsx +63 -0
  73. package/src/error-overlay/UI/LogBoxMessage.tsx +73 -0
  74. package/src/error-overlay/UI/LogBoxStyle.ts +64 -0
  75. package/src/error-overlay/UI/constants.ts +7 -0
  76. package/src/error-overlay/formatProjectFilePath.ts +38 -0
  77. package/src/error-overlay/index.tsx +34 -0
  78. package/src/error-overlay/modules/ExceptionsManager/index.native.ts +4 -0
  79. package/src/error-overlay/modules/ExceptionsManager/index.ts +82 -0
  80. package/src/error-overlay/modules/NativeLogBox/index.native.ts +3 -0
  81. package/src/error-overlay/modules/NativeLogBox/index.tsx +27 -0
  82. package/src/error-overlay/modules/openFileInEditor/index.native.ts +3 -0
  83. package/src/error-overlay/modules/openFileInEditor/index.ts +16 -0
  84. package/src/error-overlay/modules/parseErrorStack/index.ts +26 -0
  85. package/src/error-overlay/modules/parseErrorStack/parseHermesStack.ts +3 -0
  86. package/src/error-overlay/modules/stringifySafe/index.ts +115 -0
  87. package/src/error-overlay/modules/symbolicateStackTrace/index.native.ts +3 -0
  88. package/src/error-overlay/modules/symbolicateStackTrace/index.ts +39 -0
  89. package/src/error-overlay/overlay/LogBoxInspectorCodeFrame.tsx +102 -0
  90. package/src/error-overlay/overlay/LogBoxInspectorFooter.tsx +111 -0
  91. package/src/error-overlay/overlay/LogBoxInspectorHeader.tsx +167 -0
  92. package/src/error-overlay/overlay/LogBoxInspectorMessageHeader.tsx +116 -0
  93. package/src/error-overlay/overlay/LogBoxInspectorSection.tsx +52 -0
  94. package/src/error-overlay/overlay/LogBoxInspectorSourceMapStatus.tsx +125 -0
  95. package/src/error-overlay/overlay/LogBoxInspectorStackFrame.tsx +89 -0
  96. package/src/error-overlay/overlay/LogBoxInspectorStackFrames.tsx +201 -0
  97. package/src/error-overlay/toast/ErrorToast.tsx +167 -0
  98. package/src/error-overlay/toast/ErrorToastContainer.tsx +9 -0
  99. package/src/error-overlay/toast/ErrorToastContainer.web.tsx +92 -0
  100. package/src/error-overlay/toast/ErrorToastMessage.tsx +28 -0
  101. package/src/error-overlay/useRejectionHandler.ts +61 -0
  102. package/src/getDevServer.native.ts +3 -0
  103. package/src/getDevServer.ts +34 -0
  104. package/src/index.ts +12 -0
  105. package/src/location/Location.native.ts +201 -0
  106. package/src/location/Location.ts +3 -0
  107. package/src/location/install.native.ts +90 -0
  108. package/src/location/install.ts +0 -0
  109. package/src/messageSocket.ts +25 -0
  110. package/src/setupFastRefresh.ts +30 -0
  111. package/src/setupHMR.ts +28 -0
  112. package/src/symbolicate.ts +6 -0
package/build/index.js CHANGED
@@ -1,12 +1,14 @@
1
1
  "use strict";
2
+ /**
3
+ * Copyright © 2023 650 Industries.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
2
8
  Object.defineProperty(exports, "__esModule", { value: true });
3
9
  require("./location/install");
4
- // Ensure this is removed in production.
5
- // TODO: Enable in production.
6
- if (process.env.NODE_ENV !== 'production') {
7
- // IMPORT POSITION MATTERS FOR FAST REFRESH ON WEB
8
- require('./effects');
9
- // vvv EVERYTHING ELSE vvv
10
- require('./async-require');
11
- }
10
+ // IMPORT POSITION MATTERS FOR FAST REFRESH ON WEB
11
+ require("./effects");
12
+ // vvv EVERYTHING ELSE vvv
13
+ require("./async-require");
12
14
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAAA,8BAA4B;AAE5B,wCAAwC;AACxC,8BAA8B;AAC9B,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE;IACzC,kDAAkD;IAClD,OAAO,CAAC,WAAW,CAAC,CAAC;IACrB,0BAA0B;IAC1B,OAAO,CAAC,iBAAiB,CAAC,CAAC;CAC5B","sourcesContent":["import './location/install';\n\n// Ensure this is removed in production.\n// TODO: Enable in production.\nif (process.env.NODE_ENV !== 'production') {\n // IMPORT POSITION MATTERS FOR FAST REFRESH ON WEB\n require('./effects');\n // vvv EVERYTHING ELSE vvv\n require('./async-require');\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;AAEH,8BAA4B;AAC5B,kDAAkD;AAClD,qBAAmB;AACnB,0BAA0B;AAC1B,2BAAyB","sourcesContent":["/**\n * Copyright © 2023 650 Industries.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nimport './location/install';\n// IMPORT POSITION MATTERS FOR FAST REFRESH ON WEB\nimport './effects';\n// vvv EVERYTHING ELSE vvv\nimport './async-require';\n"]}
package/build/setupHMR.js CHANGED
@@ -3,32 +3,29 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const react_native_1 = require("react-native");
7
6
  const HMRClient_1 = __importDefault(require("./HMRClient"));
8
7
  // Sets up developer tools for React Native web.
9
- if (!react_native_1.Platform.isTesting) {
10
- // We assume full control over the console and send JavaScript logs to Metro.
11
- // [
12
- // 'trace',
13
- // 'info',
14
- // 'warn',
15
- // 'error',
16
- // 'log',
17
- // 'group',
18
- // 'groupCollapsed',
19
- // 'groupEnd',
20
- // 'debug',
21
- // ].forEach(level => {
22
- // const originalFunction = console[level];
23
- // console[level] = function (...args: readonly any[]) {
24
- // HMRClient.log(
25
- // // @ts-expect-error
26
- // level, args);
27
- // originalFunction.apply(console, args);
28
- // };
29
- // });
30
- HMRClient_1.default.log('log', [`[${react_native_1.Platform.OS}] Logs will appear in the browser console`]);
31
- }
8
+ // We assume full control over the console and send JavaScript logs to Metro.
9
+ // [
10
+ // 'trace',
11
+ // 'info',
12
+ // 'warn',
13
+ // 'error',
14
+ // 'log',
15
+ // 'group',
16
+ // 'groupCollapsed',
17
+ // 'groupEnd',
18
+ // 'debug',
19
+ // ].forEach(level => {
20
+ // const originalFunction = console[level];
21
+ // console[level] = function (...args: readonly any[]) {
22
+ // HMRClient.log(
23
+ // // @ts-expect-error
24
+ // level, args);
25
+ // originalFunction.apply(console, args);
26
+ // };
27
+ // });
28
+ HMRClient_1.default.log('log', [`[web] Logs will appear in the browser console`]);
32
29
  // This is called native on native platforms
33
30
  HMRClient_1.default.setup({ isEnabled: true });
34
31
  //# sourceMappingURL=setupHMR.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"setupHMR.js","sourceRoot":"","sources":["../src/setupHMR.ts"],"names":[],"mappings":";;;;;AAAA,+CAAwC;AAExC,4DAAoC;AAEpC,gDAAgD;AAChD,IAAI,CAAC,uBAAQ,CAAC,SAAS,EAAE;IACvB,6EAA6E;IAC7E,IAAI;IACJ,aAAa;IACb,YAAY;IACZ,YAAY;IACZ,aAAa;IACb,WAAW;IACX,aAAa;IACb,sBAAsB;IACtB,gBAAgB;IAChB,aAAa;IACb,uBAAuB;IACvB,6CAA6C;IAC7C,0DAA0D;IAC1D,qBAAqB;IACrB,4BAA4B;IAC5B,sBAAsB;IACtB,6CAA6C;IAC7C,OAAO;IACP,MAAM;IAEN,mBAAS,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,uBAAQ,CAAC,EAAE,2CAA2C,CAAC,CAAC,CAAC;CACpF;AAED,4CAA4C;AAC5C,mBAAS,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC","sourcesContent":["import { Platform } from 'react-native';\n\nimport HMRClient from './HMRClient';\n\n// Sets up developer tools for React Native web.\nif (!Platform.isTesting) {\n // We assume full control over the console and send JavaScript logs to Metro.\n // [\n // 'trace',\n // 'info',\n // 'warn',\n // 'error',\n // 'log',\n // 'group',\n // 'groupCollapsed',\n // 'groupEnd',\n // 'debug',\n // ].forEach(level => {\n // const originalFunction = console[level];\n // console[level] = function (...args: readonly any[]) {\n // HMRClient.log(\n // // @ts-expect-error\n // level, args);\n // originalFunction.apply(console, args);\n // };\n // });\n\n HMRClient.log('log', [`[${Platform.OS}] Logs will appear in the browser console`]);\n}\n\n// This is called native on native platforms\nHMRClient.setup({ isEnabled: true });\n"]}
1
+ {"version":3,"file":"setupHMR.js","sourceRoot":"","sources":["../src/setupHMR.ts"],"names":[],"mappings":";;;;;AAAA,4DAAoC;AAEpC,gDAAgD;AAChD,6EAA6E;AAC7E,IAAI;AACJ,aAAa;AACb,YAAY;AACZ,YAAY;AACZ,aAAa;AACb,WAAW;AACX,aAAa;AACb,sBAAsB;AACtB,gBAAgB;AAChB,aAAa;AACb,uBAAuB;AACvB,6CAA6C;AAC7C,0DAA0D;AAC1D,qBAAqB;AACrB,4BAA4B;AAC5B,sBAAsB;AACtB,6CAA6C;AAC7C,OAAO;AACP,MAAM;AAEN,mBAAS,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,+CAA+C,CAAC,CAAC,CAAC;AAExE,4CAA4C;AAC5C,mBAAS,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC","sourcesContent":["import HMRClient from './HMRClient';\n\n// Sets up developer tools for React Native web.\n// We assume full control over the console and send JavaScript logs to Metro.\n// [\n// 'trace',\n// 'info',\n// 'warn',\n// 'error',\n// 'log',\n// 'group',\n// 'groupCollapsed',\n// 'groupEnd',\n// 'debug',\n// ].forEach(level => {\n// const originalFunction = console[level];\n// console[level] = function (...args: readonly any[]) {\n// HMRClient.log(\n// // @ts-expect-error\n// level, args);\n// originalFunction.apply(console, args);\n// };\n// });\n\nHMRClient.log('log', [`[web] Logs will appear in the browser console`]);\n\n// This is called native on native platforms\nHMRClient.setup({ isEnabled: true });\n"]}
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@expo/metro-runtime",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "description": "Tools for making advanced Metro bundler features work",
5
+ "types": "build",
5
6
  "main": "build",
7
+ "browser": "src",
8
+ "react-native": "src",
6
9
  "homepage": "https://github.com/expo/expo/tree/main/packages/@expo/metro-runtime",
7
10
  "keywords": [],
8
11
  "author": "650 Industries, Inc.",
@@ -52,5 +55,5 @@
52
55
  }
53
56
  ]
54
57
  },
55
- "gitHead": "79607a7325f47aa17c36d266100d09a4ff2cc544"
58
+ "gitHead": "ee2c866ba3c7fbc35ff2a3e896041cf15d3bd7c5"
56
59
  }
@@ -0,0 +1,3 @@
1
+ const HMRClient = require('react-native/Libraries/Utilities/HMRClient');
2
+
3
+ export default HMRClient;
@@ -0,0 +1,316 @@
1
+ /**
2
+ * Copyright (c) 650 Industries.
3
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * Based on this but with web support:
9
+ * https://github.com/facebook/react-native/blob/086714b02b0fb838dee5a66c5bcefe73b53cf3df/Libraries/Utilities/HMRClient.js
10
+ */
11
+ import prettyFormat, { plugins } from 'pretty-format';
12
+
13
+ import LoadingView from './LoadingView';
14
+ import LogBox from './error-overlay/LogBox';
15
+ import getDevServer from './getDevServer';
16
+
17
+ const MetroHMRClient = require('metro-runtime/src/modules/HMRClient');
18
+ const pendingEntryPoints: string[] = [];
19
+
20
+ type HMRClientType = {
21
+ send: (msg: string) => void;
22
+ isEnabled: () => boolean;
23
+ disable: () => void;
24
+ enable: () => void;
25
+ hasPendingUpdates: () => boolean;
26
+ };
27
+
28
+ let hmrClient: HMRClientType | null = null;
29
+ let hmrUnavailableReason: string | null = null;
30
+ let currentCompileErrorMessage: string | null = null;
31
+ let didConnect: boolean = false;
32
+ const pendingLogs: [LogLevel, any[]][] = [];
33
+
34
+ type LogLevel =
35
+ | 'trace'
36
+ | 'info'
37
+ | 'warn'
38
+ | 'error'
39
+ | 'log'
40
+ | 'group'
41
+ | 'groupCollapsed'
42
+ | 'groupEnd'
43
+ | 'debug';
44
+
45
+ export type HMRClientNativeInterface = {
46
+ enable(): void;
47
+ disable(): void;
48
+ registerBundle(requestUrl: string): void;
49
+ log(level: LogLevel, data: any[]): void;
50
+ setup(props: { isEnabled: boolean }): void;
51
+ };
52
+
53
+ function assert(foo: any, msg: string): asserts foo {
54
+ if (!foo) throw new Error(msg);
55
+ }
56
+
57
+ /**
58
+ * HMR Client that receives from the server HMR updates and propagates them
59
+ * runtime to reflects those changes.
60
+ */
61
+ const HMRClient: HMRClientNativeInterface = {
62
+ enable() {
63
+ if (hmrUnavailableReason !== null) {
64
+ // If HMR became unavailable while you weren't using it,
65
+ // explain why when you try to turn it on.
66
+ // This is an error (and not a warning) because it is shown
67
+ // in response to a direct user action.
68
+ throw new Error(hmrUnavailableReason);
69
+ }
70
+
71
+ assert(hmrClient, 'Expected HMRClient.setup() call at startup.');
72
+
73
+ // We use this for internal logging only.
74
+ // It doesn't affect the logic.
75
+ hmrClient.send(JSON.stringify({ type: 'log-opt-in' }));
76
+
77
+ // When toggling Fast Refresh on, we might already have some stashed updates.
78
+ // Since they'll get applied now, we'll show a banner.
79
+ const hasUpdates = hmrClient!.hasPendingUpdates();
80
+
81
+ if (hasUpdates) {
82
+ LoadingView.showMessage('Refreshing...', 'refresh');
83
+ }
84
+ try {
85
+ hmrClient.enable();
86
+ } finally {
87
+ if (hasUpdates) {
88
+ LoadingView.hide();
89
+ }
90
+ }
91
+
92
+ // There could be a compile error while Fast Refresh was off,
93
+ // but we ignored it at the time. Show it now.
94
+ showCompileError();
95
+ },
96
+
97
+ disable() {
98
+ assert(hmrClient, 'Expected HMRClient.setup() call at startup.');
99
+ hmrClient.disable();
100
+ },
101
+
102
+ registerBundle(requestUrl: string) {
103
+ assert(hmrClient, 'Expected HMRClient.setup() call at startup.');
104
+ pendingEntryPoints.push(requestUrl);
105
+ registerBundleEntryPoints(hmrClient);
106
+ },
107
+
108
+ log(level: LogLevel, data: any[]) {
109
+ if (!hmrClient) {
110
+ // Catch a reasonable number of early logs
111
+ // in case hmrClient gets initialized later.
112
+ pendingLogs.push([level, data]);
113
+ if (pendingLogs.length > 100) {
114
+ pendingLogs.shift();
115
+ }
116
+ return;
117
+ }
118
+ try {
119
+ hmrClient.send(
120
+ JSON.stringify({
121
+ type: 'log',
122
+ level,
123
+ mode: 'BRIDGE',
124
+ data: data.map((item) =>
125
+ typeof item === 'string'
126
+ ? item
127
+ : prettyFormat(item, {
128
+ escapeString: true,
129
+ highlight: true,
130
+ maxDepth: 3,
131
+ min: true,
132
+ plugins: [plugins.ReactElement],
133
+ })
134
+ ),
135
+ })
136
+ );
137
+ } catch {
138
+ // If sending logs causes any failures we want to silently ignore them
139
+ // to ensure we do not cause infinite-logging loops.
140
+ }
141
+ },
142
+
143
+ // Called once by the bridge on startup, even if Fast Refresh is off.
144
+ // It creates the HMR client but doesn't actually set up the socket yet.
145
+ setup({ isEnabled }: { isEnabled: boolean }) {
146
+ assert(!hmrClient, 'Cannot initialize hmrClient twice');
147
+
148
+ const serverScheme = window.location.protocol === 'https:' ? 'wss' : 'ws';
149
+ const client = new MetroHMRClient(`${serverScheme}://${window.location.host}/hot`);
150
+ hmrClient = client;
151
+
152
+ const { fullBundleUrl } = getDevServer();
153
+ pendingEntryPoints.push(
154
+ // HMRServer understands regular bundle URLs, so prefer that in case
155
+ // there are any important URL parameters we can't reconstruct from
156
+ // `setup()`'s arguments.
157
+ fullBundleUrl
158
+ );
159
+
160
+ client.on('connection-error', (e: Error) => {
161
+ let error = `Cannot connect to Metro.
162
+
163
+ Try the following to fix the issue:
164
+ - Ensure the Metro dev server is running and available on the same network as this device`;
165
+ error += `
166
+
167
+ URL: ${window.location.host}
168
+
169
+ Error: ${e.message}`;
170
+
171
+ setHMRUnavailableReason(error);
172
+ });
173
+
174
+ client.on('update-start', ({ isInitialUpdate }: { isInitialUpdate?: boolean }) => {
175
+ currentCompileErrorMessage = null;
176
+ didConnect = true;
177
+
178
+ if (client.isEnabled() && !isInitialUpdate) {
179
+ LoadingView.showMessage('Refreshing...', 'refresh');
180
+ }
181
+ });
182
+
183
+ client.on('update', ({ isInitialUpdate }: { isInitialUpdate?: boolean }) => {
184
+ if (client.isEnabled() && !isInitialUpdate) {
185
+ dismissRedbox();
186
+ LogBox.clearAllLogs();
187
+ }
188
+ });
189
+
190
+ client.on('update-done', () => {
191
+ LoadingView.hide();
192
+ });
193
+
194
+ client.on('error', (data: { type: string; message: string }) => {
195
+ LoadingView.hide();
196
+
197
+ if (data.type === 'GraphNotFoundError') {
198
+ client.close();
199
+ setHMRUnavailableReason('Metro has restarted since the last edit. Reload to reconnect.');
200
+ } else if (data.type === 'RevisionNotFoundError') {
201
+ client.close();
202
+ setHMRUnavailableReason('Metro and the client are out of sync. Reload to reconnect.');
203
+ } else {
204
+ currentCompileErrorMessage = `${data.type} ${data.message}`;
205
+ if (client.isEnabled()) {
206
+ showCompileError();
207
+ }
208
+ }
209
+ });
210
+
211
+ client.on('close', (closeEvent: { code: number; reason: string }) => {
212
+ LoadingView.hide();
213
+
214
+ // https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1
215
+ // https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.5
216
+ const isNormalOrUnsetCloseReason =
217
+ closeEvent == null ||
218
+ closeEvent.code === 1000 ||
219
+ closeEvent.code === 1005 ||
220
+ closeEvent.code == null;
221
+
222
+ setHMRUnavailableReason(
223
+ `${
224
+ isNormalOrUnsetCloseReason
225
+ ? 'Disconnected from Metro.'
226
+ : `Disconnected from Metro (${closeEvent.code}: "${closeEvent.reason}").`
227
+ }
228
+
229
+ To reconnect:
230
+ - Ensure that Metro is running and available on the same network
231
+ - Reload this app (will trigger further help if Metro cannot be connected to)
232
+ `
233
+ );
234
+ });
235
+
236
+ if (isEnabled) {
237
+ HMRClient.enable();
238
+ } else {
239
+ HMRClient.disable();
240
+ }
241
+
242
+ registerBundleEntryPoints(hmrClient);
243
+ flushEarlyLogs();
244
+ },
245
+ };
246
+
247
+ function setHMRUnavailableReason(reason: string) {
248
+ assert(hmrClient, 'Expected HMRClient.setup() call at startup.');
249
+ if (hmrUnavailableReason !== null) {
250
+ // Don't show more than one warning.
251
+ return;
252
+ }
253
+ hmrUnavailableReason = reason;
254
+
255
+ // We only want to show a warning if Fast Refresh is on *and* if we ever
256
+ // previously managed to connect successfully. We don't want to show
257
+ // the warning to native engineers who use cached bundles without Metro.
258
+ if (hmrClient.isEnabled() && didConnect) {
259
+ console.warn(reason);
260
+ // (Not using the `warning` module to prevent a Buck cycle.)
261
+ }
262
+ }
263
+
264
+ function registerBundleEntryPoints(client: HMRClientType | null) {
265
+ if (hmrUnavailableReason != null) {
266
+ // "Bundle Splitting – Metro disconnected"
267
+ window.location.reload();
268
+ return;
269
+ }
270
+
271
+ if (pendingEntryPoints.length > 0) {
272
+ client?.send(
273
+ JSON.stringify({
274
+ type: 'register-entrypoints',
275
+ entryPoints: pendingEntryPoints,
276
+ })
277
+ );
278
+ pendingEntryPoints.length = 0;
279
+ }
280
+ }
281
+
282
+ function flushEarlyLogs() {
283
+ try {
284
+ pendingLogs.forEach(([level, data]) => {
285
+ HMRClient.log(level, data);
286
+ });
287
+ } finally {
288
+ pendingLogs.length = 0;
289
+ }
290
+ }
291
+
292
+ function dismissRedbox() {
293
+ // TODO(EvanBacon): Error overlay for web.
294
+ }
295
+
296
+ function showCompileError() {
297
+ if (currentCompileErrorMessage === null) {
298
+ return;
299
+ }
300
+
301
+ // Even if there is already a redbox, syntax errors are more important.
302
+ // Otherwise you risk seeing a stale runtime error while a syntax error is more recent.
303
+ dismissRedbox();
304
+
305
+ const message = currentCompileErrorMessage;
306
+ currentCompileErrorMessage = null;
307
+
308
+ const error = new Error(message);
309
+ // Symbolicating compile errors is wasted effort
310
+ // because the stack trace is meaningless:
311
+ // @ts-expect-error
312
+ error.preventSymbolication = true;
313
+ throw error;
314
+ }
315
+
316
+ export default HMRClient;
@@ -0,0 +1,3 @@
1
+ import LoadingView from 'react-native/Libraries/Utilities/LoadingView';
2
+
3
+ export default LoadingView;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Copyright © 2023 650 Industries.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import { DeviceEventEmitter } from 'react-native-web';
9
+
10
+ // Ensure events are sent so custom Fast Refresh views are shown.
11
+ function showMessage(message: string, type: 'load' | 'refresh') {
12
+ DeviceEventEmitter.emit('devLoadingView:showMessage', {
13
+ message,
14
+ });
15
+ }
16
+
17
+ function hide() {
18
+ DeviceEventEmitter.emit('devLoadingView:hide', {});
19
+ }
20
+
21
+ export default {
22
+ showMessage,
23
+ hide,
24
+ };
@@ -0,0 +1,4 @@
1
+ export default {
2
+ showMessage: jest.fn(),
3
+ hide: jest.fn(),
4
+ };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Copyright © 2022 650 Industries.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import { loadBundleAsync } from './loadBundle';
9
+
10
+ /**
11
+ * Must satisfy the requirements of the Metro bundler.
12
+ * https://github.com/react-native-community/discussions-and-proposals/blob/main/proposals/0605-lazy-bundling.md#__loadbundleasync-in-metro
13
+ */
14
+ type AsyncRequire = (path: string) => Promise<void>;
15
+
16
+ /** Create an `loadBundleAsync` function in the expected shape for Metro bundler. */
17
+ export function buildAsyncRequire(): AsyncRequire {
18
+ const cache = new Map<string, Promise<void>>();
19
+
20
+ return async function universal_loadBundleAsync(path: string): Promise<void> {
21
+ if (cache.has(path)) {
22
+ return cache.get(path)!;
23
+ }
24
+
25
+ const promise = loadBundleAsync(path).catch((error) => {
26
+ cache.delete(path);
27
+ throw error;
28
+ });
29
+
30
+ cache.set(path, promise);
31
+
32
+ return promise;
33
+ };
34
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Copyright © 2022 650 Industries.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ export function buildUrlForBundle(bundlePath: string): string {
9
+ if (process.env.NODE_ENV === 'production') {
10
+ if (typeof location !== 'undefined') {
11
+ return joinComponents(location.origin, bundlePath);
12
+ }
13
+ throw new Error(
14
+ 'Unable to determine the production URL where additional JavaScript chunks are hosted because the global "location" variable is not defined.'
15
+ );
16
+ } else {
17
+ const getDevServer = require('../getDevServer')
18
+ .default as typeof import('../getDevServer').default;
19
+
20
+ const { url: serverUrl } = getDevServer();
21
+
22
+ return joinComponents(serverUrl, bundlePath);
23
+ }
24
+ }
25
+
26
+ function joinComponents(prefix: string, suffix: string): string {
27
+ return prefix.replace(/\/+$/, '') + '/' + suffix.replace(/^\/+/, '');
28
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Copyright © 2022 650 Industries.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ /**
9
+ * Given a path and some optional additional query parameters, create the dev server bundle URL.
10
+ * @param bundlePath like `/foobar`
11
+ * @param params like `{ platform: "web" }`
12
+ * @returns a URL like "/foobar.bundle?platform=android&modulesOnly=true&runModule=false&runtimeBytecodeVersion=null"
13
+ */
14
+ export function buildUrlForBundle(bundlePath: string): string {
15
+ // NOTE(EvanBacon): This must come from the window origin (at least in dev mode).
16
+ // Otherwise Metro will crash from attempting to load a bundle that doesn't exist.
17
+ return '/' + bundlePath.replace(/^\/+/, '');
18
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Copyright (c) 650 Industries.
3
+ * Copyright (c) Facebook, Inc. and its affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
9
+ import { Platform } from 'react-native';
10
+ // @ts-expect-error
11
+ import Networking from 'react-native/Libraries/Network/RCTNetworking';
12
+
13
+ type Subscriber = { remove: () => void };
14
+
15
+ export function fetchAsync(
16
+ url: string
17
+ ): Promise<{ body: string; headers: Record<string, string> }> {
18
+ let id: string | null = null;
19
+ let responseText: string | null = null;
20
+ let headers: Record<string, string> = {};
21
+ let dataListener: Subscriber | null = null;
22
+ let completeListener: Subscriber | null = null;
23
+ let responseListener: Subscriber | null = null;
24
+ return new Promise<{ body: string; headers: Record<string, string> }>((resolve, reject) => {
25
+ const addListener = Networking.addListener as (
26
+ event: string,
27
+ callback: (props: [string, any, any]) => any
28
+ ) => Subscriber;
29
+ dataListener = addListener('didReceiveNetworkData', ([requestId, response]) => {
30
+ if (requestId === id) {
31
+ responseText = response;
32
+ }
33
+ });
34
+ responseListener = addListener(
35
+ 'didReceiveNetworkResponse',
36
+ ([requestId, status, responseHeaders]) => {
37
+ if (requestId === id) {
38
+ headers = responseHeaders;
39
+ }
40
+ }
41
+ );
42
+ completeListener = addListener('didCompleteNetworkResponse', ([requestId, error]) => {
43
+ if (requestId === id) {
44
+ if (error) {
45
+ reject(error);
46
+ } else {
47
+ resolve({ body: responseText!, headers });
48
+ }
49
+ }
50
+ });
51
+ (Networking.sendRequest as any)(
52
+ 'GET',
53
+ 'asyncRequest',
54
+ url,
55
+ {
56
+ 'expo-platform': Platform.OS,
57
+ },
58
+ '',
59
+ 'text',
60
+ false,
61
+ 0,
62
+ (requestId: string) => {
63
+ id = requestId;
64
+ },
65
+ true
66
+ );
67
+ }).finally(() => {
68
+ dataListener?.remove();
69
+ completeListener?.remove();
70
+ responseListener?.remove();
71
+ });
72
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Copyright © 2022 650 Industries.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ export async function fetchAsync(url: string): Promise<{ body: string; headers: Headers }> {
8
+ const response = await fetch(url, {
9
+ method: 'GET',
10
+ headers: {
11
+ // No real reason for this but we try to use this format for everything.
12
+ 'expo-platform': 'web',
13
+ },
14
+ });
15
+ return {
16
+ body: await response.text(),
17
+ headers: response.headers,
18
+ };
19
+ }
@@ -0,0 +1 @@
1
+ export { fetchThenEvalAsync } from './fetchThenEvalJs';