@jsenv/core 27.5.3 → 27.7.0

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 (30) hide show
  1. package/dist/js/autoreload.js +10 -6
  2. package/dist/js/html_supervisor_installer.js +272 -163
  3. package/dist/js/html_supervisor_setup.js +10 -2
  4. package/dist/js/server_events_client.js +249 -216
  5. package/dist/js/wrapper.mjs +4233 -0
  6. package/dist/main.js +21342 -21629
  7. package/package.json +4 -4
  8. package/src/build/build.js +15 -13
  9. package/src/dev/start_dev_server.js +10 -10
  10. package/src/execute/runtimes/browsers/chromium.js +1 -1
  11. package/src/execute/runtimes/browsers/firefox.js +1 -1
  12. package/src/execute/runtimes/browsers/webkit.js +1 -1
  13. package/src/omega/kitchen.js +13 -15
  14. package/src/omega/omega_server.js +15 -0
  15. package/src/omega/server/file_service.js +11 -84
  16. package/src/omega/url_graph/url_graph_load.js +0 -2
  17. package/src/omega/url_graph/url_info_transformations.js +27 -0
  18. package/src/omega/url_graph.js +1 -0
  19. package/src/plugins/autoreload/client/autoreload.js +10 -4
  20. package/src/plugins/html_supervisor/client/error_formatter.js +187 -66
  21. package/src/plugins/html_supervisor/client/error_overlay.js +29 -31
  22. package/src/plugins/html_supervisor/client/html_supervisor_installer.js +22 -67
  23. package/src/plugins/html_supervisor/client/html_supervisor_setup.js +10 -2
  24. package/src/plugins/html_supervisor/jsenv_plugin_html_supervisor.js +96 -25
  25. package/src/plugins/server_events/client/connection_manager.js +165 -0
  26. package/src/plugins/server_events/client/event_source_connection.js +50 -256
  27. package/src/plugins/server_events/client/events_manager.js +75 -0
  28. package/src/plugins/server_events/client/server_events_client.js +12 -11
  29. package/src/plugins/server_events/client/web_socket_connection.js +81 -0
  30. package/src/plugins/server_events/server_events_dispatcher.js +70 -54
