@jsenv/core 27.5.3 → 27.6.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.
@@ -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) {
@@ -16,40 +16,74 @@ 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
+ const inlineUrlMatch = url.match(/@L([0-9]+)C([0-9]+)\-L([0-9]+)C([0-9]+)(\.[\w]+)$/);
43
42
 
44
43
  if (inlineUrlMatch) {
45
44
  const htmlUrl = url.slice(0, inlineUrlMatch.index);
46
- const tagLine = parseInt(inlineUrlMatch[1]);
47
- const tagColumn = parseInt(inlineUrlMatch[2]);
45
+ const tagLineStart = parseInt(inlineUrlMatch[1]);
46
+ const tagColumnStart = parseInt(inlineUrlMatch[2]);
47
+ const tagLineEnd = parseInt(inlineUrlMatch[3]);
48
+ const tagColumnEnd = parseInt(inlineUrlMatch[4]);
49
+ const extension = inlineUrlMatch[5];
48
50
  url = htmlUrl;
49
- line = tagLine + parseInt(line) - 1;
50
- column = tagColumn + parseInt(column);
51
+ line = tagLineStart + (line === undefined ? 0 : parseInt(line)); // stackTrace formatted by V8 (chrome)
52
+
53
+ if (Error.captureStackTrace) {
54
+ line--;
55
+ }
56
+
57
+ if (errorMeta.type === "dynamic_import_syntax_error") {
58
+ // syntax error on inline script need line-1 for some reason
59
+ if (Error.captureStackTrace) {
60
+ line--;
61
+ } else {
62
+ // firefox and safari need line-2
63
+ line -= 2;
64
+ }
65
+ }
66
+
67
+ column = tagColumnStart + (column === undefined ? 0 : parseInt(column));
68
+ const fileUrl = resolveFileUrl(url);
69
+ return {
70
+ isInline: true,
71
+ originalUrl: `${fileUrl}@L${tagLineStart}C${tagColumnStart}-L${tagLineEnd}C${tagColumnEnd}${extension}`,
72
+ url: fileUrl,
73
+ line,
74
+ column
75
+ };
51
76
  }
52
77
 
78
+ return {
79
+ isInline: false,
80
+ url: resolveFileUrl(url),
81
+ line,
82
+ column
83
+ };
84
+ };
85
+
86
+ const resolveFileUrl = url => {
53
87
  let urlObject = new URL(url);
54
88
 
55
89
  if (urlObject.origin === window.origin) {
@@ -61,19 +95,11 @@ const formatError = (error, {
61
95
 
62
96
  if (atFsIndex > -1) {
63
97
  const afterAtFs = urlObject.pathname.slice(atFsIndex + "/@fs/".length);
64
- url = new URL(afterAtFs, "file:///").href;
65
- } else {
66
- url = urlObject.href;
98
+ return new URL(afterAtFs, "file:///").href;
67
99
  }
68
- } else {
69
- url = urlObject.href;
70
100
  }
71
101
 
72
- return {
73
- url,
74
- line,
75
- column
76
- };
102
+ return urlObject.href;
77
103
  };
78
104
 
79
105
  const generateClickableText = text => {
@@ -110,22 +136,68 @@ const formatError = (error, {
110
136
  return textWithHtmlLinks;
111
137
  };
112
138
 
113
- const onErrorLocated = urlSite => {
114
- errorUrlSite = urlSite;
139
+ const formatErrorText = ({
140
+ message,
141
+ stack,
142
+ codeFrame
143
+ }) => {
144
+ let text;
115
145
 
116
- if (codeFrame) {
117
- return;
146
+ if (message && stack) {
147
+ text = `${generateClickableText(message)}\n${generateClickableText(stack)}`;
148
+ } else if (stack) {
149
+ text = generateClickableText(stack);
150
+ } else {
151
+ text = generateClickableText(message);
118
152
  }
119
153
 
120
- if (reportedBy !== "browser") {
121
- return;
154
+ if (codeFrame) {
155
+ text += `\n\n${generateClickableText(codeFrame)}`;
122
156
  }
123
157
 
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;
158
+ return text;
159
+ };
160
+
161
+ const onErrorLocated = urlSite => {
162
+ errorUrlSite = urlSite;
163
+
164
+ errorDetailsPromiseReference.current = (async () => {
165
+ if (errorMeta.type === "dynamic_import_fetch_error") {
166
+ const response = await window.fetch(`/__get_error_cause__/${urlSite.isInline ? urlSite.originalUrl : urlSite.url}`);
167
+
168
+ if (response.status !== 200) {
169
+ return null;
170
+ }
171
+
172
+ const causeInfo = await response.json();
173
+
174
+ if (!causeInfo) {
175
+ return null;
176
+ }
177
+
178
+ const causeText = causeInfo.code === "NOT_FOUND" ? formatErrorText({
179
+ message: causeInfo.reason,
180
+ stack: causeInfo.codeFrame
181
+ }) : formatErrorText({
182
+ message: causeInfo.stack,
183
+ stack: causeInfo.codeFrame
184
+ });
185
+ return {
186
+ cause: causeText
187
+ };
188
+ }
189
+
190
+ if (urlSite.line !== undefined) {
191
+ const response = await window.fetch(`/__get_code_frame__/${formatUrlWithLineAndColumn(urlSite)}`);
192
+ const codeFrame = await response.text();
193
+ return {
194
+ codeFrame: formatErrorText({
195
+ message: codeFrame
196
+ })
197
+ };
198
+ }
199
+
200
+ return null;
129
201
  })();
130
202
  }; // error.stack is more reliable than url/line/column reported on window error events
131
203
  // so use it only when error.stack is not available
@@ -138,33 +210,99 @@ const formatError = (error, {
138
210
  line,
139
211
  column
140
212
  }));
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)}`;
213
+ } else if (errorMeta.url) {
214
+ onErrorLocated(resolveUrlSite(errorMeta));
155
215
  }
156
216
 
157
217
  return {
158
218
  theme: error && error.cause && error.cause.code === "PARSE_ERROR" ? "light" : "dark",
159
219
  title: "An error occured",
160
- text,
161
- codeFramePromise: codeFramePromiseReference.current,
220
+ text: formatErrorText({
221
+ message,
222
+ stack
223
+ }),
162
224
  tip: `${tip}
163
225
  <br />
164
- Click outside to close.`
226
+ Click outside to close.`,
227
+ errorDetailsPromise: errorDetailsPromiseReference.current
165
228
  };
166
229
  };
167
230
 
231
+ const extractErrorMeta = (error, {
232
+ line
233
+ }) => {
234
+ if (!error) {
235
+ return {};
236
+ }
237
+
238
+ const {
239
+ message
240
+ } = error;
241
+
242
+ if (!message) {
243
+ return {};
244
+ }
245
+
246
+ {
247
+ // chrome
248
+ if (message.includes("does not provide an export named")) {
249
+ return {
250
+ type: "dynamic_import_export_missing"
251
+ };
252
+ } // firefox
253
+
254
+
255
+ if (message.startsWith("import not found:")) {
256
+ return {
257
+ type: "dynamic_import_export_missing",
258
+ browser: "firefox"
259
+ };
260
+ } // safari
261
+
262
+
263
+ if (message.startsWith("import binding name")) {
264
+ return {
265
+ type: "dynamic_import_export_missing"
266
+ };
267
+ }
268
+ }
269
+
270
+ {
271
+ if (error.name === "SyntaxError" && typeof line === "number") {
272
+ return {
273
+ type: "dynamic_import_syntax_error"
274
+ };
275
+ }
276
+ }
277
+
278
+ {
279
+ // chrome
280
+ if (message.startsWith("Failed to fetch dynamically imported module: ")) {
281
+ const url = error.message.slice("Failed to fetch dynamically imported module: ".length);
282
+ return {
283
+ type: "dynamic_import_fetch_error",
284
+ url
285
+ };
286
+ } // firefox
287
+
288
+
289
+ if (message === "error loading dynamically imported module") {
290
+ return {
291
+ type: "dynamic_import_fetch_error"
292
+ };
293
+ } // safari
294
+
295
+
296
+ if (message === "Importing a module script failed.") {
297
+ return {
298
+ type: "dynamic_import_fetch_error"
299
+ };
300
+ }
301
+ }
302
+
303
+ return {};
304
+ };
305
+
168
306
  const formatUrlWithLineAndColumn = ({
169
307
  url,
170
308
  line,
@@ -251,17 +389,6 @@ const getErrorStackWithoutErrorMessage = error => {
251
389
  return stack;
252
390
  };
253
391
 
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
392
  const makeLinksClickable = (string, {
266
393
  createLink = url => url
267
394
  }) => {
@@ -359,7 +486,6 @@ const link = ({
359
486
  }) => `<a href="${href}">${text}</a>`;
360
487
 
361
488
  const JSENV_ERROR_OVERLAY_TAGNAME = "jsenv-error-overlay";
362
- let previousErrorInfo = null;
363
489
  const displayErrorInDocument = (error, {
364
490
  rootDirectoryUrl,
365
491
  errorBaseUrl,
@@ -367,32 +493,14 @@ const displayErrorInDocument = (error, {
367
493
  url,
368
494
  line,
369
495
  column,
370
- codeFrame,
371
- reportedBy,
372
- requestedRessource
496
+ codeFrame
373
497
  }) => {
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
498
  const {
391
499
  theme,
392
500
  title,
393
501
  text,
394
- codeFramePromise,
395
- tip
502
+ tip,
503
+ errorDetailsPromise
396
504
  } = formatError(error, {
397
505
  rootDirectoryUrl,
398
506
  errorBaseUrl,
@@ -400,16 +508,14 @@ const displayErrorInDocument = (error, {
400
508
  url,
401
509
  line,
402
510
  column,
403
- codeFrame,
404
- reportedBy,
405
- requestedRessource
511
+ codeFrame
406
512
  });
407
513
  let jsenvErrorOverlay = new JsenvErrorOverlay({
408
514
  theme,
409
515
  title,
410
516
  text,
411
- codeFramePromise,
412
- tip
517
+ tip,
518
+ errorDetailsPromise
413
519
  });
414
520
  document.querySelectorAll(JSENV_ERROR_OVERLAY_TAGNAME).forEach(node => {
415
521
  node.parentNode.removeChild(node);
@@ -439,8 +545,8 @@ class JsenvErrorOverlay extends HTMLElement {
439
545
  theme,
440
546
  title,
441
547
  text,
442
- codeFramePromise,
443
- tip
548
+ tip,
549
+ errorDetailsPromise
444
550
  }) {
445
551
  super();
446
552
  this.root = this.attachShadow({
@@ -471,17 +577,46 @@ class JsenvErrorOverlay extends HTMLElement {
471
577
  this.parentNode.removeChild(this);
472
578
  };
473
579
 
474
- if (codeFramePromise) {
475
- codeFramePromise.then(codeFrame => {
476
- if (this.parentNode) {
580
+ if (errorDetailsPromise) {
581
+ errorDetailsPromise.then(errorDetails => {
582
+ if (!errorDetails || !this.parentNode) {
583
+ return;
584
+ }
585
+
586
+ const {
587
+ codeFrame,
588
+ cause
589
+ } = errorDetails;
590
+
591
+ if (codeFrame) {
477
592
  this.root.querySelector(".text").innerHTML += `\n\n${codeFrame}`;
478
593
  }
594
+
595
+ if (cause) {
596
+ const causeIndented = prefixRemainingLines(cause, " ");
597
+ this.root.querySelector(".text").innerHTML += `\n [cause]: ${causeIndented}`;
598
+ }
479
599
  });
480
600
  }
481
601
  }
482
602
 
483
603
  }
484
604
 
605
+ const prefixRemainingLines = (text, prefix) => {
606
+ const lines = text.split(/\r?\n/);
607
+ const firstLine = lines.shift();
608
+ let result = firstLine;
609
+ let i = 0;
610
+
611
+ while (i < lines.length) {
612
+ const line = lines[i];
613
+ i++;
614
+ result += line.length ? `\n${prefix}${line}` : `\n`;
615
+ }
616
+
617
+ return result;
618
+ };
619
+
485
620
  if (customElements && !customElements.get(JSENV_ERROR_OVERLAY_TAGNAME)) {
486
621
  customElements.define(JSENV_ERROR_OVERLAY_TAGNAME, JsenvErrorOverlay);
487
622
  }
@@ -683,14 +818,18 @@ const installHtmlSupervisor = ({
683
818
  let completed;
684
819
  let result;
685
820
  let error;
821
+ const urlObject = new URL(src, window.location);
686
822
 
687
- try {
688
- const urlObject = new URL(src, window.location);
823
+ if (reload) {
824
+ urlObject.searchParams.set("hmr", Date.now());
825
+ }
689
826
 
690
- if (reload) {
691
- urlObject.searchParams.set("hmr", Date.now());
692
- }
827
+ __html_supervisor__.currentExecution = {
828
+ type: type === "module" ? "dynamic_import" : "script_injection",
829
+ url: urlObject.href
830
+ };
693
831
 
832
+ try {
694
833
  result = await execute(urlObject.href);
695
834
  completed = true;
696
835
  } catch (e) {
@@ -711,6 +850,7 @@ const installHtmlSupervisor = ({
711
850
  console.groupEnd();
712
851
  }
713
852
 
853
+ __html_supervisor__.currentExecution = null;
714
854
  return;
715
855
  }
716
856
 
@@ -738,6 +878,8 @@ const installHtmlSupervisor = ({
738
878
  if (logs) {
739
879
  console.groupEnd();
740
880
  }
881
+
882
+ __html_supervisor__.currentExecution = null;
741
883
  };
742
884
 
743
885
  const classicExecutionQueue = createExecutionQueue(performExecution);
@@ -826,6 +968,21 @@ const installHtmlSupervisor = ({
826
968
  });
827
969
 
828
970
  if (errorOverlay) {
971
+ const onErrorReportedByBrowser = (error, {
972
+ url,
973
+ line,
974
+ column
975
+ }) => {
976
+ displayErrorInDocument(error, {
977
+ rootDirectoryUrl,
978
+ errorBaseUrl,
979
+ openInEditor,
980
+ url,
981
+ line,
982
+ column
983
+ });
984
+ };
985
+
829
986
  window.addEventListener("error", errorEvent => {
830
987
  if (!errorEvent.isTrusted) {
831
988
  // ignore custom error event (not sent by browser)
@@ -838,72 +995,12 @@ const installHtmlSupervisor = ({
838
995
  lineno,
839
996
  colno
840
997
  } = errorEvent;
841
- displayErrorInDocument(error, {
842
- rootDirectoryUrl,
843
- errorBaseUrl,
844
- openInEditor,
998
+ onErrorReportedByBrowser(error, {
845
999
  url: filename,
846
1000
  line: lineno,
847
- column: colno,
848
- reportedBy: "browser"
1001
+ column: colno
849
1002
  });
850
1003
  });
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
1004
  }
908
1005
  };
909
1006