@jsenv/core 27.3.4 → 27.5.1

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 (88) hide show
  1. package/dist/js/autoreload.js +359 -0
  2. package/dist/js/execute_using_dynamic_import.js +1 -1
  3. package/dist/js/html_supervisor_installer.js +524 -147
  4. package/dist/js/html_supervisor_setup.js +3 -4
  5. package/dist/js/new_stylesheet.js +26 -58
  6. package/dist/js/server_events_client.js +307 -0
  7. package/dist/main.js +7709 -7324
  8. package/package.json +15 -15
  9. package/{README.md → readme.md} +18 -7
  10. package/src/build/build.js +16 -18
  11. package/src/build/start_build_server.js +24 -28
  12. package/src/dev/start_dev_server.js +34 -96
  13. package/src/execute/execute.js +17 -35
  14. package/src/omega/errors.js +43 -9
  15. package/src/omega/kitchen.js +42 -25
  16. package/src/omega/omega_server.js +96 -74
  17. package/src/omega/server/file_service.js +256 -28
  18. package/src/omega/url_graph.js +33 -20
  19. package/src/plugins/autoreload/client/autoreload.js +201 -0
  20. package/src/plugins/autoreload/{dev_sse/client → client}/autoreload_preference.js +0 -0
  21. package/src/plugins/autoreload/{dev_sse/client → client}/reload.js +29 -10
  22. package/src/plugins/autoreload/{dev_sse/client → client}/url_helpers.js +0 -0
  23. package/src/plugins/autoreload/jsenv_plugin_autoreload.js +4 -8
  24. package/src/plugins/autoreload/{dev_sse/jsenv_plugin_dev_sse_client.js → jsenv_plugin_autoreload_client.js} +8 -8
  25. package/src/plugins/autoreload/jsenv_plugin_autoreload_server.js +196 -0
  26. package/src/{dev/plugins → plugins}/explorer/client/explorer.html +0 -0
  27. package/src/{dev/plugins → plugins}/explorer/client/jsenv.png +0 -0
  28. package/src/{dev/plugins → plugins}/explorer/jsenv_plugin_explorer.js +1 -3
  29. package/src/plugins/html_supervisor/client/error_overlay.js +401 -0
  30. package/src/plugins/html_supervisor/client/html_supervisor_installer.js +138 -23
  31. package/src/plugins/html_supervisor/client/html_supervisor_setup.js +3 -4
  32. package/src/plugins/html_supervisor/jsenv_plugin_html_supervisor.js +55 -23
  33. package/src/plugins/inline/jsenv_plugin_html_inline_content.js +97 -117
  34. package/src/plugins/node_esm_resolution/jsenv_plugin_node_esm_resolution.js +66 -58
  35. package/src/plugins/plugin_controller.js +102 -67
  36. package/src/plugins/plugins.js +10 -10
  37. package/src/{helpers/event_source/event_source.js → plugins/server_events/client/event_source_connection.js} +125 -33
  38. package/src/plugins/server_events/client/server_events_client.js +17 -0
  39. package/src/plugins/server_events/jsenv_plugin_server_events_client_injection.js +48 -0
  40. package/src/plugins/server_events/server_events_dispatcher.js +69 -0
  41. package/src/{dev/plugins → plugins}/toolbar/client/animation/toolbar_animation.js +0 -0
  42. package/src/{dev/plugins → plugins}/toolbar/client/eventsource/eventsource.css +0 -0
  43. package/src/{dev/plugins → plugins}/toolbar/client/eventsource/toolbar_eventsource.js +0 -0
  44. package/src/{dev/plugins → plugins}/toolbar/client/execution/execution.css +0 -0
  45. package/src/{dev/plugins → plugins}/toolbar/client/execution/toolbar_execution.js +0 -0
  46. package/src/{dev/plugins → plugins}/toolbar/client/focus/focus.css +0 -0
  47. package/src/{dev/plugins → plugins}/toolbar/client/focus/toolbar_focus.js +0 -0
  48. package/src/{dev/plugins → plugins}/toolbar/client/jsenv_logo.svg +0 -0
  49. package/src/{dev/plugins → plugins}/toolbar/client/notification/toolbar_notification.js +0 -0
  50. package/src/{dev/plugins → plugins}/toolbar/client/responsive/overflow_menu.css +0 -0
  51. package/src/{dev/plugins → plugins}/toolbar/client/responsive/toolbar_responsive.js +0 -0
  52. package/src/{dev/plugins → plugins}/toolbar/client/settings/settings.css +0 -0
  53. package/src/{dev/plugins → plugins}/toolbar/client/settings/toolbar_settings.js +0 -0
  54. package/src/{dev/plugins → plugins}/toolbar/client/theme/jsenv_theme.css +0 -0
  55. package/src/{dev/plugins → plugins}/toolbar/client/theme/light_theme.css +0 -0
  56. package/src/{dev/plugins → plugins}/toolbar/client/theme/toolbar_theme.js +0 -0
  57. package/src/{dev/plugins → plugins}/toolbar/client/toolbar.html +0 -0
  58. package/src/{dev/plugins → plugins}/toolbar/client/toolbar_injector.js +0 -0
  59. package/src/{dev/plugins → plugins}/toolbar/client/toolbar_main.css +0 -0
  60. package/src/{dev/plugins → plugins}/toolbar/client/toolbar_main.js +0 -0
  61. package/src/{dev/plugins → plugins}/toolbar/client/tooltip/tooltip.css +0 -0
  62. package/src/{dev/plugins → plugins}/toolbar/client/tooltip/tooltip.js +0 -0
  63. package/src/{dev/plugins → plugins}/toolbar/client/util/animation.js +0 -0
  64. package/src/{dev/plugins → plugins}/toolbar/client/util/dom.js +0 -0
  65. package/src/{dev/plugins → plugins}/toolbar/client/util/fetch_using_xhr.js +0 -0
  66. package/src/{dev/plugins → plugins}/toolbar/client/util/fetching.js +0 -0
  67. package/src/{dev/plugins → plugins}/toolbar/client/util/iframe_to_parent_href.js +0 -0
  68. package/src/{dev/plugins → plugins}/toolbar/client/util/jsenv_logger.js +0 -0
  69. package/src/{dev/plugins → plugins}/toolbar/client/util/preferences.js +0 -0
  70. package/src/{dev/plugins → plugins}/toolbar/client/util/responsive.js +0 -0
  71. package/src/{dev/plugins → plugins}/toolbar/client/util/util.js +0 -0
  72. package/src/{dev/plugins → plugins}/toolbar/client/variant/variant.js +0 -0
  73. package/src/{dev/plugins → plugins}/toolbar/jsenv_plugin_toolbar.js +0 -0
  74. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic_html.js +4 -3
  75. package/src/plugins/transpilation/babel/new_stylesheet/client/new_stylesheet.js +25 -55
  76. package/src/plugins/transpilation/import_assertions/jsenv_plugin_import_assertions.js +44 -24
  77. package/src/plugins/transpilation/jsenv_plugin_transpilation.js +6 -1
  78. package/src/plugins/url_analysis/html/html_urls.js +8 -8
  79. package/src/plugins/url_analysis/jsenv_plugin_url_analysis.js +3 -1
  80. package/src/test/execute_plan.js +36 -54
  81. package/src/test/execute_test_plan.js +2 -2
  82. package/src/test/logs_file_execution.js +60 -27
  83. package/src/test/logs_file_execution.test.mjs +41 -0
  84. package/dist/js/event_source_client.js +0 -528
  85. package/src/helpers/event_source/sse_service.js +0 -53
  86. package/src/plugins/autoreload/dev_sse/client/event_source_client.js +0 -193
  87. package/src/plugins/autoreload/dev_sse/jsenv_plugin_dev_sse_server.js +0 -203
  88. package/src/plugins/html_supervisor/client/error_in_document.js +0 -198
