rollipop 0.1.0-alpha.16 → 0.1.0-alpha.17
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/CHANGELOG.md +6 -0
- package/dist/index.js +2 -2
- package/package.json +3 -2
- package/src/runtime/hmr-client.ts +322 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# rollipop
|
|
2
2
|
|
|
3
|
+
## 0.1.0-alpha.17
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 5985887: Include `src/runtime/hmr-client.ts` in the published tarball. The `./hmr-client` export maps to this raw TypeScript source, but the `files` field in `package.json` only listed `bin`, `dist`, and `client.d.ts` — so consumers hitting `import 'rollipop/hmr-client'` against a published version would fail to resolve the file.
|
|
8
|
+
|
|
3
9
|
## 0.1.0-alpha.16
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/dist/index.js
CHANGED
|
@@ -89,7 +89,7 @@ async function stripFlowTypes(id, code) {
|
|
|
89
89
|
}
|
|
90
90
|
//#endregion
|
|
91
91
|
//#region src/constants.ts
|
|
92
|
-
const ROLLIPOP_VERSION = "0.1.0-alpha.
|
|
92
|
+
const ROLLIPOP_VERSION = "0.1.0-alpha.17";
|
|
93
93
|
const GLOBAL_IDENTIFIER = "__ROLLIPOP_GLOBAL__";
|
|
94
94
|
/**
|
|
95
95
|
* @see {@link https://github.com/facebook/metro/blob/0.81.x/docs/Configuration.md#resolvermainfields}
|
|
@@ -3360,7 +3360,7 @@ async function runServer(config, options) {
|
|
|
3360
3360
|
}
|
|
3361
3361
|
//#endregion
|
|
3362
3362
|
//#region package.json
|
|
3363
|
-
var version = "0.1.0-alpha.
|
|
3363
|
+
var version = "0.1.0-alpha.17";
|
|
3364
3364
|
//#endregion
|
|
3365
3365
|
//#region src/node/logger.ts
|
|
3366
3366
|
const logger = new Logger("cli");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rollipop",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.17",
|
|
4
4
|
"homepage": "https://github.com/leegeunhyeok/rollipop#readme",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/leegeunhyeok/rollipop/issues"
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"files": [
|
|
17
17
|
"client.d.ts",
|
|
18
18
|
"bin",
|
|
19
|
-
"dist"
|
|
19
|
+
"dist",
|
|
20
|
+
"src/runtime/hmr-client.ts"
|
|
20
21
|
],
|
|
21
22
|
"type": "module",
|
|
22
23
|
"main": "./dist/index.js",
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import prettyFormat from 'pretty-format';
|
|
2
|
+
|
|
3
|
+
import LogBox from '../LogBox/LogBox';
|
|
4
|
+
import NativeRedBox from '../NativeModules/specs/NativeRedBox';
|
|
5
|
+
import type { HMRClientLogLevel, HMRClientMessage, HMRServerMessage } from '../types/hmr';
|
|
6
|
+
import DevLoadingView from './DevLoadingView';
|
|
7
|
+
import Platform from './Platform';
|
|
8
|
+
|
|
9
|
+
declare var __DEV__: boolean;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @see https://github.com/facebook/react-native/blob/0.83-stable/packages/react-native/Libraries/Utilities/HMRClient.js#L42-L55
|
|
13
|
+
*/
|
|
14
|
+
interface HMRClientNativeInterface {
|
|
15
|
+
enable(): void;
|
|
16
|
+
disable(): void;
|
|
17
|
+
registerBundle(requestUrl: string): void;
|
|
18
|
+
log(level: string, data: any[]): void;
|
|
19
|
+
setup(
|
|
20
|
+
platform: string,
|
|
21
|
+
bundleEntry: string,
|
|
22
|
+
host: string,
|
|
23
|
+
port: number | string,
|
|
24
|
+
isEnabled: boolean,
|
|
25
|
+
scheme?: string,
|
|
26
|
+
): void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface SocketInstance {
|
|
30
|
+
socket: WebSocket;
|
|
31
|
+
origin: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
class HMRClient implements HMRClientNativeInterface {
|
|
35
|
+
static readonly STARTUP_ERROR = 'Expected HMRClient.setup() call at startup';
|
|
36
|
+
static readonly MAX_PENDING_LOGS = 100;
|
|
37
|
+
|
|
38
|
+
private enabled = true;
|
|
39
|
+
private _socketHolder: SocketInstance | null = null;
|
|
40
|
+
private unavailableMessage: string | null = null;
|
|
41
|
+
private compileErrorMessage: string | null = null;
|
|
42
|
+
private pendingUpdatesCount = 0;
|
|
43
|
+
private readonly pendingLogs: [HMRClientLogLevel, any[]][] = [];
|
|
44
|
+
|
|
45
|
+
enable() {
|
|
46
|
+
if (this.unavailableMessage) {
|
|
47
|
+
throw new Error(this.unavailableMessage);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (this._socketHolder == null) {
|
|
51
|
+
throw new Error(HMRClient.STARTUP_ERROR);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.enabled = true;
|
|
55
|
+
this.showCompileErrorIfNeeded();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
disable() {
|
|
59
|
+
this.enabled = false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
registerBundle(requestUrl: string) {
|
|
63
|
+
if (this._socketHolder == null) {
|
|
64
|
+
throw new Error(HMRClient.STARTUP_ERROR);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!requestUrl.startsWith(this._socketHolder.origin)) {
|
|
68
|
+
console.warn(
|
|
69
|
+
`[HMR]: Cannot register bundle from unknown origin:\n${requestUrl}\n` +
|
|
70
|
+
`(expected: ${this._socketHolder.origin})`,
|
|
71
|
+
);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Nothing to do for Rollipop HMR runtime
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
log(level: HMRClientLogLevel, data: any[]) {
|
|
79
|
+
if (this._socketHolder == null) {
|
|
80
|
+
this.pendingLogs.push([level, data]);
|
|
81
|
+
if (this.pendingLogs.length > HMRClient.MAX_PENDING_LOGS) {
|
|
82
|
+
this.pendingLogs.shift();
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const prettifyData = data.map((item) =>
|
|
89
|
+
typeof item === 'string'
|
|
90
|
+
? item
|
|
91
|
+
: prettyFormat.format(item, {
|
|
92
|
+
escapeString: true,
|
|
93
|
+
highlight: true,
|
|
94
|
+
maxDepth: 3,
|
|
95
|
+
min: true,
|
|
96
|
+
plugins: [prettyFormat.plugins.ReactElement],
|
|
97
|
+
}),
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
this.send({ type: 'hmr:log', level, data: prettifyData });
|
|
101
|
+
} catch {}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
setup(
|
|
105
|
+
platform: string,
|
|
106
|
+
bundleEntry: string,
|
|
107
|
+
host: string,
|
|
108
|
+
port: number | string,
|
|
109
|
+
isEnabled = true,
|
|
110
|
+
protocol = 'http',
|
|
111
|
+
) {
|
|
112
|
+
if (!__DEV__) {
|
|
113
|
+
throw new Error('HMR is only available in development mode');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (this._socketHolder != null) {
|
|
117
|
+
throw new Error('Cannot initialize HMRClient more than once');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (platform == null) {
|
|
121
|
+
throw new Error('Missing required parameter `platform`');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (bundleEntry == null) {
|
|
125
|
+
throw new Error('Missing required parameter `bundleEntry`');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (host == null) {
|
|
129
|
+
throw new Error('Missing required parameter `host`');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const serverHost = port !== null && port !== '' ? `${host}:${port}` : host;
|
|
133
|
+
const origin = `${protocol}://${serverHost}`;
|
|
134
|
+
const socket = new globalThis.WebSocket(`${origin}/hot`);
|
|
135
|
+
|
|
136
|
+
this._socketHolder = { socket, origin };
|
|
137
|
+
|
|
138
|
+
socket.addEventListener('open', () => {
|
|
139
|
+
socket.send(
|
|
140
|
+
JSON.stringify({ type: 'hmr:connected', bundleEntry, platform } satisfies HMRClientMessage),
|
|
141
|
+
);
|
|
142
|
+
this.handleConnection();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
socket.addEventListener('error', (event) => {
|
|
146
|
+
this.handleConnectionError(event.error, origin);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
socket.addEventListener('message', (event) => {
|
|
150
|
+
this.handleMessage(event);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
socket.addEventListener('close', (event) => {
|
|
154
|
+
this.handleClose(event);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// `__rolldown_runtime__` can be undefined when HMR is disabled.
|
|
158
|
+
if (globalThis.__rolldown_runtime__ != null) {
|
|
159
|
+
globalThis.__rolldown_runtime__.setup(socket, origin);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
this.enabled = isEnabled;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private send(payload: HMRClientMessage) {
|
|
166
|
+
if (this._socketHolder == null) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (this._socketHolder.socket.readyState === WebSocket.OPEN) {
|
|
171
|
+
this._socketHolder.socket.send(JSON.stringify(payload));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private flushEarlyLogs() {
|
|
176
|
+
if (this._socketHolder == null || this._socketHolder.socket.readyState !== WebSocket.OPEN) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
for (const [level, data] of this.pendingLogs) {
|
|
181
|
+
this.send({ type: 'hmr:log', level, data });
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
this.pendingLogs.length = 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private dismissRedbox() {
|
|
188
|
+
if (Platform.OS === 'ios' && NativeRedBox != null && NativeRedBox.dismiss != null) {
|
|
189
|
+
NativeRedBox.dismiss();
|
|
190
|
+
} else {
|
|
191
|
+
const NativeExceptionsManager = require('../Core/NativeExceptionsManager').default as {
|
|
192
|
+
dismissRedbox?: () => void;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
if (NativeExceptionsManager != null && NativeExceptionsManager.dismissRedbox) {
|
|
196
|
+
NativeExceptionsManager.dismissRedbox();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private showCompileErrorIfNeeded() {
|
|
202
|
+
if (this.compileErrorMessage == null) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
this.dismissRedbox();
|
|
207
|
+
|
|
208
|
+
const error = new Error(this.compileErrorMessage);
|
|
209
|
+
this.compileErrorMessage = null;
|
|
210
|
+
|
|
211
|
+
Object.defineProperty(error, 'preventSymbolication', { value: true });
|
|
212
|
+
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private showUnavailableMessageIfNeeded() {
|
|
217
|
+
if (this.unavailableMessage == null) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
DevLoadingView.hide();
|
|
222
|
+
|
|
223
|
+
if (this.enabled) {
|
|
224
|
+
DevLoadingView.showMessage('Fast Refresh disconnected. Reload app to reconnect.', 'error', {
|
|
225
|
+
dismissButton: true,
|
|
226
|
+
});
|
|
227
|
+
console.warn(this.unavailableMessage);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private handleConnection() {
|
|
232
|
+
DevLoadingView.hide();
|
|
233
|
+
this.flushEarlyLogs();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private handleConnectionError(error: Error, origin: string) {
|
|
237
|
+
let errorMessage =
|
|
238
|
+
'Cannot connect to Rollipop.\n\n' +
|
|
239
|
+
'Try the following to fix the issue:\n' +
|
|
240
|
+
'- Ensure that Rollipop is running and available on the same network';
|
|
241
|
+
|
|
242
|
+
if (Platform.OS === 'ios') {
|
|
243
|
+
errorMessage += '- Ensure that the Rollipop URL is correctly set in AppDelegate';
|
|
244
|
+
} else {
|
|
245
|
+
errorMessage +=
|
|
246
|
+
`- 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\n` +
|
|
247
|
+
`- 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\n` +
|
|
248
|
+
`- If your device is on the same Wi-Fi network, set 'Debug server host & port for device' in 'Dev settings' to your machine's IP address and the port of the local dev server - e.g. 10.0.1.1:8081`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
errorMessage += `\n\nURL: ${origin}` + `\n\nError: ${error.message}`;
|
|
252
|
+
|
|
253
|
+
this.unavailableMessage ??= errorMessage;
|
|
254
|
+
this.showCompileErrorIfNeeded();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private handleMessage(message: MessageEvent) {
|
|
258
|
+
const data = JSON.parse(String(message.data)) as HMRServerMessage;
|
|
259
|
+
|
|
260
|
+
if (!this.enabled && data.type.startsWith('hmr:')) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
switch (data.type) {
|
|
265
|
+
case 'hmr:update-start':
|
|
266
|
+
this.pendingUpdatesCount++;
|
|
267
|
+
this.compileErrorMessage = null;
|
|
268
|
+
DevLoadingView.showMessage('Refreshing...', 'refresh');
|
|
269
|
+
break;
|
|
270
|
+
|
|
271
|
+
case 'hmr:update':
|
|
272
|
+
this.dismissRedbox();
|
|
273
|
+
LogBox.clearAllLogs();
|
|
274
|
+
break;
|
|
275
|
+
|
|
276
|
+
case 'hmr:update-done':
|
|
277
|
+
this.pendingUpdatesCount = Math.max(0, this.pendingUpdatesCount - 1);
|
|
278
|
+
if (this.pendingUpdatesCount === 0) {
|
|
279
|
+
DevLoadingView.hide();
|
|
280
|
+
}
|
|
281
|
+
break;
|
|
282
|
+
|
|
283
|
+
case 'hmr:error':
|
|
284
|
+
this.compileErrorMessage = data.payload.message;
|
|
285
|
+
this.showCompileErrorIfNeeded();
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private handleClose(event: CloseEvent) {
|
|
291
|
+
const { code, reason } = event;
|
|
292
|
+
// https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1
|
|
293
|
+
// https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.5
|
|
294
|
+
const isNormalOrUnsetCloseReason = code === 1000 || code === 1005;
|
|
295
|
+
|
|
296
|
+
const message = isNormalOrUnsetCloseReason
|
|
297
|
+
? 'Disconnected from Rollipop.'
|
|
298
|
+
: `Disconnected from Rollipop (${code}: "${reason}").`;
|
|
299
|
+
|
|
300
|
+
this.unavailableMessage ??=
|
|
301
|
+
message +
|
|
302
|
+
'\n\n' +
|
|
303
|
+
'To reconnect:\n' +
|
|
304
|
+
'- Ensure that Rollipop is running and available on the same network\n' +
|
|
305
|
+
'- Reload this app (will trigger further help if Rollipop cannot be connected to)\n';
|
|
306
|
+
|
|
307
|
+
this.showUnavailableMessageIfNeeded();
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const instance = new HMRClient();
|
|
312
|
+
|
|
313
|
+
// For compatibility with CommonJS modules.
|
|
314
|
+
//
|
|
315
|
+
// ```ts
|
|
316
|
+
// import HMRClient from '/path/to/hmr-client';
|
|
317
|
+
// const HMRClient = require('/path/to/hmr-client');
|
|
318
|
+
// const HMRClient = require('/path/to/hmr-client').default;
|
|
319
|
+
// ```
|
|
320
|
+
export default Object.defineProperty(instance, 'default', {
|
|
321
|
+
get: () => instance,
|
|
322
|
+
});
|