@jsenv/core 27.5.0 → 27.5.3

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,170 @@ 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
19
  column,
20
- reportedBy,
21
- requestedRessource
20
+ codeFrame,
21
+ requestedRessource,
22
+ reportedBy
22
23
  }) => {
23
- document.querySelectorAll(JSENV_ERROR_OVERLAY_TAGNAME).forEach(node => {
24
- node.parentNode.removeChild(node);
25
- });
26
- const {
27
- theme,
28
- title,
24
+ let {
29
25
  message,
30
- stack,
31
- tip
32
- } = errorToHTML(error, {
33
- url,
34
- line,
35
- column,
26
+ stack
27
+ } = normalizeErrorParts(error);
28
+ let codeFramePromiseReference = {
29
+ current: null
30
+ };
31
+ let tip = formatTip({
36
32
  reportedBy,
37
33
  requestedRessource
38
34
  });
39
- let jsenvErrorOverlay = new JsenvErrorOverlay({
40
- theme,
41
- title,
42
- text: createErrorText({
43
- rootDirectoryUrl,
44
- openInEditor,
45
- message,
46
- stack
47
- }),
48
- tip
49
- });
50
- document.body.appendChild(jsenvErrorOverlay);
35
+ let errorUrlSite;
51
36
 
52
- const removeErrorOverlay = () => {
53
- if (jsenvErrorOverlay && jsenvErrorOverlay.parentNode) {
54
- document.body.removeChild(jsenvErrorOverlay);
55
- jsenvErrorOverlay = null;
37
+ const resolveUrlSite = ({
38
+ url,
39
+ line,
40
+ column
41
+ }) => {
42
+ const inlineUrlMatch = url.match(/@L([0-9]+)\-L([0-9]+)\.[\w]+$/);
43
+
44
+ if (inlineUrlMatch) {
45
+ const htmlUrl = url.slice(0, inlineUrlMatch.index);
46
+ const tagLine = parseInt(inlineUrlMatch[1]);
47
+ const tagColumn = parseInt(inlineUrlMatch[2]);
48
+ url = htmlUrl;
49
+ line = tagLine + parseInt(line) - 1;
50
+ column = tagColumn + parseInt(column);
56
51
  }
57
- };
58
52
 
59
- if (window.__reloader__) {
60
- window.__reloader__.onstatuschange = () => {
61
- if (window.__reloader__.status === "reloading") {
62
- removeErrorOverlay();
63
- }
64
- };
65
- }
53
+ let urlObject = new URL(url);
66
54
 
67
- return removeErrorOverlay;
68
- };
55
+ if (urlObject.origin === window.origin) {
56
+ urlObject = new URL(`${urlObject.pathname.slice(1)}${urlObject.search}`, rootDirectoryUrl);
57
+ }
69
58
 
70
- const createErrorText = ({
71
- rootDirectoryUrl,
72
- openInEditor,
73
- message,
74
- stack
75
- }) => {
76
- if (message && stack) {
77
- return `${replaceLinks(message, {
78
- rootDirectoryUrl,
79
- openInEditor
80
- })}\n${replaceLinks(stack, {
81
- rootDirectoryUrl,
82
- openInEditor
83
- })}`;
84
- }
59
+ if (urlObject.href.startsWith("file:")) {
60
+ const atFsIndex = urlObject.pathname.indexOf("/@fs/");
85
61
 
86
- if (stack) {
87
- return replaceLinks(stack, {
88
- rootDirectoryUrl,
89
- openInEditor
90
- });
91
- }
62
+ if (atFsIndex > -1) {
63
+ const afterAtFs = urlObject.pathname.slice(atFsIndex + "/@fs/".length);
64
+ url = new URL(afterAtFs, "file:///").href;
65
+ } else {
66
+ url = urlObject.href;
67
+ }
68
+ } else {
69
+ url = urlObject.href;
70
+ }
92
71
 
93
- return replaceLinks(message, {
94
- rootDirectoryUrl,
95
- openInEditor
96
- });
97
- };
72
+ return {
73
+ url,
74
+ line,
75
+ column
76
+ };
77
+ };
98
78
 
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;
79
+ const generateClickableText = text => {
80
+ const textWithHtmlLinks = makeLinksClickable(text, {
81
+ createLink: (url, {
82
+ line,
83
+ column
84
+ }) => {
85
+ const urlSite = resolveUrlSite({
86
+ url,
87
+ line,
88
+ column
89
+ });
111
90
 
112
- this.root.querySelector(".backdrop").onclick = () => {
113
- if (!this.parentNode) {
114
- // not in document anymore
115
- return;
116
- }
91
+ if (!errorUrlSite && text === stack) {
92
+ onErrorLocated(urlSite);
93
+ }
117
94
 
118
- this.root.querySelector(".backdrop").onclick = null;
119
- this.parentNode.removeChild(this);
120
- };
95
+ if (errorBaseUrl) {
96
+ if (urlSite.url.startsWith(rootDirectoryUrl)) {
97
+ urlSite.url = `${errorBaseUrl}${urlSite.url.slice(rootDirectoryUrl.length)}`;
98
+ } else {
99
+ urlSite.url = "file:///mocked_for_snapshots";
100
+ }
101
+ }
121
102
 
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;
126
- }
103
+ const urlWithLineAndColumn = formatUrlWithLineAndColumn(urlSite);
104
+ return {
105
+ href: url.startsWith("file:") && openInEditor ? `javascript:window.fetch('/__open_in_editor__/${urlWithLineAndColumn}')` : urlSite.url,
106
+ text: urlWithLineAndColumn
107
+ };
108
+ }
109
+ });
110
+ return textWithHtmlLinks;
111
+ };
127
112
 
128
- }
113
+ const onErrorLocated = urlSite => {
114
+ errorUrlSite = urlSite;
129
115
 
130
- if (customElements && !customElements.get(JSENV_ERROR_OVERLAY_TAGNAME)) {
131
- customElements.define(JSENV_ERROR_OVERLAY_TAGNAME, JsenvErrorOverlay);
132
- }
116
+ if (codeFrame) {
117
+ return;
118
+ }
133
119
 
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
- }
120
+ if (reportedBy !== "browser") {
121
+ return;
122
+ }
147
123
 
148
- .backdrop {
149
- position: absolute;
150
- left: 0;
151
- right: 0;
152
- top: 0;
153
- bottom: 0;
154
- }
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;
129
+ })();
130
+ }; // error.stack is more reliable than url/line/column reported on window error events
131
+ // so use it only when error.stack is not available
155
132
 
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
- }
170
133
 