@@ -217,7 +217,6 @@ const formatConsoleCalls = (consoleCalls) => {
217
217
  if (consoleCalls.length === 0) {
218
218
  return ""
219
219
  }
220
-
221
220
  const repartition = {
222
221
  debug: 0,
223
222
  info: 0,
@@ -225,17 +224,10 @@ const formatConsoleCalls = (consoleCalls) => {
225
224
  error: 0,
226
225
  log: 0,
227
226
  }
228
- let consoleOutput = ``
229
227
  consoleCalls.forEach((consoleCall) => {
230
228
  repartition[consoleCall.type]++
231
- const text = consoleCall.text
232
- const textFormatted = prefixFirstAndIndentRemainingLines({
233
- prefix: CONSOLE_ICONS[consoleCall.type],
234
- text,
235
- trimLastLine: consoleCall === consoleCalls[consoleCalls.length - 1],
236
- })
237
- consoleOutput += textFormatted
238
229
  })
230
+ const consoleOutput = formatConsoleOutput(consoleCalls)
239
231
 
240
232
  return `${ANSI.color(
241
233
  `-------- ${formatConsoleSummary(repartition)} --------`,
@@ -245,6 +237,65 @@ ${consoleOutput}
245
237
  ${ANSI.color(`-------------------------`, ANSI.GREY)}`
246
238
  }
247
239
 
240
+ export const formatConsoleOutput = (consoleCalls) => {
241
+ // inside Node.js you can do process.stdout.write()
242
+ // and in that case the consoleCall is not suffixed with "\n"
243
+ // we want to keep these calls together in the output
244
+ const regroupedCalls = []
245
+ consoleCalls.forEach((consoleCall, index) => {
246
+ if (index === 0) {
247
+ regroupedCalls.push(consoleCall)
248
+ return
249
+ }
250
+ const previousCall = consoleCalls[index - 1]
251
+ if (previousCall.type !== consoleCall.type) {
252
+ regroupedCalls.push(consoleCall)
253
+ return
254
+ }
255
+ if (previousCall.text.endsWith("\n")) {
256
+ regroupedCalls.push(consoleCall)
257
+ return
258
+ }
259
+ if (previousCall.text.endsWith("\r")) {
260
+ regroupedCalls.push(consoleCall)
261
+ return
262
+ }
263
+ const previousRegroupedCallIndex = regroupedCalls.length - 1
264
+ const previousRegroupedCall = regroupedCalls[previousRegroupedCallIndex]
265
+ previousRegroupedCall.text = `${previousRegroupedCall.text}${consoleCall.text}`
266
+ })
267
+
268
+ let consoleOutput = ``
269
+ regroupedCalls.forEach((regroupedCall, index) => {
270
+ const text = regroupedCall.text
271
+ const textFormatted = prefixFirstAndIndentRemainingLines({
272
+ prefix: CONSOLE_ICONS[regroupedCall.type],
273
+ text,
274
+ trimLastLine: index === regroupedCalls.length - 1,
275
+ })
276
+ consoleOutput += textFormatted
277
+ })
278
+ return consoleOutput
279
+ }
280
+
281
+ const prefixFirstAndIndentRemainingLines = ({ prefix, text, trimLastLine }) => {
282
+ const lines = text.split(/\r?\n/)
283
+ const firstLine = lines.shift()
284
+ let result = `${prefix} ${firstLine}`
285
+ let i = 0
286
+ const indentation = ` `
287
+ while (i < lines.length) {
288
+ const line = lines[i].trim()
289
+ i++
290
+ result += line.length
291
+ ? `\n${indentation}${line}`
292
+ : trimLastLine && i === lines.length
293
+ ? ""
294
+ : `\n`
295
+ }
296
+ return result
297
+ }
298
+
248
299
  const CONSOLE_ICONS = {
249
300
  debug: UNICODE.DEBUG,
250
301
  info: UNICODE.INFO,
@@ -274,24 +325,6 @@ const formatConsoleSummary = (repartition) => {
274
325
  return `console (${parts.join(" ")})`
275
326
  }
276
327
 
277
- const prefixFirstAndIndentRemainingLines = ({ prefix, text, trimLastLine }) => {
278
- const lines = text.split(/\r?\n/)
279
- const firstLine = lines.shift()
280
- let result = `${prefix} ${firstLine}`
281
- let i = 0
282
- const indentation = ` `
283
- while (i < lines.length) {
284
- const line = lines[i].trim()
285
- i++
286
- result += line.length
287
- ? `\n${indentation}${line}`
288
- : trimLastLine && i === lines.length
289
- ? ""
290
- : `\n`
291
- }
292
- return result
293
- }
294
-
295
328
  const formatExecution = ({ label, details = {}, consoleOutput }) => {
296
329
  let message = ``
297
330
  message += label
@@ -0,0 +1,41 @@
1
+ import { assert } from "@jsenv/assert"
2
+
3
+ import { formatConsoleOutput } from "./logs_file_execution.js"
4
+
5
+ {
6
+ const actual = formatConsoleOutput([
7
+ { type: "log", text: "a\n" },
8
+ { type: "log", text: "b\n" },
9
+ ])
10
+ const expected = ` a
11
+ b`
12
+ assert({ actual, expected })
13
+ }
14
+
15
+ {
16
+ const actual = formatConsoleOutput([
17
+ { type: "log", text: "a" },
18
+ { type: "log", text: "b" },
19
+ ])
20
+ const expected = ` ab`
21
+ assert({ actual, expected })
22
+ }
23
+
24
+ {
25
+ const actual = formatConsoleOutput([
26
+ {
27
+ type: "log",
28
+ text: `1
29
+ 2`,
30
+ },
31
+ {
32
+ type: "log",
33
+ text: `alpha
34
+ beta`,
35
+ },
36
+ ])
37
+ const expected = ` 1
38
+ 2alpha
39
+ beta`
40
+ assert({ actual, expected })
41
+ }
@@ -1,528 +0,0 @@
1
- import { urlHotMetas } from "./import_meta_hot.js";
2
- import { parseSrcSet, stringifySrcSet } from "@jsenv/ast/src/html/html_src_set.js";
3
-
4
- /* eslint-env browser */
5
- const createEventSourceConnection = (eventSourceUrl, events = {}, {
6
- retryMaxAttempt = Infinity,
7
- retryAllocatedMs = Infinity,
8
- lastEventId
9
- } = {}) => {
10
- const {
11
- EventSource
12
- } = window;
13
-
14
- if (typeof EventSource !== "function") {
15
- return () => {};
16
- }
17
-
18
- const eventSourceOrigin = new URL(eventSourceUrl).origin;
19
- Object.keys(events).forEach(eventName => {
20
- const eventCallback = events[eventName];
21
-
22
- events[eventName] = e => {
23
- if (e.origin === eventSourceOrigin) {
24
- if (e.lastEventId) {
25
- lastEventId = e.lastEventId;
26
- }
27
-
28
- eventCallback(e);
29
- }
30
- };
31
- });
32
- const status = {
33
- value: "default",
34
- goTo: value => {
35
- if (value === status.value) {
36
- return;
37
- }
38
-
39
- status.value = value;
40
- status.onchange();
41
- },
42
- onchange: () => {}
43
- };
44
-
45
- let _disconnect = () => {};
46
-
47
- const attemptConnection = url => {
48
- const eventSource = new EventSource(url, {
49
- withCredentials: true
50
- });
51
-
52
- _disconnect = () => {
53
- if (status.value !== "connecting" && status.value !== "connected") {
54
- console.warn(`disconnect() ignored because connection is ${status.value}`);
55
- return;
56
- }
57
-
58
- eventSource.onerror = undefined;
59
- eventSource.close();
60
- Object.keys(events).forEach(eventName => {
61
- eventSource.removeEventListener(eventName, events[eventName]);
62
- });
63
- status.goTo("disconnected");
64
- };
65
-
66
- let retryCount = 0;
67
- let firstRetryMs = Date.now();
68
-
69
- eventSource.onerror = errorEvent => {
70
- if (errorEvent.target.readyState === EventSource.CONNECTING) {
71
- if (retryCount > retryMaxAttempt) {
72
- console.info(`could not connect after ${retryMaxAttempt} attempt`);
73
-
74
- _disconnect();
75
-
76
- return;
77
- }
78
-
79
- if (retryCount === 0) {
80
- firstRetryMs = Date.now();
81
- } else {
82
- const allRetryDuration = Date.now() - firstRetryMs;
83
-
84
- if (retryAllocatedMs && allRetryDuration > retryAllocatedMs) {
85
- console.info(`could not connect in less than ${retryAllocatedMs} ms`);
86
-
87
- _disconnect();
88
-
89
- return;
90
- }
91
- }
92
-
93
- retryCount++;
94
- status.goTo("connecting");
95
- return;
96
- }
97
-
98
- if (errorEvent.target.readyState === EventSource.CLOSED) {
99
- _disconnect();
100
-
101
- return;
102
- }
103
- };
104
-
105
- eventSource.onopen = () => {
106
- status.goTo("connected");
107
- };
108
-
109
- Object.keys(events).forEach(eventName => {
110
- eventSource.addEventListener(eventName, events[eventName]);
111
- });
112
-
113
- if (!events.hasOwnProperty("welcome")) {
114
- eventSource.addEventListener("welcome", e => {
115
- if (e.origin === eventSourceOrigin && e.lastEventId) {
116
- lastEventId = e.lastEventId;
117
- }
118
- });
119
- }
120
-
121
- status.goTo("connecting");
122
- };
123
-
124
- let connect = () => {
125
- attemptConnection(eventSourceUrl);
126
-
127
- connect = () => {
128
- attemptConnection(lastEventId ? addLastEventIdIntoUrlSearchParams(eventSourceUrl, lastEventId) : eventSourceUrl);
129
- };
130
- };
131
-
132
- const removePageUnloadListener = listenPageUnload(() => {
133
- if (status.value === "connecting" || status.value === "connected") {
134
- _disconnect();
135
- }
136
- });
137
-
138
- const destroy = () => {
139
- removePageUnloadListener();
140
-
141
- _disconnect();
142
- };
143
-
144
- return {
145
- status,
146
- connect,
147
- disconnect: () => _disconnect(),
148
- destroy
149
- };
150
- };
151
-
152
- const addLastEventIdIntoUrlSearchParams = (url, lastEventId) => {
153
- if (url.indexOf("?") === -1) {
154
- url += "?";
155
- } else {
156
- url += "&";
157
- }
158
-
159
- return `${url}last-event-id=${encodeURIComponent(lastEventId)}`;
160
- }; // const listenPageMightFreeze = (callback) => {
161
- // const removePageHideListener = listenEvent(window, "pagehide", (pageHideEvent) => {
162
- // if (pageHideEvent.persisted === true) {
163
- // callback(pageHideEvent)
164
- // }
165
- // })
166
- // return removePageHideListener
167
- // }
168
- // const listenPageFreeze = (callback) => {
169
- // const removeFreezeListener = listenEvent(document, "freeze", (freezeEvent) => {
170
- // callback(freezeEvent)
171
- // })
172
- // return removeFreezeListener
173
- // }
174
- // const listenPageIsRestored = (callback) => {
175
- // const removeResumeListener = listenEvent(document, "resume", (resumeEvent) => {
176
- // removePageshowListener()
177
- // callback(resumeEvent)
178
- // })
179
- // const removePageshowListener = listenEvent(window, "pageshow", (pageshowEvent) => {
180
- // if (pageshowEvent.persisted === true) {
181
- // removePageshowListener()
182
- // removeResumeListener()
183
- // callback(pageshowEvent)
184
- // }
185
- // })
186
- // return () => {
187
- // removeResumeListener()
188
- // removePageshowListener()
189
- // }
190
- // }
191
-
192
-
193
- const listenPageUnload = callback => {
194
- const removePageHideListener = listenEvent(window, "pagehide", pageHideEvent => {
195
- if (pageHideEvent.persisted !== true) {
196
- callback(pageHideEvent);
197
- }
198
- });
199
- return removePageHideListener;
200
- };
201
-
202
- const listenEvent = (emitter, event, callback) => {
203
- emitter.addEventListener(event, callback);
204
- return () => {
205
- emitter.removeEventListener(event, callback);
206
- };
207
- };
208
-
209
- const isAutoreloadEnabled = () => {
210
- const value = window.localStorage.getItem("autoreload");
211
-
212
- if (value === "0") {
213
- return false;
214
- }
215
-
216
- return true;
217
- };
218
- const setAutoreloadPreference = value => {
219
- window.localStorage.setItem("autoreload", value ? "1" : "0");
220
- };
221
-
222
- const compareTwoUrlPaths = (url, otherUrl) => {
223
- if (url === otherUrl) {
224
- return true;
225
- }
226
-
227
- const urlObject = new URL(url);
228
- const otherUrlObject = new URL(otherUrl);
229
- return urlObject.origin === otherUrlObject.origin && urlObject.pathname === otherUrlObject.pathname;
230
- };
231
- const injectQuery = (url, query) => {
232
- const urlObject = new URL(url);
233
- const {
234
- searchParams
235
- } = urlObject;
236
- Object.keys(query).forEach(key => {
237
- searchParams.set(key, query[key]);
238
- });
239
- return String(urlObject);
240
- };
241
-
242
- const reloadHtmlPage = () => {
243
- window.location.reload(true);
244
- }; // This function can consider everything as hot reloadable:
245
- // - no need to check [hot-accept]and [hot-decline] attributes for instance
246
- // This is because if something should full reload, we receive "full_reload"
247
- // from server and this function is not called
248
-
249
- const reloadDOMNodesUsingUrl = urlToReload => {
250
- const mutations = [];
251
-
252
- const shouldReloadUrl = urlCandidate => {
253
- return compareTwoUrlPaths(urlCandidate, urlToReload);
254
- };
255
-
256
- const visitNodeAttributeAsUrl = (node, attributeName) => {
257
- let attribute = node[attributeName];
258
-
259
- if (!attribute) {
260
- return;
261
- }
262
-
263
- if (SVGAnimatedString && attribute instanceof SVGAnimatedString) {
264
- attribute = attribute.animVal;
265
- }
266
-
267
- if (!shouldReloadUrl(attribute)) {
268
- return;
269
- }
270
-
271
- mutations.push(() => {
272
- node[attributeName] = injectQuery(attribute, {
273
- hmr: Date.now()
274
- });
275
- });
276
- };
277
-
278
- Array.from(document.querySelectorAll(`link[rel="stylesheet"]`)).forEach(link => {
279
- visitNodeAttributeAsUrl(link, "href");
280
- });
281
- Array.from(document.querySelectorAll(`link[rel="icon"]`)).forEach(link => {
282
- visitNodeAttributeAsUrl(link, "href");
283
- });
284
- Array.from(document.querySelector("script")).forEach(script => {
285
- visitNodeAttributeAsUrl(script, "src");
286
- }); // There is no real need to update a.href because the ressource will be fetched when clicked.
287
- // But in a scenario where the ressource was already visited and is in browser cache, adding
288
- // the dynamic query param ensure the cache is invalidated
289
-
290
- Array.from(document.querySelectorAll("a")).forEach(a => {
291
- visitNodeAttributeAsUrl(a, "href");
292
- }); // About iframes:
293
- // - By default iframe itself and everything inside trigger a parent page full-reload
294
- // - Adding [hot-accept] on the iframe means parent page won't reload when iframe full/hot reload
295
- // In that case and if there is code in the iframe and parent doing post message communication:
296
- // you must put import.meta.hot.decline() for code involved in communication.
297
- // (both in parent and iframe)
298
-
299
- Array.from(document.querySelectorAll("img")).forEach(img => {
300
- visitNodeAttributeAsUrl(img, "src");
301
- const srcset = img.srcset;
302
-
303
- if (srcset) {
304
- const srcCandidates = parseSrcSet(srcset);
305
- srcCandidates.forEach(srcCandidate => {
306
- const url = new URL(srcCandidate.specifier, `${window.location.href}`);
307
-
308
- if (shouldReloadUrl(url)) {
309
- srcCandidate.specifier = injectQuery(url, {
310
- hmr: Date.now()
311
- });
312
- }
313
- });
314
- mutations.push(() => {
315
- img.srcset = stringifySrcSet(srcCandidates);
316
- });
317
- }
318
- });
319
- Array.from(document.querySelectorAll("source")).forEach(source => {
320
- visitNodeAttributeAsUrl(source, "src");
321
- }); // svg image tag
322
-
323
- Array.from(document.querySelectorAll("image")).forEach(image => {
324
- visitNodeAttributeAsUrl(image, "href");
325
- }); // svg use
326
-
327
- Array.from(document.querySelectorAll("use")).forEach(use => {
328
- visitNodeAttributeAsUrl(use, "href");
329
- });
330
- mutations.forEach(mutation => {
331
- mutation();
332
- });
333
- };
334
- const reloadJsImport = async url => {
335
- const urlWithHmr = injectQuery(url, {
336
- hmr: Date.now()
337
- });
338
- const namespace = await import(urlWithHmr);
339
- return namespace;
340
- };
341
-
342
- const reloadMessages = [];
343
- const reloadMessagesSignal = {
344
- onchange: () => {}
345
- };
346
- let pendingCallbacks = [];
347
- let running = false;
348
-
349
- const addToHotQueue = async callback => {
350
- pendingCallbacks.push(callback);
351
- dequeue();
352
- };
353
-
354
- const dequeue = async () => {
355
- if (running) {
356
- return;
357
- }
358
-
359
- const callbacks = pendingCallbacks.slice();
360
- pendingCallbacks = [];
361
- running = true;
362
-
363
- try {
364
- await callbacks.reduce(async (previous, callback) => {
365
- await previous;
366
- await callback();
367
- }, Promise.resolve());
368
- } finally {
369
- running = false;
370
-
371
- if (pendingCallbacks.length) {
372
- dequeue();
373
- }
374
- }
375
- };
376
-
377
- const applyReloadMessageEffects = async () => {
378
- const someEffectIsFullReload = reloadMessages.some(reloadMessage => reloadMessage.type === "full");
379
-
380
- if (someEffectIsFullReload) {
381
- reloadHtmlPage();
382
- return;
383
- }
384
-
385
- const onApplied = reloadMessage => {
386
- const index = reloadMessages.indexOf(reloadMessage);
387
- reloadMessages.splice(index, 1);
388
- reloadMessagesSignal.onchange();
389
- };
390
-
391
- const setReloadMessagePromise = (reloadMessage, promise) => {
392
- reloadMessage.status = "pending";
393
- promise.then(() => {
394
- onApplied(reloadMessage);
395
- }, e => {
396
- // TODO: reuse error display from html supervisor
397
- console.error(e);
398
- console.error(`[hmr] Failed to reload after ${reloadMessage.reason}.
399
- This could be due to syntax errors or importing non-existent modules (see errors above)`);
400
- reloadMessage.status = "failed";
401
- reloadMessagesSignal.onchange();
402
- });
403
- };
404
-
405
- reloadMessages.forEach(reloadMessage => {
406
- if (reloadMessage.type === "hot") {
407
- const promise = addToHotQueue(() => {
408
- return applyHotReload(reloadMessage);
409
- });
410
- setReloadMessagePromise(reloadMessage, promise);
411
- } else {
412
- setReloadMessagePromise(reloadMessage, Promise.resolve());
413
- }
414
- });
415
- reloadMessagesSignal.onchange(); // reload status is "pending"
416
- };
417
-
418
- const applyHotReload = async ({
419
- hotInstructions
420
- }) => {
421
- await hotInstructions.reduce(async (previous, {
422
- type,
423
- boundary,
424
- acceptedBy
425
- }) => {
426
- await previous;
427
- const urlToFetch = new URL(boundary, `${window.location.origin}/`).href;
428
- const urlHotMeta = urlHotMetas[urlToFetch]; // TODO: we should return when there is no url hot meta because
429
- // it means code was not executed (code splitting with dynamic import)
430
- // if (!urlHotMeta) {return }
431
-
432
- if (type === "prune") {
433
- console.groupCollapsed(`[jsenv] prune: ${boundary} (inside ${acceptedBy})`);
434
- } else if (acceptedBy === boundary) {
435
- console.groupCollapsed(`[jsenv] hot reloading: ${boundary}`);
436
- } else {
437
- console.groupCollapsed(`[jsenv] hot reloading: ${acceptedBy} inside ${boundary}`);
438
- }
439
-
440
- if (urlHotMeta && urlHotMeta.disposeCallback) {
441
- console.log(`call dispose callback`);
442
- await urlHotMeta.disposeCallback();
443
- }
444
-
445
- if (type === "prune") {
446
- delete urlHotMetas[urlToFetch];
447
- console.log(`cleanup pruned url`);
448
- console.groupEnd();
449
- return null;
450
- }
451
-
452
- if (type === "js_module") {
453
- console.log(`importing js module`);
454
- const namespace = await reloadJsImport(urlToFetch);
455
-
456
- if (urlHotMeta && urlHotMeta.acceptCallback) {
457
- await urlHotMeta.acceptCallback(namespace);
458
- }
459
-
460
- console.log(`js module import done`);
461
- console.groupEnd();
462
- return namespace;
463
- }
464
-
465
- if (type === "html") {
466
- if (!compareTwoUrlPaths(urlToFetch, window.location.href)) {
467
- // we are not in that HTML page
468
- return null;
469
- }
470
-
471
- console.log(`reloading url`);
472
- const urlToReload = new URL(acceptedBy, `${window.location.origin}/`).href;
473
- reloadDOMNodesUsingUrl(urlToReload);
474
- console.log(`url reloaded`);
475
- console.groupEnd();
476
- return null;
477
- }
478
-
479
- console.warn(`unknown update type: "${type}"`);
480
- return null;
481
- }, Promise.resolve());
482
- };
483
-
484
- const addReloadMessage = reloadMessage => {
485
- reloadMessages.push(reloadMessage);
486
-
487
- if (isAutoreloadEnabled()) {
488
- applyReloadMessageEffects();
489
- } else {
490
- reloadMessagesSignal.onchange();
491
- }
492
- };
493
-
494
- const eventsourceConnection = createEventSourceConnection(document.location.href, {
495
- reload: ({
496
- data
497
- }) => {
498
- const reloadMessage = JSON.parse(data);
499
- addReloadMessage(reloadMessage);
500
- }
501
- }, {
502
- retryMaxAttempt: Infinity,
503
- retryAllocatedMs: 20 * 1000
504
- });
505
- const {
506
- status,
507
- connect,
508
- disconnect
509
- } = eventsourceConnection;
510
- connect();
511
- window.__jsenv_event_source_client__ = {
512
- status,
513
- connect,
514
- disconnect,
515
- isAutoreloadEnabled,
516
- setAutoreloadPreference,
517
- urlHotMetas,
518
- reloadMessages,
519
- reloadMessagesSignal,
520
- applyReloadMessageEffects,
521
- addReloadMessage
522
- }; // const findHotMetaUrl = (originalFileRelativeUrl) => {
523
- // return Object.keys(urlHotMetas).find((compileUrl) => {
524
- // return (
525
- // parseCompiledUrl(compileUrl).fileRelativeUrl === originalFileRelativeUrl
526
- // )
527
- // })
528
- // }