@@ -158,6 +158,7 @@ const reloader = {
158
158
  isAutoreloadEnabled,
159
159
  setAutoreloadPreference,
160
160
  status: "idle",
161
+ currentExecution: null,
161
162
  onstatuschange: () => {},
162
163
  setStatus: status => {
163
164
  reloader.status = status;
@@ -195,6 +196,7 @@ const reloader = {
195
196
  const setReloadMessagePromise = (reloadMessage, promise) => {
196
197
  promise.then(() => {
197
198
  onApplied(reloadMessage);
199
+ reloader.currentExecution = null;
198
200
  }, e => {
199
201
  reloader.setStatus("failed");
200
202
 
@@ -206,6 +208,7 @@ const reloader = {
206
208
 
207
209
  console.error(`[jsenv] Hot reload failed after ${reloadMessage.reason}.
208
210
  This could be due to syntax errors or importing non-existent modules (see errors in console)`);
211
+ reloader.currentExecution = null;
209
212
  });
210
213
  };
211
214
 
@@ -299,6 +302,10 @@ const applyHotReload = async ({
299
302
  }
300
303
 
301
304
  console.log(`importing js module`);
305
+ reloader.currentExecution = {
306
+ type: "dynamic_import",
307
+ url: urlToFetch
308
+ };
302
309
  const namespace = await reloadJsImport(urlToFetch);
303
310
 
304
311
  if (urlHotMeta.acceptCallback) {
@@ -343,12 +350,9 @@ const applyHotReload = async ({
343
350
 
344
351
  window.__reloader__ = reloader;
345
352
 
346
- window.__server_events__.addEventCallbacks({
347
- reload: ({
348
- data
349
- }) => {
350
- const reloadMessage = JSON.parse(data);
351
- reloader.addMessage(reloadMessage);
353
+ window.__server_events__.listenEvents({
354
+ reload: reloadServerEvent => {
355
+ reloader.addMessage(reloadServerEvent.data);
352
356
  }
353
357
  }); // const findHotMetaUrl = (originalFileRelativeUrl) => {
354
358
  // return Object.keys(urlHotMetas).find((compileUrl) => {
@@ -16,40 +16,76 @@ const formatError = (error, {
16
16
  openInEditor,
17
17
  url,
18
18
  line,
19
- column,
20
- codeFrame,
21
- requestedRessource,
22
- reportedBy
19
+ column
23
20
  }) => {
24
21
  let {
25
22
  message,
26
23
  stack
27
24
  } = normalizeErrorParts(error);
28
- let codeFramePromiseReference = {
25
+ let errorDetailsPromiseReference = {
29
26
  current: null
30
27
  };
31
- let tip = formatTip({
32
- reportedBy,
33
- requestedRessource
34
- });
28
+ let tip = `Reported by the browser while executing <code>${window.location.pathname}${window.location.search}</code>.`;
35
29
  let errorUrlSite;
30
+ const errorMeta = extractErrorMeta(error, {
31
+ url,
32
+ line,
33
+ column
34
+ });
36
35
 
37
36
  const resolveUrlSite = ({
38
37
  url,
39
38
  line,
40
39
  column
41
40
  }) => {
42
- const inlineUrlMatch = url.match(/@L([0-9]+)\-L([0-9]+)\.[\w]+$/);
41
+ if (typeof line === "string") line = parseInt(line);
42
+ if (typeof column === "string") column = parseInt(column);
43
+ const inlineUrlMatch = url.match(/@L([0-9]+)C([0-9]+)\-L([0-9]+)C([0-9]+)(\.[\w]+)$/);
43
44
 
44
45
  if (inlineUrlMatch) {
45
46
  const htmlUrl = url.slice(0, inlineUrlMatch.index);
46
- const tagLine = parseInt(inlineUrlMatch[1]);
47
- const tagColumn = parseInt(inlineUrlMatch[2]);
47
+ const tagLineStart = parseInt(inlineUrlMatch[1]);
48
+ const tagColumnStart = parseInt(inlineUrlMatch[2]);
49
+ const tagLineEnd = parseInt(inlineUrlMatch[3]);
50
+ const tagColumnEnd = parseInt(inlineUrlMatch[4]);
51
+ const extension = inlineUrlMatch[5];
48
52
  url = htmlUrl;
49
- line = tagLine + parseInt(line) - 1;
50
- column = tagColumn + parseInt(column);
53
+ line = tagLineStart + (typeof line === "number" ? line : 0); // stackTrace formatted by V8 (chrome)
54
+
55
+ if (Error.captureStackTrace) {
56
+ line--;
57
+ }
58
+
59
+ if (errorMeta.type === "dynamic_import_syntax_error") {
60
+ // syntax error on inline script need line-1 for some reason
61
+ if (Error.captureStackTrace) {
62
+ line--;
63
+ } else {
64
+ // firefox and safari need line-2
65
+ line -= 2;
66
+ }
67
+ }
68
+
69
+ column = tagColumnStart + (typeof column === "number" ? column : 0);
70
+ const fileUrl = resolveFileUrl(url);
71
+ return {
72
+ isInline: true,
73
+ originalUrl: `${fileUrl}@L${tagLineStart}C${tagColumnStart}-L${tagLineEnd}C${tagColumnEnd}${extension}`,
74
+ url: fileUrl,
75
+ line,
76
+ column
77
+ };
51
78
  }
52
79
 
80
+ return {
81
+ isInline: false,
82
+ url: resolveFileUrl(url),
83
+ line,
84
+ column
85
+ };
86
+ };
87
+
88
+ const resolveFileUrl = url => {
53
89
  let urlObject = new URL(url);
54
90
 
55
91
  if (urlObject.origin === window.origin) {
@@ -61,19 +97,11 @@ const formatError = (error, {
61
97
 
62
98
  if (atFsIndex > -1) {
63
99
  const afterAtFs = urlObject.pathname.slice(atFsIndex + "/@fs/".length);
64
- url = new URL(afterAtFs, "file:///").href;
65
- } else {
66
- url = urlObject.href;
100
+ return new URL(afterAtFs, "file:///").href;
67
101
  }
68
- } else {
69
- url = urlObject.href;
70
102
  }
71
103
 
72
- return {
73
- url,
74
- line,
75
- column
76
- };
104
+ return urlObject.href;
77
105
  };
78
106
 
79
107
  const generateClickableText = text => {
@@ -110,22 +138,79 @@ const formatError = (error, {
110
138
  return textWithHtmlLinks;
111
139
  };
112
140
 
113
- const onErrorLocated = urlSite => {
114
- errorUrlSite = urlSite;
141
+ const formatErrorText = ({
142
+ message,
143
+ stack,
144
+ codeFrame
145
+ }) => {
146
+ let text;
115
147
 
116
- if (codeFrame) {
117
- return;
148
+ if (message && stack) {
149
+ text = `${generateClickableText(message)}\n${generateClickableText(stack)}`;
150
+ } else if (stack) {
151
+ text = generateClickableText(stack);
152
+ } else {
153
+ text = generateClickableText(message);
118
154
  }
119
155
 
120
- if (reportedBy !== "browser") {
121
- return;
156
+ if (codeFrame) {
157
+ text += `\n\n${generateClickableText(codeFrame)}`;
122
158
  }
123
159
 
124
- codeFramePromiseReference.current = (async () => {
125
- const response = await window.fetch(`/__get_code_frame__/${formatUrlWithLineAndColumn(urlSite)}`);
126
- const codeFrame = await response.text();
127
- const codeFrameClickable = generateClickableText(codeFrame);
128
- return codeFrameClickable;
160
+ return text;
161
+ };
162
+
163
+ const onErrorLocated = urlSite => {
164
+ errorUrlSite = urlSite;
165
+
166
+ errorDetailsPromiseReference.current = (async () => {
167
+ try {
168
+ if (errorMeta.type === "dynamic_import_fetch_error") {
169
+ const response = await window.fetch(`/__get_error_cause__/${urlSite.isInline ? urlSite.originalUrl : urlSite.url}`);
170
+
171
+ if (response.status !== 200) {
172
+ return null;
173
+ }
174
+
175
+ const causeInfo = await response.json();
176
+
177
+ if (!causeInfo) {
178
+ return null;
179
+ }
180
+
181
+ const causeText = causeInfo.code === "NOT_FOUND" ? formatErrorText({
182
+ message: causeInfo.reason,
183
+ stack: causeInfo.codeFrame
184
+ }) : formatErrorText({
185
+ message: causeInfo.stack,
186
+ stack: causeInfo.codeFrame
187
+ });
188
+ return {
189
+ cause: causeText
190
+ };
191
+ }
192
+
193
+ if (urlSite.line !== undefined) {
194
+ let ressourceToFetch = `/__get_code_frame__/${formatUrlWithLineAndColumn(urlSite)}`;
195
+
196
+ if (!Error.captureStackTrace) {
197
+ ressourceToFetch += `?remap`;
198
+ }
199
+
200
+ const response = await window.fetch(ressourceToFetch);
201
+ const codeFrame = await response.text();
202
+ return {
203
+ codeFrame: formatErrorText({
204
+ message: codeFrame
205
+ })
206
+ };
207
+ }
208
+ } catch (e) {
209
+ // happens if server is closed for instance
210
+ return null;
211
+ }
212
+
213
+ return null;
129
214
  })();
130
215
  }; // error.stack is more reliable than url/line/column reported on window error events
131
216
  // so use it only when error.stack is not available
@@ -138,33 +223,99 @@ const formatError = (error, {
138
223
  line,
139
224
  column
140
225
  }));
141
- }
142
-
143
- let text;
144
-
145
- if (message && stack) {
146
- text = `${generateClickableText(message)}\n${generateClickableText(stack)}`;
147
- } else if (stack) {
148
- text = generateClickableText(stack);
149
- } else {
150
- text = generateClickableText(message);
151
- }
152
-
153
- if (codeFrame) {
154
- text += `\n\n${generateClickableText(codeFrame)}`;
226
+ } else if (errorMeta.url) {
227
+ onErrorLocated(resolveUrlSite(errorMeta));
155
228
  }
156
229
 
157
230
  return {
158
231
  theme: error && error.cause && error.cause.code === "PARSE_ERROR" ? "light" : "dark",
159
232
  title: "An error occured",
160
- text,
161
- codeFramePromise: codeFramePromiseReference.current,
233
+ text: formatErrorText({
234
+ message,
235
+ stack
236
+ }),
162
237
  tip: `${tip}
163
238
  <br />
164
- Click outside to close.`
239
+ Click outside to close.`,
240
+ errorDetailsPromise: errorDetailsPromiseReference.current
165
241
  };
166
242
  };
167
243
 
244
+ const extractErrorMeta = (error, {
245
+ line
246
+ }) => {
247
+ if (!error) {
248
+ return {};
249
+ }
250
+
251
+ const {
252
+ message
253
+ } = error;
254
+
255
+ if (!message) {
256
+ return {};
257
+ }
258
+
259
+ {
260
+ // chrome
261
+ if (message.includes("does not provide an export named")) {
262
+ return {
263
+ type: "dynamic_import_export_missing"
264
+ };
265
+ } // firefox
266
+
267
+
268
+ if (message.startsWith("import not found:")) {
269
+ return {
270
+ type: "dynamic_import_export_missing",
271
+ browser: "firefox"
272
+ };
273
+ } // safari
274
+
275
+
276
+ if (message.startsWith("import binding name")) {
277
+ return {
278
+ type: "dynamic_import_export_missing"
279
+ };
280
+ }
281
+ }
282
+
283
+ {
284
+ if (error.name === "SyntaxError" && typeof line === "number") {
285
+ return {
286
+ type: "dynamic_import_syntax_error"
287
+ };
288
+ }
289
+ }
290
+
291
+ {
292
+ // chrome
293
+ if (message.startsWith("Failed to fetch dynamically imported module: ")) {
294
+ const url = error.message.slice("Failed to fetch dynamically imported module: ".length);
295
+ return {
296
+ type: "dynamic_import_fetch_error",
297
+ url
298
+ };
299
+ } // firefox
300
+
301
+
302
+ if (message === "error loading dynamically imported module") {
303
+ return {
304
+ type: "dynamic_import_fetch_error"
305
+ };
306
+ } // safari
307
+
308
+
309
+ if (message === "Importing a module script failed.") {
310
+ return {
311
+ type: "dynamic_import_fetch_error"
312
+ };
313
+ }
314
+ }
315
+
316
+ return {};
317
+ };
318
+
168
319
  const formatUrlWithLineAndColumn = ({
169
320
  url,
170
321
  line,
@@ -251,17 +402,6 @@ const getErrorStackWithoutErrorMessage = error => {
251
402
  return stack;
252
403
  };
253
404
 
254
- const formatTip = ({
255
- reportedBy,
256
- requestedRessource
257
- }) => {
258
- if (reportedBy === "browser") {
259
- return `Reported by the browser while executing <code>${window.location.pathname}${window.location.search}</code>.`;
260
- }
261
-
262
- return `Reported by the server while serving <code>${requestedRessource}</code>`;
263
- };
264
-
265
405
  const makeLinksClickable = (string, {
266
406
  createLink = url => url
267
407
  }) => {
@@ -359,7 +499,6 @@ const link = ({
359
499
  }) => `<a href="${href}">${text}</a>`;
360
500
 
361
501
  const JSENV_ERROR_OVERLAY_TAGNAME = "jsenv-error-overlay";
362
- let previousErrorInfo = null;
363
502
  const displayErrorInDocument = (error, {
364
503
  rootDirectoryUrl,
365
504
  errorBaseUrl,
@@ -367,32 +506,14 @@ const displayErrorInDocument = (error, {
367
506
  url,
368
507
  line,
369
508
  column,
370
- codeFrame,
371
- reportedBy,
372
- requestedRessource
509
+ codeFrame
373
510
  }) => {
374
- const nowMs = Date.now(); // ensure error dispatched on window by browser is displayed first
375
- // then the server error replaces it (because it contains more information)
376
-
377
- if (previousErrorInfo) {
378
- const previousErrorReportedBy = previousErrorInfo.reportedBy;
379
- const msEllapsedSincePreviousError = nowMs - previousErrorInfo.ms;
380
-
381
- if (previousErrorReportedBy === "server" && reportedBy === "browser" && msEllapsedSincePreviousError < 50) {
382
- return () => {};
383
- }
384
- }
385
-
386
- previousErrorInfo = {
387
- ms: nowMs,
388
- reportedBy
389
- };
390
511
  const {
391
512
  theme,
392
513
  title,
393
514
  text,
394
- codeFramePromise,
395
- tip
515
+ tip,
516
+ errorDetailsPromise
396
517
  } = formatError(error, {
397
518
  rootDirectoryUrl,
398
519
  errorBaseUrl,
@@ -400,16 +521,14 @@ const displayErrorInDocument = (error, {
400
521
  url,
401
522
  line,
402
523
  column,
403
- codeFrame,
404
- reportedBy,
405
- requestedRessource
524
+ codeFrame
406
525
  });
407
526
  let jsenvErrorOverlay = new JsenvErrorOverlay({
408
527
  theme,
409
528
  title,
410
529
  text,
411
- codeFramePromise,
412
- tip
530
+ tip,
531
+ errorDetailsPromise
413
532
  });
414
533
  document.querySelectorAll(JSENV_ERROR_OVERLAY_TAGNAME).forEach(node => {
415
534
  node.parentNode.removeChild(node);
@@ -439,8 +558,8 @@ class JsenvErrorOverlay extends HTMLElement {
439
558
  theme,
440
559
  title,
441
560
  text,
442
- codeFramePromise,
443
- tip
561
+ tip,
562
+ errorDetailsPromise
444
563
  }) {
445
564
  super();
446
565
  this.root = this.attachShadow({
@@ -471,17 +590,46 @@ class JsenvErrorOverlay extends HTMLElement {
471
590
  this.parentNode.removeChild(this);
472
591
  };
473
592
 
474
- if (codeFramePromise) {
475
- codeFramePromise.then(codeFrame => {
476
- if (this.parentNode) {
593
+ if (errorDetailsPromise) {
594
+ errorDetailsPromise.then(errorDetails => {
595
+ if (!errorDetails || !this.parentNode) {
596
+ return;
597
+ }
598
+
599
+ const {
600
+ codeFrame,
601
+ cause
602
+ } = errorDetails;
603
+
604
+ if (codeFrame) {
477
605
  this.root.querySelector(".text").innerHTML += `\n\n${codeFrame}`;
478
606
  }
607
+
608
+ if (cause) {
609
+ const causeIndented = prefixRemainingLines(cause, " ");
610
+ this.root.querySelector(".text").innerHTML += `\n [cause]: ${causeIndented}`;
611
+ }
479
612
  });
480
613
  }
481
614
  }
482
615
 
483
616
  }
484
617
 
618
+ const prefixRemainingLines = (text, prefix) => {
619
+ const lines = text.split(/\r?\n/);
620
+ const firstLine = lines.shift();
621
+ let result = firstLine;
622
+ let i = 0;
623
+
624
+ while (i < lines.length) {
625
+ const line = lines[i];
626
+ i++;
627
+ result += line.length ? `\n${prefix}${line}` : `\n`;
628
+ }
629
+
630
+ return result;
631
+ };
632
+
485
633
  if (customElements && !customElements.get(JSENV_ERROR_OVERLAY_TAGNAME)) {
486
634
  customElements.define(JSENV_ERROR_OVERLAY_TAGNAME, JsenvErrorOverlay);
487
635
  }
@@ -683,14 +831,18 @@ const installHtmlSupervisor = ({
683
831
  let completed;
684
832
  let result;
685
833
  let error;
834
+ const urlObject = new URL(src, window.location);
686
835
 
687
- try {
688
- const urlObject = new URL(src, window.location);
836
+ if (reload) {
837
+ urlObject.searchParams.set("hmr", Date.now());
838
+ }
689
839
 
690
- if (reload) {
691
- urlObject.searchParams.set("hmr", Date.now());
692
- }
840
+ __html_supervisor__.currentExecution = {
841
+ type: type === "module" ? "dynamic_import" : "script_injection",
842
+ url: urlObject.href
843
+ };
693
844
 
845
+ try {
694
846
  result = await execute(urlObject.href);
695
847
  completed = true;
696
848
  } catch (e) {
@@ -711,6 +863,7 @@ const installHtmlSupervisor = ({
711
863
  console.groupEnd();
712
864
  }
713
865
 
866
+ __html_supervisor__.currentExecution = null;
714
867
  return;
715
868
  }
716
869
 
@@ -738,6 +891,8 @@ const installHtmlSupervisor = ({
738
891
  if (logs) {
739
892
  console.groupEnd();
740
893
  }
894
+
895
+ __html_supervisor__.currentExecution = null;
741
896
  };
742
897
 
743
898
  const classicExecutionQueue = createExecutionQueue(performExecution);
@@ -773,7 +928,6 @@ const installHtmlSupervisor = ({
773
928
  const useDeferQueue = scriptToExecute.defer || scriptToExecute.type === "module";
774
929
 
775
930
  if (useDeferQueue) {
776
- // defer must wait for classic script to be done
777
931
  const classicExecutionPromise = classicExecutionQueue.getPromise();
778
932
 
779
933
  if (classicExecutionPromise) {
@@ -826,6 +980,21 @@ const installHtmlSupervisor = ({
826
980
  });
827
981
 
828
982
  if (errorOverlay) {
983
+ const onErrorReportedByBrowser = (error, {
984
+ url,
985
+ line,
986
+ column
987
+ }) => {
988
+ displayErrorInDocument(error, {
989
+ rootDirectoryUrl,
990
+ errorBaseUrl,
991
+ openInEditor,
992
+ url,
993
+ line,
994
+ column
995
+ });
996
+ };
997
+
829
998
  window.addEventListener("error", errorEvent => {
830
999
  if (!errorEvent.isTrusted) {
831
1000
  // ignore custom error event (not sent by browser)
@@ -838,72 +1007,12 @@ const installHtmlSupervisor = ({
838
1007
  lineno,
839
1008
  colno
840
1009
  } = errorEvent;
841
- displayErrorInDocument(error, {
842
- rootDirectoryUrl,
843
- errorBaseUrl,
844
- openInEditor,
1010
+ onErrorReportedByBrowser(error, {
845
1011
  url: filename,
846
1012
  line: lineno,
847
- column: colno,
848
- reportedBy: "browser"
1013
+ column: colno
849
1014
  });
850
1015
  });
851
-
852
- if (window.__server_events__) {
853
- const isExecuting = () => {
854
- if (pendingExecutionCount > 0) {
855
- return true;
856
- }
857
-
858
- if (document.readyState === "loading" || document.readyState === "interactive") {
859
- return true;
860
- }
861
-
862
- if (window.__reloader__ && window.__reloader__.status === "reloading") {
863
- return true;
864
- }
865
-
866
- return false;
867
- };
868
-
869
- window.__server_events__.addEventCallbacks({
870
- error_while_serving_file: serverErrorEvent => {
871
- if (!isExecuting()) {
872
- return;
873
- }
874
-
875
- const {
876
- message,
877
- stack,
878
- traceUrl,
879
- traceLine,
880
- traceColumn,
881
- traceMessage,
882
- requestedRessource,
883
- isFaviconAutoRequest
884
- } = JSON.parse(serverErrorEvent.data);
885
-
886
- if (isFaviconAutoRequest) {
887
- return;
888
- }
889
-
890
- displayErrorInDocument({
891
- message,
892
- stack
893
- }, {
894
- rootDirectoryUrl,
895
- errorBaseUrl,
896
- openInEditor,
897
- url: traceUrl,
898
- line: traceLine,
899
- column: traceColumn,
900
- codeFrame: traceMessage,
901
- reportedBy: "server",
902
- requestedRessource
903
- });
904
- }
905
- });
906
- }
907
1016
  }
908
1017
  };
909
1018
 
@@ -9,11 +9,12 @@ window.__html_supervisor__ = {
9
9
  window.__html_supervisor__.scriptsToExecute.push(scriptToExecute)
10
10
  },
11
11
  superviseScript: ({ src, isInline, crossorigin, integrity }) => {
12
+ const { currentScript } = document
12
13
  window.__html_supervisor__.addScriptToExecute({
13
14
  src,
14
15
  type: "js_classic",
15
16
  isInline,
16
- currentScript: document.currentScript,
17
+ currentScript,
17
18
  execute: (url) => {
18
19
  return new Promise((resolve, reject) => {
19
20
  const script = document.createElement("script")
@@ -50,7 +51,14 @@ window.__html_supervisor__ = {
50
51
  resolve()
51
52
  }
52
53
  })
53
- document.body.appendChild(script)
54
+ if (currentScript) {
55
+ currentScript.parentNode.insertBefore(
56
+ script,
57
+ currentScript.nextSibling,
58
+ )
59
+ } else {
60
+ document.body.appendChild(script)
61
+ }
54
62
  })
55
63
  },
56
64
  })