@jsenv/core 27.5.2 → 27.6.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.
@@ -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,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,60 +138,179 @@ 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
+ if (errorMeta.type === "dynamic_import_fetch_error") {
168
+ const response = await window.fetch(`/__get_error_cause__/${urlSite.isInline ? urlSite.originalUrl : urlSite.url}`);
169
+
170
+ if (response.status !== 200) {
171
+ return null;
172
+ }
173
+
174
+ const causeInfo = await response.json();
175
+
176
+ if (!causeInfo) {
177
+ return null;
178
+ }
179
+
180
+ const causeText = causeInfo.code === "NOT_FOUND" ? formatErrorText({
181
+ message: causeInfo.reason,
182
+ stack: causeInfo.codeFrame
183
+ }) : formatErrorText({
184
+ message: causeInfo.stack,
185
+ stack: causeInfo.codeFrame
186
+ });
187
+ return {
188
+ cause: causeText
189
+ };
190
+ }
191
+
192
+ if (urlSite.line !== undefined) {
193
+ let ressourceToFetch = `/__get_code_frame__/${formatUrlWithLineAndColumn(urlSite)}`;
194
+
195
+ if (!Error.captureStackTrace) {
196
+ ressourceToFetch += `?remap`;
197
+ }
198
+
199
+ const response = await window.fetch(ressourceToFetch);
200
+ const codeFrame = await response.text();
201
+ return {
202
+ codeFrame: formatErrorText({
203
+ message: codeFrame
204
+ })
205
+ };
206
+ }
207
+
208
+ return null;
129
209
  })();
130
210
  }; // error.stack is more reliable than url/line/column reported on window error events
131
211
  // so use it only when error.stack is not available
132
212
 
133
213
 