171
- h1 {
172
- color: red;
173
- text-align: center;
174
- }
134
+ if (url && !stack && // ignore window.reportError() it gives no valuable info
135
+ !url.endsWith("html_supervisor_installer.js")) {
136
+ onErrorLocated(resolveUrlSite({
137
+ url,
138
+ line,
139
+ column
140
+ }));
141
+ }
175
142
 
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
- }
143
+ let text;
183
144
 
184
- .tip {
185
- border-top: 1px solid #999;
186
- padding-top: 12px;
187
- }
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
+ }
188
152
 
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
- }
153
+ if (codeFrame) {
154
+ text += `\n\n${generateClickableText(codeFrame)}`;
155
+ }
197
156
 
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
- }
157
+ return {
158
+ theme: error && error.cause && error.cause.code === "PARSE_ERROR" ? "light" : "dark",
159
+ title: "An error occured",
160
+ text,
161
+ codeFramePromise: codeFramePromiseReference.current,
162
+ tip: `${tip}
163
+ <br />
164
+ Click outside to close.`
165
+ };
166
+ };
206
167
 
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 => {
168
+ const formatUrlWithLineAndColumn = ({
169
+ url,
170
+ line,
171
+ column
172
+ }) => {
173
+ return line === undefined && column === undefined ? url : column === undefined ? `${url}:${line}` : `${url}:${line}:${column}`;
174
+ };
175
+
176
+ const normalizeErrorParts = error => {
220
177
  if (error === undefined) {
221
178
  return {
222
179
  message: "undefined"
@@ -294,42 +251,6 @@ const getErrorStackWithoutErrorMessage = error => {
294
251
  return stack;
295
252
  };
296
253
 
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
254
  const formatTip = ({
334
255
  reportedBy,
335
256
  requestedRessource
@@ -341,9 +262,8 @@ const formatTip = ({
341
262
  return `Reported by the server while serving <code>${requestedRessource}</code>`;
342
263
  };
343
264
 
344
- const replaceLinks = (string, {
345
- rootDirectoryUrl,
346
- openInEditor
265
+ const makeLinksClickable = (string, {
266
+ createLink = url => url
347
267
  }) => {
348
268
  // normalize line breaks
349
269
  string = string.replace(/\n/g, "\n");
@@ -354,44 +274,16 @@ const replaceLinks = (string, {
354
274
  line,
355
275
  column
356
276
  }) => {
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
-
277
+ const {
278
+ href,
279
+ text
280
+ } = createLink(url, {
281
+ line,
282
+ column
283
+ });
389
284
  return link({
390
- href: url,
391
- text: appendLineAndColumn(url, {
392
- line,
393
- column
394
- })
285
+ href,
286
+ text
395
287
  });
396
288
  }
397
289
  });
@@ -400,21 +292,6 @@ const replaceLinks = (string, {
400
292
 
401
293
  const escapeHtml = string => {
402
294
  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
295
  }; // `Error: yo
419
296
  // at Object.execute (http://127.0.0.1:57300/build/src/__test__/file-throw.js:9:13)
420
297
  // at doExec (http://127.0.0.1:3000/src/__test__/file-throw.js:452:38)
@@ -481,6 +358,210 @@ const link = ({
481
358
  text = href
482
359
  }) => `<a href="${href}">${text}</a>`;
483
360
 
361
+ const JSENV_ERROR_OVERLAY_TAGNAME = "jsenv-error-overlay";
362
+ let previousErrorInfo = null;
363
+ const displayErrorInDocument = (error, {
364
+ rootDirectoryUrl,
365
+ errorBaseUrl,
366
+ openInEditor,
367
+ url,
368
+ line,
369
+ column,
370
+ codeFrame,
371
+ reportedBy,
372
+ requestedRessource
373
+ }) => {
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
+ const {
391
+ theme,
392
+ title,
393
+ text,
394
+ codeFramePromise,
395
+ tip
396
+ } = formatError(error, {
397
+ rootDirectoryUrl,
398
+ errorBaseUrl,
399
+ openInEditor,
400
+ url,
401
+ line,
402
+ column,
403
+ codeFrame,
404
+ reportedBy,
405
+ requestedRessource
406
+ });
407
+ let jsenvErrorOverlay = new JsenvErrorOverlay({
408
+ theme,
409
+ title,
410
+ text,
411
+ codeFramePromise,
412
+ tip
413
+ });
414
+ document.querySelectorAll(JSENV_ERROR_OVERLAY_TAGNAME).forEach(node => {
415
+ node.parentNode.removeChild(node);
416
+ });
417
+ document.body.appendChild(jsenvErrorOverlay);
418
+
419
+ const removeErrorOverlay = () => {
420
+ if (jsenvErrorOverlay && jsenvErrorOverlay.parentNode) {
421
+ document.body.removeChild(jsenvErrorOverlay);
422
+ jsenvErrorOverlay = null;
423
+ }
424
+ };
425
+
426
+ if (window.__reloader__) {
427
+ window.__reloader__.onstatuschange = () => {
428
+ if (window.__reloader__.status === "reloading") {
429
+ removeErrorOverlay();
430
+ }
431
+ };
432
+ }
433
+
434
+ return removeErrorOverlay;
435
+ };
436
+
437
+ class JsenvErrorOverlay extends HTMLElement {
438
+ constructor({
439
+ theme,
440
+ title,
441
+ text,
442
+ codeFramePromise,
443
+ tip
444
+ }) {
445
+ super();
446
+ this.root = this.attachShadow({
447
+ mode: "open"
448
+ });
449
+ this.root.innerHTML = `
450
+ <style>
451
+ ${overlayCSS}
452
+ </style>
453
+ <div class="backdrop"></div>
454
+ <div class="overlay" data-theme=${theme}>
455
+ <h1 class="title">
456
+ ${title}
457
+ </h1>
458
+ <pre class="text">${text}</pre>
459
+ <div class="tip">
460
+ ${tip}
461
+ </div>
462
+ </div>`;
463
+
464
+ this.root.querySelector(".backdrop").onclick = () => {
465
+ if (!this.parentNode) {
466
+ // not in document anymore
467
+ return;
468
+ }
469
+
470
+ this.root.querySelector(".backdrop").onclick = null;
471
+ this.parentNode.removeChild(this);
472
+ };
473
+
474
+ if (codeFramePromise) {
475
+ codeFramePromise.then(codeFrame => {
476
+ if (this.parentNode) {
477
+ this.root.querySelector(".text").innerHTML += `\n\n${codeFrame}`;
478
+ }
479
+ });
480
+ }
481
+ }
482
+
483
+ }
484
+
485
+ if (customElements && !customElements.get(JSENV_ERROR_OVERLAY_TAGNAME)) {
486
+ customElements.define(JSENV_ERROR_OVERLAY_TAGNAME, JsenvErrorOverlay);
487
+ }
488
+
489
+ const overlayCSS = `
490
+ :host {
491
+ position: fixed;
492
+ z-index: 99999;
493
+ top: 0;
494
+ left: 0;
495
+ width: 100%;
496
+ height: 100%;
497
+ overflow-y: scroll;
498
+ margin: 0;
499
+ background: rgba(0, 0, 0, 0.66);
500
+ }
501
+
502
+ .backdrop {
503
+ position: absolute;
504
+ left: 0;
505
+ right: 0;
506
+ top: 0;
507
+ bottom: 0;
508
+ }
509
+
510
+ .overlay {
511
+ position: relative;
512
+ background: rgba(0, 0, 0, 0.95);
513
+ width: 800px;
514
+ margin: 30px auto;
515
+ padding: 25px 40px;
516
+ padding-top: 0;
517
+ overflow: hidden; /* for h1 margins */
518
+ border-radius: 4px 8px;
519
+ box-shadow: 0 20px 40px rgb(0 0 0 / 30%), 0 15px 12px rgb(0 0 0 / 20%);
520
+ box-sizing: border-box;
521
+ font-family: monospace;
522
+ direction: ltr;
523
+ }
524
+
525
+ h1 {
526
+ color: red;
527
+ text-align: center;
528
+ }
529
+
530
+ pre {
531
+ overflow: auto;
532
+ max-width: 100%;
533
+ /* padding is nice + prevents scrollbar from hiding the text behind it */
534
+ /* does not work nicely on firefox though https://bugzilla.mozilla.org/show_bug.cgi?id=748518 */
535
+ padding: 20px;
536
+ }
537
+
538
+ .tip {
539
+ border-top: 1px solid #999;
540
+ padding-top: 12px;
541
+ }
542
+
543
+ [data-theme="dark"] {
544
+ color: #999;
545
+ }
546
+ [data-theme="dark"] pre {
547
+ background: #111;
548
+ border: 1px solid #333;
549
+ color: #eee;
550
+ }
551
+
552
+ [data-theme="light"] {
553
+ color: #EEEEEE;
554
+ }
555
+ [data-theme="light"] pre {
556
+ background: #1E1E1E;
557
+ border: 1px solid white;
558
+ color: #EEEEEE;
559
+ }
560
+
561
+ pre a {
562
+ color: inherit;
563
+ }`;
564
+
484
565
  const {
485
566
  Notification
486
567
  } = window;
@@ -514,6 +595,7 @@ const installHtmlSupervisor = ({
514
595
  logs,
515
596
  measurePerf,
516
597
  errorOverlay,
598
+ errorBaseUrl,
517
599
  openInEditor
518
600
  }) => {
519
601
 
@@ -751,14 +833,18 @@ const installHtmlSupervisor = ({
751
833
  }
752
834
 
753
835
  const {
754
- error
836
+ error,
837
+ filename,
838
+ lineno,
839
+ colno
755
840
  } = errorEvent;
756
841
  displayErrorInDocument(error, {
757
842
  rootDirectoryUrl,
843
+ errorBaseUrl,
758
844
  openInEditor,
759
- url: errorEvent.filename,
760
- line: errorEvent.lineno,
761
- column: errorEvent.colno,
845
+ url: filename,
846
+ line: lineno,
847
+ column: colno,
762
848
  reportedBy: "browser"
763
849
  });
764
850
  });
@@ -799,25 +885,22 @@ const installHtmlSupervisor = ({
799
885
 
800
886
  if (isFaviconAutoRequest) {
801
887
  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);
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
+ });
821
904
  }
822
905
  });
823
906
  }