@jsenv/core 27.5.1 → 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.
@@ -10,213 +10,308 @@ const unevalException = value => {
10
10
  });
11
11
  };
12
12
 
13
- const JSENV_ERROR_OVERLAY_TAGNAME = "jsenv-error-overlay";
14
- const displayErrorInDocument = (error, {
13
+ const formatError = (error, {
15
14
  rootDirectoryUrl,
15
+ errorBaseUrl,
16
16
  openInEditor,
17
17
  url,
18
18
  line,
19
- column,
20
- reportedBy,
21
- requestedRessource
19
+ column
22
20
  }) => {
23
- document.querySelectorAll(JSENV_ERROR_OVERLAY_TAGNAME).forEach(node => {
24
- node.parentNode.removeChild(node);
25
- });
26
- const {
27
- theme,
28
- title,
21
+ let {
29
22
  message,
30
- stack,
31
- tip
32
- } = errorToHTML(error, {
23
+ stack
24
+ } = normalizeErrorParts(error);
25
+ let errorDetailsPromiseReference = {
26
+ current: null
27
+ };
28
+ let tip = `Reported by the browser while executing <code>${window.location.pathname}${window.location.search}</code>.`;
29
+ let errorUrlSite;
30
+ const errorMeta = extractErrorMeta(error, {
33
31
  url,
34
32
  line,
35
- column,
36
- reportedBy,
37
- requestedRessource
38
- });
39
- let jsenvErrorOverlay = new JsenvErrorOverlay({
40
- theme,
41
- title,
42
- text: createErrorText({
43
- rootDirectoryUrl,
44
- openInEditor,
45
- message,
46
- stack
47
- }),
48
- tip
33
+ column
49
34
  });
50
- document.body.appendChild(jsenvErrorOverlay);
51
35
 
52
- const removeErrorOverlay = () => {
53
- if (jsenvErrorOverlay && jsenvErrorOverlay.parentNode) {
54
- document.body.removeChild(jsenvErrorOverlay);
55
- jsenvErrorOverlay = null;
36
+ const resolveUrlSite = ({
37
+ url,
38
+ line,
39
+ column
40
+ }) => {
41
+ const inlineUrlMatch = url.match(/@L([0-9]+)C([0-9]+)\-L([0-9]+)C([0-9]+)(\.[\w]+)$/);
42
+
43
+ if (inlineUrlMatch) {
44
+ const htmlUrl = url.slice(0, inlineUrlMatch.index);
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];
50
+ url = htmlUrl;
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
+ };
56
76
  }
77
+
78
+ return {
79
+ isInline: false,
80
+ url: resolveFileUrl(url),
81
+ line,
82
+ column
83
+ };
57
84
  };
58
85
 
59
- if (window.__reloader__) {
60
- window.__reloader__.onstatuschange = () => {
61
- if (window.__reloader__.status === "reloading") {
62
- removeErrorOverlay();
86
+ const resolveFileUrl = url => {
87
+ let urlObject = new URL(url);
88
+
89
+ if (urlObject.origin === window.origin) {
90
+ urlObject = new URL(`${urlObject.pathname.slice(1)}${urlObject.search}`, rootDirectoryUrl);
91
+ }
92
+
93
+ if (urlObject.href.startsWith("file:")) {
94
+ const atFsIndex = urlObject.pathname.indexOf("/@fs/");
95
+
96
+ if (atFsIndex > -1) {
97
+ const afterAtFs = urlObject.pathname.slice(atFsIndex + "/@fs/".length);
98
+ return new URL(afterAtFs, "file:///").href;
63
99
  }
64
- };
100
+ }
101
+
102
+ return urlObject.href;
103
+ };
104
+
105
+ const generateClickableText = text => {
106
+ const textWithHtmlLinks = makeLinksClickable(text, {
107
+ createLink: (url, {
108
+ line,
109
+ column
110
+ }) => {
111
+ const urlSite = resolveUrlSite({
112
+ url,
113
+ line,
114
+ column
115
+ });
116
+
117
+ if (!errorUrlSite && text === stack) {
118
+ onErrorLocated(urlSite);
119
+ }
120
+
121
+ if (errorBaseUrl) {
122
+ if (urlSite.url.startsWith(rootDirectoryUrl)) {
123
+ urlSite.url = `${errorBaseUrl}${urlSite.url.slice(rootDirectoryUrl.length)}`;
124
+ } else {
125
+ urlSite.url = "file:///mocked_for_snapshots";
126
+ }
127
+ }
128
+
129
+ const urlWithLineAndColumn = formatUrlWithLineAndColumn(urlSite);
130
+ return {
131
+ href: url.startsWith("file:") && openInEditor ? `javascript:window.fetch('/__open_in_editor__/${urlWithLineAndColumn}')` : urlSite.url,
132
+ text: urlWithLineAndColumn
133
+ };
134
+ }
135
+ });
136
+ return textWithHtmlLinks;
137
+ };
138
+
139
+ const formatErrorText = ({
140
+ message,
141
+ stack,
142
+ codeFrame
143
+ }) => {
144
+ let text;
145
+
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);
152
+ }
153
+
154
+ if (codeFrame) {
155
+ text += `\n\n${generateClickableText(codeFrame)}`;
156
+ }
157
+
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;
201
+ })();
202
+ }; // error.stack is more reliable than url/line/column reported on window error events
203
+ // so use it only when error.stack is not available
204
+
205
+
206
+ if (url && !stack && // ignore window.reportError() it gives no valuable info
207
+ !url.endsWith("html_supervisor_installer.js")) {
208
+ onErrorLocated(resolveUrlSite({
209
+ url,
210
+ line,
211
+ column
212
+ }));
213
+ } else if (errorMeta.url) {
214
+ onErrorLocated(resolveUrlSite(errorMeta));
65
215
  }
66
216
 
67
- return removeErrorOverlay;
217
+ return {
218
+ theme: error && error.cause && error.cause.code === "PARSE_ERROR" ? "light" : "dark",
219
+ title: "An error occured",
220
+ text: formatErrorText({
221
+ message,
222
+ stack
223
+ }),
224
+ tip: `${tip}
225
+ <br />
226
+ Click outside to close.`,
227
+ errorDetailsPromise: errorDetailsPromiseReference.current
228
+ };
68
229
  };
69
230
 
70
- const createErrorText = ({
71
- rootDirectoryUrl,
72
- openInEditor,
73
- message,
74
- stack
231
+ const extractErrorMeta = (error, {
232
+ line
75
233
  }) => {
76
- if (message && stack) {
77
- return `${replaceLinks(message, {
78
- rootDirectoryUrl,
79
- openInEditor
80
- })}\n${replaceLinks(stack, {
81
- rootDirectoryUrl,
82
- openInEditor
83
- })}`;
234
+ if (!error) {
235
+ return {};
84
236
  }
85
237
 
86
- if (stack) {
87
- return replaceLinks(stack, {
88
- rootDirectoryUrl,
89
- openInEditor
90
- });
238
+ const {
239
+ message
240
+ } = error;
241
+
242
+ if (!message) {
243
+ return {};
91
244
  }
92
245
 
93
- return replaceLinks(message, {
94
- rootDirectoryUrl,
95
- openInEditor
96
- });
97
- };
246
+ {
247
+ // chrome
248
+ if (message.includes("does not provide an export named")) {
249
+ return {
250
+ type: "dynamic_import_export_missing"
251
+ };
252
+ } // firefox
98
253
 
99
- class JsenvErrorOverlay extends HTMLElement {
100
- constructor({
101
- theme,
102
- title,
103
- text,
104
- tip
105
- }) {
106
- super();
107
- this.root = this.attachShadow({
108
- mode: "open"
109
- });
110
- this.root.innerHTML = overlayHtml;
111
254
 
112
- this.root.querySelector(".backdrop").onclick = () => {
113
- if (!this.parentNode) {
114
- // not in document anymore
115
- return;
116
- }
255
+ if (message.startsWith("import not found:")) {
256
+ return {
257
+ type: "dynamic_import_export_missing",
258
+ browser: "firefox"
259
+ };
260
+ } // safari
117
261
 
118
- this.root.querySelector(".backdrop").onclick = null;
119
- this.parentNode.removeChild(this);
120
- };
121
262
 
122
- this.root.querySelector(".overlay").setAttribute("data-theme", theme);
123
- this.root.querySelector(".title").innerHTML = title;
124
- this.root.querySelector(".text").innerHTML = text;
125
- this.root.querySelector(".tip").innerHTML = tip;
263
+ if (message.startsWith("import binding name")) {
264
+ return {
265
+ type: "dynamic_import_export_missing"
266
+ };
267
+ }
126
268
  }
127
269
 
128
- }
129
-
130
- if (customElements && !customElements.get(JSENV_ERROR_OVERLAY_TAGNAME)) {
131
- customElements.define(JSENV_ERROR_OVERLAY_TAGNAME, JsenvErrorOverlay);
132
- }
270
+ {
271
+ if (error.name === "SyntaxError" && typeof line === "number") {
272
+ return {
273
+ type: "dynamic_import_syntax_error"
274
+ };
275
+ }
276
+ }
133
277
 
134
- const overlayHtml = `
135
- <style>
136
- :host {
137
- position: fixed;
138
- z-index: 99999;
139
- top: 0;
140
- left: 0;
141
- width: 100%;
142
- height: 100%;
143
- overflow-y: scroll;
144
- margin: 0;
145
- background: rgba(0, 0, 0, 0.66);
146
- }
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
147
287
 
148
- .backdrop {
149
- position: absolute;
150
- left: 0;
151
- right: 0;
152
- top: 0;
153
- bottom: 0;
154
- }
155
288
 
156
- .overlay {
157
- position: relative;
158
- background: rgba(0, 0, 0, 0.95);
159
- width: 800px;
160
- margin: 30px auto;
161
- padding: 25px 40px;
162
- padding-top: 0;
163
- overflow: hidden; /* for h1 margins */
164
- border-radius: 4px 8px;
165
- box-shadow: 0 20px 40px rgb(0 0 0 / 30%), 0 15px 12px rgb(0 0 0 / 20%);
166
- box-sizing: border-box;
167
- font-family: monospace;
168
- direction: ltr;
169
- }
289
+ if (message === "error loading dynamically imported module") {
290
+ return {
291
+ type: "dynamic_import_fetch_error"
292
+ };
293
+ } // safari
170
294
 
171
- h1 {
172
- color: red;
173
- text-align: center;
174
- }
175
295
 
176
- pre {
177
- overflow: auto;
178
- max-width: 100%;
179
- /* padding is nice + prevents scrollbar from hiding the text behind it */
180
- /* does not work nicely on firefox though https://bugzilla.mozilla.org/show_bug.cgi?id=748518 */
181
- padding: 20px;
182
- }
183
-
184
- .tip {
185
- border-top: 1px solid #999;
186
- padding-top: 12px;
187
- }
296
+ if (message === "Importing a module script failed.") {
297
+ return {
298
+ type: "dynamic_import_fetch_error"
299
+ };
300
+ }
301
+ }
188
302
 
189
- [data-theme="dark"] {
190
- color: #999;
191
- }
192
- [data-theme="dark"] pre {
193
- background: #111;
194
- border: 1px solid #333;
195
- color: #eee;
196
- }
303
+ return {};
304
+ };
197
305
 
198
- [data-theme="light"] {
199
- color: #EEEEEE;
200
- }
201
- [data-theme="light"] pre {
202
- background: #1E1E1E;
203
- border: 1px solid white;
204
- color: #EEEEEE;
205
- }
306
+ const formatUrlWithLineAndColumn = ({
307
+ url,
308
+ line,
309
+ column
310
+ }) => {
311
+ return line === undefined && column === undefined ? url : column === undefined ? `${url}:${line}` : `${url}:${line}:${column}`;
312
+ };
206
313
 
207
- pre a {
208
- color: inherit;
209
- }
210
- </style>
211
- <div class="backdrop"></div>
212
- <div class="overlay">
213
- <h1 class="title"></h1>
214
- <pre class="text"></pre>
215
- <div class="tip"></div>
216
- </div>
217
- `;
218
-
219
- const parseErrorInfo = error => {
314
+ const normalizeErrorParts = error => {
220
315
  if (error === undefined) {
221
316
  return {
222
317
  message: "undefined"
@@ -294,56 +389,8 @@ const getErrorStackWithoutErrorMessage = error => {
294
389
  return stack;
295
390
  };
296
391
 
297
- const errorToHTML = (error, {
298
- url,
299
- line,
300
- column,
301
- reportedBy,
302
- requestedRessource
303
- }) => {
304
- let {
305
- message,
306
- stack
307
- } = parseErrorInfo(error);
308
-
309
- if (url) {
310
- if (!stack || error && error.name === "SyntaxError") {
311
- stack = ` at ${appendLineAndColumn(url, {
312
- line,
313
- column
314
- })}`;
315
- }
316
- }
317
-
318
- let tip = formatTip({
319
- reportedBy,
320
- requestedRessource
321
- });
322
- return {
323
- theme: error && error.cause && error.cause.code === "PARSE_ERROR" ? "light" : "dark",
324
- title: "An error occured",
325
- message,
326
- stack,
327
- tip: `${tip}
328
- <br />
329
- Click outside to close.`
330
- };
331
- };
332
-
333
- const formatTip = ({
334
- reportedBy,
335
- requestedRessource
336
- }) => {
337
- if (reportedBy === "browser") {
338
- return `Reported by the browser while executing <code>${window.location.pathname}${window.location.search}</code>.`;
339
- }
340
-
341
- return `Reported by the server while serving <code>${requestedRessource}</code>`;
342
- };
343
-
344
- const replaceLinks = (string, {
345
- rootDirectoryUrl,
346
- openInEditor
392
+ const makeLinksClickable = (string, {
393
+ createLink = url => url
347
394
  }) => {
348
395
  // normalize line breaks
349
396
  string = string.replace(/\n/g, "\n");
@@ -354,44 +401,16 @@ const replaceLinks = (string, {
354
401
  line,
355
402
  column
356
403
  }) => {
357
- const urlObject = new URL(url);
358
-
359
- const onFileUrl = fileUrlObject => {
360
- const atFsIndex = fileUrlObject.pathname.indexOf("/@fs/");
361
- let fileUrl;
362
-
363
- if (atFsIndex > -1) {
364
- const afterAtFs = fileUrlObject.pathname.slice(atFsIndex + "/@fs/".length);
365
- fileUrl = new URL(afterAtFs, "file:///").href;
366
- } else {
367
- fileUrl = fileUrlObject.href;
368
- }
369
-
370
- fileUrl = appendLineAndColumn(fileUrl, {
371
- line,
372
- column
373
- });
374
- return link({
375
- href: openInEditor ? `javascript:window.fetch('/__open_in_editor__/${fileUrl}')` : fileUrl,
376
- text: fileUrl
377
- });
378
- };
379
-
380
- if (urlObject.origin === window.origin) {
381
- const fileUrlObject = new URL(`${urlObject.pathname.slice(1)}${urlObject.search}`, rootDirectoryUrl);
382
- return onFileUrl(fileUrlObject);
383
- }
384
-
385
- if (urlObject.href.startsWith("file:")) {
386
- return onFileUrl(urlObject);
387
- }
388
-
404
+ const {
405
+ href,
406
+ text
407
+ } = createLink(url, {
408
+ line,
409
+ column
410
+ });
389
411
  return link({
390
- href: url,
391
- text: appendLineAndColumn(url, {
392
- line,
393
- column
394
- })
412
+ href,
413
+ text
395
414
  });
396
415
  }
397
416
  });
@@ -400,21 +419,6 @@ const replaceLinks = (string, {
400
419
 
401
420
  const escapeHtml = string => {
402
421
  return string.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
403
- };
404
-
405
- const appendLineAndColumn = (url, {
406
- line,
407
- column
408
- }) => {
409
- if (line !== undefined && column !== undefined) {
410
- return `${url}:${line}:${column}`;
411
- }
412
-
413
- if (line !== undefined) {
414
- return `${url}:${line}`;
415
- }
416
-
417
- return url;
418
422
  }; // `Error: yo
419
423
  // at Object.execute (http://127.0.0.1:57300/build/src/__test__/file-throw.js:9:13)
420
424
  // at doExec (http://127.0.0.1:3000/src/__test__/file-throw.js:452:38)
@@ -481,6 +485,218 @@ const link = ({
481
485
  text = href
482
486
  }) => `<a href="${href}">${text}</a>`;
483
487
 
488
+ const JSENV_ERROR_OVERLAY_TAGNAME = "jsenv-error-overlay";
489
+ const displayErrorInDocument = (error, {
490
+ rootDirectoryUrl,
491
+ errorBaseUrl,
492
+ openInEditor,
493
+ url,
494
+ line,
495
+ column,
496
+ codeFrame
497
+ }) => {
498
+ const {
499
+ theme,
500
+ title,
501
+ text,
502
+ tip,
503
+ errorDetailsPromise
504
+ } = formatError(error, {
505
+ rootDirectoryUrl,
506
+ errorBaseUrl,
507
+ openInEditor,
508
+ url,
509
+ line,
510
+ column,
511
+ codeFrame
512
+ });
513
+ let jsenvErrorOverlay = new JsenvErrorOverlay({
514
+ theme,
515
+ title,
516
+ text,
517
+ tip,
518
+ errorDetailsPromise
519
+ });
520
+ document.querySelectorAll(JSENV_ERROR_OVERLAY_TAGNAME).forEach(node => {
521
+ node.parentNode.removeChild(node);
522
+ });
523
+ document.body.appendChild(jsenvErrorOverlay);
524
+
525
+ const removeErrorOverlay = () => {
526
+ if (jsenvErrorOverlay && jsenvErrorOverlay.parentNode) {
527
+ document.body.removeChild(jsenvErrorOverlay);
528
+ jsenvErrorOverlay = null;
529
+ }
530
+ };
531
+
532
+ if (window.__reloader__) {
533
+ window.__reloader__.onstatuschange = () => {
534
+ if (window.__reloader__.status === "reloading") {
535
+ removeErrorOverlay();
536
+ }
537
+ };
538
+ }
539
+
540
+ return removeErrorOverlay;
541
+ };
542
+
543
+ class JsenvErrorOverlay extends HTMLElement {
544
+ constructor({
545
+ theme,
546
+ title,
547
+ text,
548
+ tip,
549
+ errorDetailsPromise
550
+ }) {
551
+ super();
552
+ this.root = this.attachShadow({
553
+ mode: "open"
554
+ });
555
+ this.root.innerHTML = `
556
+ <style>
557
+ ${overlayCSS}
558
+ </style>
559
+ <div class="backdrop"></div>
560
+ <div class="overlay" data-theme=${theme}>
561
+ <h1 class="title">
562
+ ${title}
563
+ </h1>
564
+ <pre class="text">${text}</pre>
565
+ <div class="tip">
566
+ ${tip}
567
+ </div>
568
+ </div>`;
569
+
570
+ this.root.querySelector(".backdrop").onclick = () => {
571
+ if (!this.parentNode) {
572
+ // not in document anymore
573
+ return;
574
+ }
575
+
576
+ this.root.querySelector(".backdrop").onclick = null;
577
+ this.parentNode.removeChild(this);
578
+ };
579
+
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) {
592
+ this.root.querySelector(".text").innerHTML += `\n\n${codeFrame}`;
593
+ }
594
+
595
+ if (cause) {
596
+ const causeIndented = prefixRemainingLines(cause, " ");
597
+ this.root.querySelector(".text").innerHTML += `\n [cause]: ${causeIndented}`;
598
+ }
599
+ });
600
+ }
601
+ }
602
+
603
+ }
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
+
620
+ if (customElements && !customElements.get(JSENV_ERROR_OVERLAY_TAGNAME)) {
621
+ customElements.define(JSENV_ERROR_OVERLAY_TAGNAME, JsenvErrorOverlay);
622
+ }
623
+
624
+ const overlayCSS = `
625
+ :host {
626
+ position: fixed;
627
+ z-index: 99999;
628
+ top: 0;
629
+ left: 0;
630
+ width: 100%;
631
+ height: 100%;
632
+ overflow-y: scroll;
633
+ margin: 0;
634
+ background: rgba(0, 0, 0, 0.66);
635
+ }
636
+
637
+ .backdrop {
638
+ position: absolute;
639
+ left: 0;
640
+ right: 0;
641
+ top: 0;
642
+ bottom: 0;
643
+ }
644
+
645
+ .overlay {
646
+ position: relative;
647
+ background: rgba(0, 0, 0, 0.95);
648
+ width: 800px;
649
+ margin: 30px auto;
650
+ padding: 25px 40px;
651
+ padding-top: 0;
652
+ overflow: hidden; /* for h1 margins */
653
+ border-radius: 4px 8px;
654
+ box-shadow: 0 20px 40px rgb(0 0 0 / 30%), 0 15px 12px rgb(0 0 0 / 20%);
655
+ box-sizing: border-box;
656
+ font-family: monospace;
657
+ direction: ltr;
658
+ }
659
+
660
+ h1 {
661
+ color: red;
662
+ text-align: center;
663
+ }
664
+
665
+ pre {
666
+ overflow: auto;
667
+ max-width: 100%;
668
+ /* padding is nice + prevents scrollbar from hiding the text behind it */
669
+ /* does not work nicely on firefox though https://bugzilla.mozilla.org/show_bug.cgi?id=748518 */
670
+ padding: 20px;
671
+ }
672
+
673
+ .tip {
674
+ border-top: 1px solid #999;
675
+ padding-top: 12px;
676
+ }
677
+
678
+ [data-theme="dark"] {
679
+ color: #999;
680
+ }
681
+ [data-theme="dark"] pre {
682
+ background: #111;
683
+ border: 1px solid #333;
684
+ color: #eee;
685
+ }
686
+
687
+ [data-theme="light"] {
688
+ color: #EEEEEE;
689
+ }
690
+ [data-theme="light"] pre {
691
+ background: #1E1E1E;
692
+ border: 1px solid white;
693
+ color: #EEEEEE;
694
+ }
695
+
696
+ pre a {
697
+ color: inherit;
698
+ }`;
699
+
484
700
  const {
485
701
  Notification
486
702
  } = window;
@@ -514,6 +730,7 @@ const installHtmlSupervisor = ({
514
730
  logs,
515
731
  measurePerf,
516
732
  errorOverlay,
733
+ errorBaseUrl,
517
734
  openInEditor
518
735
  }) => {
519
736
 
@@ -601,14 +818,18 @@ const installHtmlSupervisor = ({
601
818
  let completed;
602
819
  let result;
603
820
  let error;
821
+ const urlObject = new URL(src, window.location);
604
822
 
605
- try {
606
- const urlObject = new URL(src, window.location);
823
+ if (reload) {
824
+ urlObject.searchParams.set("hmr", Date.now());
825
+ }
607
826
 
608
- if (reload) {
609
- urlObject.searchParams.set("hmr", Date.now());
610
- }
827
+ __html_supervisor__.currentExecution = {
828
+ type: type === "module" ? "dynamic_import" : "script_injection",
829
+ url: urlObject.href
830
+ };
611
831
 
832
+ try {
612
833
  result = await execute(urlObject.href);
613
834
  completed = true;
614
835
  } catch (e) {
@@ -629,6 +850,7 @@ const installHtmlSupervisor = ({
629
850
  console.groupEnd();
630
851
  }
631
852
 
853
+ __html_supervisor__.currentExecution = null;
632
854
  return;
633
855
  }
634
856
 
@@ -656,6 +878,8 @@ const installHtmlSupervisor = ({
656
878
  if (logs) {
657
879
  console.groupEnd();
658
880
  }
881
+
882
+ __html_supervisor__.currentExecution = null;
659
883
  };
660
884
 
661
885
  const classicExecutionQueue = createExecutionQueue(performExecution);
@@ -744,6 +968,21 @@ const installHtmlSupervisor = ({
744
968
  });
745
969
 
746
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
+
747
986
  window.addEventListener("error", errorEvent => {
748
987
  if (!errorEvent.isTrusted) {
749
988
  // ignore custom error event (not sent by browser)
@@ -751,76 +990,17 @@ const installHtmlSupervisor = ({
751
990
  }
752
991
 
753
992
  const {
754
- error
993
+ error,
994
+ filename,
995
+ lineno,
996
+ colno
755
997
  } = errorEvent;
756
- displayErrorInDocument(error, {
757
- rootDirectoryUrl,
758
- openInEditor,
759
- url: errorEvent.filename,
760
- line: errorEvent.lineno,
761
- column: errorEvent.colno,
762
- reportedBy: "browser"
998
+ onErrorReportedByBrowser(error, {
999
+ url: filename,
1000
+ line: lineno,
1001
+ column: colno
763
1002
  });
764
1003
  });
765
-
766
- if (window.__server_events__) {
767
- const isExecuting = () => {
768
- if (pendingExecutionCount > 0) {
769
- return true;
770
- }
771
-
772
- if (document.readyState === "loading" || document.readyState === "interactive") {
773
- return true;
774
- }
775
-
776
- if (window.__reloader__ && window.__reloader__.status === "reloading") {
777
- return true;
778
- }
779
-
780
- return false;
781
- };
782
-
783
- window.__server_events__.addEventCallbacks({
784
- error_while_serving_file: serverErrorEvent => {
785
- if (!isExecuting()) {
786
- return;
787
- }
788
-
789
- const {
790
- message,
791
- stack,
792
- traceUrl,
793
- traceLine,
794
- traceColumn,
795
- traceMessage,
796
- requestedRessource,
797
- isFaviconAutoRequest
798
- } = JSON.parse(serverErrorEvent.data);
799
-
800
- if (isFaviconAutoRequest) {
801
- return;
802
- } // setTimeout is to ensure the error
803
- // dispatched on window by browser is displayed first,
804
- // then the server error replaces it (because it contains more information)
805
-
806
-
807
- setTimeout(() => {
808
- displayErrorInDocument({
809
- message,
810
- stack: stack && traceMessage ? `${stack}\n\n${traceMessage}` : stack ? stack : traceMessage ? `\n${traceMessage}` : ""
811
- }, {
812
- rootDirectoryUrl,
813
- openInEditor,
814
- url: traceUrl,
815
- line: traceLine,
816
- column: traceColumn,
817
- reportedBy: "server",
818
- requestedRessource
819
- });
820
- }, 10);
821
- }
822
- });
823
- }
824
1004
  }
825
1005
  };
826
1006