134
- if (url && !stack) {
214
+ if (url && !stack && // ignore window.reportError() it gives no valuable info
215
+ !url.endsWith("html_supervisor_installer.js")) {
135
216
  onErrorLocated(resolveUrlSite({
136
217
  url,
137
218
  line,
138
219
  column
139
220
  }));
140
- }
141
-
142
- let text;
143
-
144
- if (message && stack) {
145
- text = `${generateClickableText(message)}\n${generateClickableText(stack)}`;
146
- } else if (stack) {
147
- text = generateClickableText(stack);
148
- } else {
149
- text = generateClickableText(message);
150
- }
151
-
152
- if (codeFrame) {
153
- text += `\n\n${generateClickableText(codeFrame)}`;
221
+ } else if (errorMeta.url) {
222
+ onErrorLocated(resolveUrlSite(errorMeta));
154
223
  }
155
224
 
156
225
  return {
157
226
  theme: error && error.cause && error.cause.code === "PARSE_ERROR" ? "light" : "dark",
158
227
  title: "An error occured",
159
- text,
160
- codeFramePromise: codeFramePromiseReference.current,
228
+ text: formatErrorText({
229
+ message,
230
+ stack
231
+ }),
161
232
  tip: `${tip}
162
233
  <br />
163
- Click outside to close.`
234
+ Click outside to close.`,
235
+ errorDetailsPromise: errorDetailsPromiseReference.current
164
236
  };
165
237
  };
166
238
 
239
+ const extractErrorMeta = (error, {
240
+ line
241
+ }) => {
242
+ if (!error) {
243
+ return {};
244
+ }
245
+
246
+ const {
247
+ message
248
+ } = error;
249
+
250
+ if (!message) {
251
+ return {};
252
+ }
253
+
254
+ {
255
+ // chrome
256
+ if (message.includes("does not provide an export named")) {
257
+ return {
258
+ type: "dynamic_import_export_missing"
259
+ };
260
+ } // firefox
261
+
262
+
263
+ if (message.startsWith("import not found:")) {
264
+ return {
265
+ type: "dynamic_import_export_missing",
266
+ browser: "firefox"
267
+ };
268
+ } // safari
269
+
270
+
271
+ if (message.startsWith("import binding name")) {
272
+ return {
273
+ type: "dynamic_import_export_missing"
274
+ };
275
+ }
276
+ }
277
+
278
+ {
279
+ if (error.name === "SyntaxError" && typeof line === "number") {
280
+ return {
281
+ type: "dynamic_import_syntax_error"
282
+ };
283
+ }
284
+ }
285
+
286
+ {
287
+ // chrome
288
+ if (message.startsWith("Failed to fetch dynamically imported module: ")) {
289
+ const url = error.message.slice("Failed to fetch dynamically imported module: ".length);
290
+ return {
291
+ type: "dynamic_import_fetch_error",
292
+ url
293
+ };
294
+ } // firefox
295
+
296
+
297
+ if (message === "error loading dynamically imported module") {
298
+ return {
299
+ type: "dynamic_import_fetch_error"
300
+ };
301
+ } // safari
302
+
303
+
304
+ if (message === "Importing a module script failed.") {
305
+ return {
306
+ type: "dynamic_import_fetch_error"
307
+ };
308
+ }
309
+ }
310
+
311
+ return {};
312
+ };
313
+
167
314
  const formatUrlWithLineAndColumn = ({
168
315
  url,
169
316
  line,
@@ -250,17 +397,6 @@ const getErrorStackWithoutErrorMessage = error => {
250
397
  return stack;
251
398
  };
252
399
 
253
- const formatTip = ({
254
- reportedBy,
255
- requestedRessource
256
- }) => {
257
- if (reportedBy === "browser") {
258
- return `Reported by the browser while executing <code>${window.location.pathname}${window.location.search}</code>.`;
259
- }
260
-
261
- return `Reported by the server while serving <code>${requestedRessource}</code>`;
262
- };
263
-
264
400
  const makeLinksClickable = (string, {
265
401
  createLink = url => url
266
402
  }) => {
@@ -365,16 +501,14 @@ const displayErrorInDocument = (error, {
365
501
  url,
366
502
  line,
367
503
  column,
368
- codeFrame,
369
- reportedBy,
370
- requestedRessource
504
+ codeFrame
371
505
  }) => {
372
506
  const {
373
507
  theme,
374
508
  title,
375
509
  text,
376
- codeFramePromise,
377
- tip
510
+ tip,
511
+ errorDetailsPromise
378
512
  } = formatError(error, {
379
513
  rootDirectoryUrl,
380
514
  errorBaseUrl,
@@ -382,16 +516,14 @@ const displayErrorInDocument = (error, {
382
516
  url,
383
517
  line,
384
518
  column,
385
- codeFrame,
386
- reportedBy,
387
- requestedRessource
519
+ codeFrame
388
520
  });
389
521
  let jsenvErrorOverlay = new JsenvErrorOverlay({
390
522
  theme,
391
523
  title,
392
524
  text,
393
- codeFramePromise,
394
- tip
525
+ tip,
526
+ errorDetailsPromise
395
527
  });
396
528
  document.querySelectorAll(JSENV_ERROR_OVERLAY_TAGNAME).forEach(node => {
397
529
  node.parentNode.removeChild(node);
@@ -421,8 +553,8 @@ class JsenvErrorOverlay extends HTMLElement {
421
553
  theme,
422
554
  title,
423
555
  text,
424
- codeFramePromise,
425
- tip
556
+ tip,
557
+ errorDetailsPromise
426
558
  }) {
427
559
  super();
428
560
  this.root = this.attachShadow({
@@ -453,17 +585,46 @@ class JsenvErrorOverlay extends HTMLElement {
453
585
  this.parentNode.removeChild(this);
454
586
  };
455
587
 
456
- if (codeFramePromise) {
457
- codeFramePromise.then(codeFrame => {
458
- if (this.parentNode) {
588
+ if (errorDetailsPromise) {
589
+ errorDetailsPromise.then(errorDetails => {
590
+ if (!errorDetails || !this.parentNode) {
591
+ return;
592
+ }
593
+
594
+ const {
595
+ codeFrame,
596
+ cause
597
+ } = errorDetails;
598
+
599
+ if (codeFrame) {
459
600
  this.root.querySelector(".text").innerHTML += `\n\n${codeFrame}`;
460
601
  }
602
+
603
+ if (cause) {
604
+ const causeIndented = prefixRemainingLines(cause, " ");
605
+ this.root.querySelector(".text").innerHTML += `\n [cause]: ${causeIndented}`;
606
+ }
461
607
  });
462
608
  }
463
609
  }
464
610
 
465
611
  }
466
612
 
613
+ const prefixRemainingLines = (text, prefix) => {
614
+ const lines = text.split(/\r?\n/);
615
+ const firstLine = lines.shift();
616
+ let result = firstLine;
617
+ let i = 0;
618
+
619
+ while (i < lines.length) {
620
+ const line = lines[i];
621
+ i++;
622
+ result += line.length ? `\n${prefix}${line}` : `\n`;
623
+ }
624
+
625
+ return result;
626
+ };
627
+
467
628
  if (customElements && !customElements.get(JSENV_ERROR_OVERLAY_TAGNAME)) {
468
629
  customElements.define(JSENV_ERROR_OVERLAY_TAGNAME, JsenvErrorOverlay);
469
630
  }
@@ -665,14 +826,18 @@ const installHtmlSupervisor = ({
665
826
  let completed;
666
827
  let result;
667
828
  let error;
829
+ const urlObject = new URL(src, window.location);
668
830
 
669
- try {
670
- const urlObject = new URL(src, window.location);
831
+ if (reload) {
832
+ urlObject.searchParams.set("hmr", Date.now());
833
+ }
671
834
 
672
- if (reload) {
673
- urlObject.searchParams.set("hmr", Date.now());
674
- }
835
+ __html_supervisor__.currentExecution = {
836
+ type: type === "module" ? "dynamic_import" : "script_injection",
837
+ url: urlObject.href
838
+ };
675
839
 
840
+ try {
676
841
  result = await execute(urlObject.href);
677
842
  completed = true;
678
843
  } catch (e) {
@@ -693,6 +858,7 @@ const installHtmlSupervisor = ({
693
858
  console.groupEnd();
694
859
  }
695
860
 
861
+ __html_supervisor__.currentExecution = null;
696
862
  return;
697
863
  }
698
864
 
@@ -720,6 +886,8 @@ const installHtmlSupervisor = ({
720
886
  if (logs) {
721
887
  console.groupEnd();
722
888
  }
889
+
890
+ __html_supervisor__.currentExecution = null;
723
891
  };
724
892
 
725
893
  const classicExecutionQueue = createExecutionQueue(performExecution);
@@ -808,6 +976,21 @@ const installHtmlSupervisor = ({
808
976
  });
809
977
 
810
978
  if (errorOverlay) {
979
+ const onErrorReportedByBrowser = (error, {
980
+ url,
981
+ line,
982
+ column
983
+ }) => {
984
+ displayErrorInDocument(error, {
985
+ rootDirectoryUrl,
986
+ errorBaseUrl,
987
+ openInEditor,
988
+ url,
989
+ line,
990
+ column
991
+ });
992
+ };
993
+
811
994
  window.addEventListener("error", errorEvent => {
812
995
  if (!errorEvent.isTrusted) {
813
996
  // ignore custom error event (not sent by browser)
@@ -815,79 +998,17 @@ const installHtmlSupervisor = ({
815
998
  }
816
999
 
817
1000
  const {
818
- error
1001
+ error,
1002
+ filename,
1003
+ lineno,
1004
+ colno
819
1005
  } = errorEvent;
820
- displayErrorInDocument(error, {
821
- rootDirectoryUrl,
822
- errorBaseUrl,
823
- openInEditor,
824
- url: errorEvent.filename,
825
- line: errorEvent.lineno,
826
- column: errorEvent.colno,
827
- reportedBy: "browser"
1006
+ onErrorReportedByBrowser(error, {
1007
+ url: filename,
1008
+ line: lineno,
1009
+ column: colno
828
1010
  });
829
1011
  });
830
-
831
- if (window.__server_events__) {
832
- const isExecuting = () => {
833
- if (pendingExecutionCount > 0) {
834
- return true;
835
- }
836
-
837
- if (document.readyState === "loading" || document.readyState === "interactive") {
838
- return true;
839
- }
840
-
841
- if (window.__reloader__ && window.__reloader__.status === "reloading") {
842
- return true;
843
- }
844
-
845
- return false;
846
- };
847
-
848
- window.__server_events__.addEventCallbacks({
849
- error_while_serving_file: serverErrorEvent => {
850
- if (!isExecuting()) {
851
- return;
852
- }
853
-
854
- const {
855
- message,
856
- stack,
857
- traceUrl,
858
- traceLine,
859
- traceColumn,
860
- traceMessage,
861
- requestedRessource,
862
- isFaviconAutoRequest
863
- } = JSON.parse(serverErrorEvent.data);
864
-
865
- if (isFaviconAutoRequest) {
866
- return;
867
- } // setTimeout is to ensure the error
868
- // dispatched on window by browser is displayed first,
869
- // then the server error replaces it (because it contains more information)
870
-
871
-
872
- setTimeout(() => {
873
- displayErrorInDocument({
874
- message,
875
- stack
876
- }, {
877
- rootDirectoryUrl,
878
- errorBaseUrl,
879
- openInEditor,
880
- url: traceUrl,
881
- line: traceLine,
882
- column: traceColumn,
883
- codeFrame: traceMessage,
884
- reportedBy: "server",
885
- requestedRessource
886
- });
887
- }, 10);
888
- }
889
- });
890
- }
891
1012
  }
892
1013
  };
893
1014