@jsenv/core 27.5.1 → 27.5.2

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,169 @@ 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) {
135
+ onErrorLocated(resolveUrlSite({
136
+ url,
137
+ line,
138
+ column
139
+ }));
140
+ }
175
141
 
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
- }
142
+ let text;
183
143
 
184
- .tip {
185
- border-top: 1px solid #999;
186
- padding-top: 12px;
187
- }
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
+ }
188
151
 
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
- }
152
+ if (codeFrame) {
153
+ text += `\n\n${generateClickableText(codeFrame)}`;
154
+ }
197
155
 
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
- }
156
+ return {
157
+ theme: error && error.cause && error.cause.code === "PARSE_ERROR" ? "light" : "dark",
158
+ title: "An error occured",
159
+ text,
160
+ codeFramePromise: codeFramePromiseReference.current,
161
+ tip: `${tip}
162
+ <br />
163
+ Click outside to close.`
164
+ };
165
+ };
206
166
 
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 => {
167
+ const formatUrlWithLineAndColumn = ({
168
+ url,
169
+ line,
170
+ column
171
+ }) => {
172
+ return line === undefined && column === undefined ? url : column === undefined ? `${url}:${line}` : `${url}:${line}:${column}`;
173
+ };
174
+
175
+ const normalizeErrorParts = error => {
220
176
  if (error === undefined) {
221
177
  return {
222
178
  message: "undefined"
@@ -294,42 +250,6 @@ const getErrorStackWithoutErrorMessage = error => {
294
250
  return stack;
295
251
  };
296
252
 
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
253
  const formatTip = ({
334
254
  reportedBy,
335
255
  requestedRessource
@@ -341,9 +261,8 @@ const formatTip = ({
341
261
  return `Reported by the server while serving <code>${requestedRessource}</code>`;
342
262
  };
343
263
 
344
- const replaceLinks = (string, {
345
- rootDirectoryUrl,
346
- openInEditor
264
+ const makeLinksClickable = (string, {
265
+ createLink = url => url
347
266
  }) => {
348
267
  // normalize line breaks
349
268
  string = string.replace(/\n/g, "\n");
@@ -354,44 +273,16 @@ const replaceLinks = (string, {
354
273
  line,
355
274
  column
356
275
  }) => {
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
-
276
+ const {
277
+ href,
278
+ text
279
+ } = createLink(url, {
280
+ line,
281
+ column
282
+ });
389
283
  return link({
390
- href: url,
391
- text: appendLineAndColumn(url, {
392
- line,
393
- column
394
- })
284
+ href,
285
+ text
395
286
  });
396
287
  }
397
288
  });
@@ -400,21 +291,6 @@ const replaceLinks = (string, {
400
291
 
401
292
  const escapeHtml = string => {
402
293
  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
294
  }; // `Error: yo
419
295
  // at Object.execute (http://127.0.0.1:57300/build/src/__test__/file-throw.js:9:13)
420
296
  // at doExec (http://127.0.0.1:3000/src/__test__/file-throw.js:452:38)
@@ -481,6 +357,193 @@ const link = ({
481
357
  text = href
482
358
  }) => `<a href="${href}">${text}</a>`;
483
359
 
360
+ const JSENV_ERROR_OVERLAY_TAGNAME = "jsenv-error-overlay";
361
+ const displayErrorInDocument = (error, {
362
+ rootDirectoryUrl,
363
+ errorBaseUrl,
364
+ openInEditor,
365
+ url,
366
+ line,
367
+ column,
368
+ codeFrame,
369
+ reportedBy,
370
+ requestedRessource
371
+ }) => {
372
+ const {
373
+ theme,
374
+ title,
375
+ text,
376
+ codeFramePromise,
377
+ tip
378
+ } = formatError(error, {
379
+ rootDirectoryUrl,
380
+ errorBaseUrl,
381
+ openInEditor,
382
+ url,
383
+ line,
384
+ column,
385
+ codeFrame,
386
+ reportedBy,
387
+ requestedRessource
388
+ });
389
+ let jsenvErrorOverlay = new JsenvErrorOverlay({
390
+ theme,
391
+ title,
392
+ text,
393
+ codeFramePromise,
394
+ tip
395
+ });
396
+ document.querySelectorAll(JSENV_ERROR_OVERLAY_TAGNAME).forEach(node => {
397
+ node.parentNode.removeChild(node);
398
+ });
399
+ document.body.appendChild(jsenvErrorOverlay);
400
+
401
+ const removeErrorOverlay = () => {
402
+ if (jsenvErrorOverlay && jsenvErrorOverlay.parentNode) {
403
+ document.body.removeChild(jsenvErrorOverlay);
404
+ jsenvErrorOverlay = null;
405
+ }
406
+ };
407
+
408
+ if (window.__reloader__) {
409
+ window.__reloader__.onstatuschange = () => {
410
+ if (window.__reloader__.status === "reloading") {
411
+ removeErrorOverlay();
412
+ }
413
+ };
414
+ }
415
+
416
+ return removeErrorOverlay;
417
+ };
418
+
419
+ class JsenvErrorOverlay extends HTMLElement {
420
+ constructor({
421
+ theme,
422
+ title,
423
+ text,
424
+ codeFramePromise,
425
+ tip
426
+ }) {
427
+ super();
428
+ this.root = this.attachShadow({
429
+ mode: "open"
430
+ });
431
+ this.root.innerHTML = `
432
+ <style>
433
+ ${overlayCSS}
434
+ </style>
435
+ <div class="backdrop"></div>
436
+ <div class="overlay" data-theme=${theme}>
437
+ <h1 class="title">
438
+ ${title}
439
+ </h1>
440
+ <pre class="text">${text}</pre>
441
+ <div class="tip">
442
+ ${tip}
443
+ </div>
444
+ </div>`;
445
+
446
+ this.root.querySelector(".backdrop").onclick = () => {
447
+ if (!this.parentNode) {
448
+ // not in document anymore
449
+ return;
450
+ }
451
+
452
+ this.root.querySelector(".backdrop").onclick = null;
453
+ this.parentNode.removeChild(this);
454
+ };
455
+
456
+ if (codeFramePromise) {
457
+ codeFramePromise.then(codeFrame => {
458
+ if (this.parentNode) {
459
+ this.root.querySelector(".text").innerHTML += `\n\n${codeFrame}`;
460
+ }
461
+ });
462
+ }
463
+ }
464
+
465
+ }
466
+
467
+ if (customElements && !customElements.get(JSENV_ERROR_OVERLAY_TAGNAME)) {
468
+ customElements.define(JSENV_ERROR_OVERLAY_TAGNAME, JsenvErrorOverlay);
469
+ }
470
+
471
+ const overlayCSS = `
472
+ :host {
473
+ position: fixed;
474
+ z-index: 99999;
475
+ top: 0;
476
+ left: 0;
477
+ width: 100%;
478
+ height: 100%;
479
+ overflow-y: scroll;
480
+ margin: 0;
481
+ background: rgba(0, 0, 0, 0.66);
482
+ }
483
+
484
+ .backdrop {
485
+ position: absolute;
486
+ left: 0;
487
+ right: 0;
488
+ top: 0;
489
+ bottom: 0;
490
+ }
491
+
492
+ .overlay {
493
+ position: relative;
494
+ background: rgba(0, 0, 0, 0.95);
495
+ width: 800px;
496
+ margin: 30px auto;
497
+ padding: 25px 40px;
498
+ padding-top: 0;
499
+ overflow: hidden; /* for h1 margins */
500
+ border-radius: 4px 8px;
501
+ box-shadow: 0 20px 40px rgb(0 0 0 / 30%), 0 15px 12px rgb(0 0 0 / 20%);
502
+ box-sizing: border-box;
503
+ font-family: monospace;
504
+ direction: ltr;
505
+ }
506
+
507
+ h1 {
508
+ color: red;
509
+ text-align: center;
510
+ }
511
+
512
+ pre {
513
+ overflow: auto;
514
+ max-width: 100%;
515
+ /* padding is nice + prevents scrollbar from hiding the text behind it */
516
+ /* does not work nicely on firefox though https://bugzilla.mozilla.org/show_bug.cgi?id=748518 */
517
+ padding: 20px;
518
+ }
519
+
520
+ .tip {
521
+ border-top: 1px solid #999;
522
+ padding-top: 12px;
523
+ }
524
+
525
+ [data-theme="dark"] {
526
+ color: #999;
527
+ }
528
+ [data-theme="dark"] pre {
529
+ background: #111;
530
+ border: 1px solid #333;
531
+ color: #eee;
532
+ }
533
+
534
+ [data-theme="light"] {
535
+ color: #EEEEEE;
536
+ }
537
+ [data-theme="light"] pre {
538
+ background: #1E1E1E;
539
+ border: 1px solid white;
540
+ color: #EEEEEE;
541
+ }
542
+
543
+ pre a {
544
+ color: inherit;
545
+ }`;
546
+
484
547
  const {
485
548
  Notification
486
549
  } = window;
@@ -514,6 +577,7 @@ const installHtmlSupervisor = ({
514
577
  logs,
515
578
  measurePerf,
516
579
  errorOverlay,
580
+ errorBaseUrl,
517
581
  openInEditor
518
582
  }) => {
519
583
 
@@ -755,6 +819,7 @@ const installHtmlSupervisor = ({
755
819
  } = errorEvent;
756
820
  displayErrorInDocument(error, {
757
821
  rootDirectoryUrl,
822
+ errorBaseUrl,
758
823
  openInEditor,
759
824
  url: errorEvent.filename,
760
825
  line: errorEvent.lineno,
@@ -807,13 +872,15 @@ const installHtmlSupervisor = ({
807
872
  setTimeout(() => {
808
873
  displayErrorInDocument({
809
874
  message,
810
- stack: stack && traceMessage ? `${stack}\n\n${traceMessage}` : stack ? stack : traceMessage ? `\n${traceMessage}` : ""
875
+ stack
811
876
  }, {
812
877
  rootDirectoryUrl,
878
+ errorBaseUrl,
813
879
  openInEditor,
814
880
  url: traceUrl,
815
881
  line: traceLine,
816
882
  column: traceColumn,
883
+ codeFrame: traceMessage,
817
884
  reportedBy: "server",
818
885
  requestedRessource
819
886
  });