@jsenv/core 28.0.1 → 28.1.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.
Files changed (57) hide show
  1. package/dist/controllable_child_process.mjs +1 -2
  2. package/dist/controllable_worker_thread.mjs +1 -2
  3. package/dist/js/autoreload.js +25 -9
  4. package/dist/js/execute_using_dynamic_import.js +804 -1
  5. package/dist/js/script_type_module_supervisor.js +122 -0
  6. package/dist/js/supervisor.js +933 -0
  7. package/dist/js/{wrapper.mjs → ws.js} +0 -0
  8. package/dist/main.js +452 -504
  9. package/package.json +13 -13
  10. package/readme.md +1 -1
  11. package/src/build/inject_global_version_mappings.js +3 -3
  12. package/src/dev/start_dev_server.js +2 -2
  13. package/src/execute/execute.js +1 -1
  14. package/src/execute/run.js +26 -38
  15. package/src/execute/runtimes/browsers/from_playwright.js +51 -77
  16. package/src/execute/runtimes/node/node_child_process.js +36 -36
  17. package/src/execute/runtimes/node/node_worker_thread.js +36 -36
  18. package/src/omega/kitchen.js +28 -9
  19. package/src/omega/omega_server.js +2 -2
  20. package/src/omega/server/file_service.js +2 -2
  21. package/src/omega/url_graph/url_info_transformations.js +8 -1
  22. package/src/plugins/autoreload/client/reload.js +20 -7
  23. package/src/plugins/autoreload/jsenv_plugin_autoreload_client.js +4 -4
  24. package/src/plugins/import_meta_hot/html_hot_dependencies.js +2 -2
  25. package/src/plugins/importmap/jsenv_plugin_importmap.js +5 -3
  26. package/src/plugins/inject_globals/inject_globals.js +3 -3
  27. package/src/plugins/inline/jsenv_plugin_data_urls.js +1 -1
  28. package/src/plugins/inline/jsenv_plugin_html_inline_content.js +10 -5
  29. package/src/plugins/node_esm_resolution/jsenv_plugin_node_esm_resolution.js +2 -13
  30. package/src/plugins/plugins.js +5 -5
  31. package/src/plugins/server_events/jsenv_plugin_server_events_client_injection.js +4 -4
  32. package/src/plugins/supervisor/client/script_type_module_supervisor.js +99 -0
  33. package/src/plugins/supervisor/client/supervisor.js +933 -0
  34. package/src/plugins/{html_supervisor/jsenv_plugin_html_supervisor.js → supervisor/jsenv_plugin_supervisor.js} +128 -102
  35. package/src/plugins/toolbar/client/execution/toolbar_execution.js +1 -1
  36. package/src/plugins/toolbar/jsenv_plugin_toolbar.js +4 -4
  37. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic.js +7 -5
  38. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic_html.js +5 -4
  39. package/src/plugins/transpilation/babel/jsenv_plugin_babel.js +13 -7
  40. package/src/plugins/transpilation/babel/new_stylesheet/babel_plugin_new_stylesheet_as_jsenv_import.js +6 -4
  41. package/src/plugins/transpilation/jsenv_plugin_transpilation.js +4 -2
  42. package/src/plugins/url_analysis/html/html_urls.js +11 -10
  43. package/src/test/coverage/babel_plugin_instrument.js +1 -35
  44. package/src/test/coverage/empty_coverage_factory.js +1 -1
  45. package/src/test/execute_plan.js +7 -3
  46. package/src/test/execute_test_plan.js +2 -1
  47. package/src/test/logs_file_execution.js +49 -8
  48. package/dist/js/html_supervisor_installer.js +0 -1091
  49. package/dist/js/html_supervisor_setup.js +0 -89
  50. package/dist/js/uneval.js +0 -804
  51. package/src/plugins/html_supervisor/client/error_formatter.js +0 -426
  52. package/src/plugins/html_supervisor/client/error_in_notification.js +0 -21
  53. package/src/plugins/html_supervisor/client/error_overlay.js +0 -191
  54. package/src/plugins/html_supervisor/client/html_supervisor_installer.js +0 -315
  55. package/src/plugins/html_supervisor/client/html_supervisor_setup.js +0 -89
  56. package/src/plugins/html_supervisor/client/perf_browser.js +0 -17
  57. package/src/plugins/html_supervisor/client/uneval_exception.js +0 -8
@@ -0,0 +1,933 @@
1
+ window.__supervisor__ = (() => {
2
+ const notImplemented = () => {
3
+ throw new Error(`window.__supervisor__.setup() not called`)
4
+ }
5
+ const executionResults = {}
6
+ const supervisor = {
7
+ reportError: notImplemented,
8
+ superviseScript: notImplemented,
9
+ reloadSupervisedScript: notImplemented,
10
+ collectScriptResults: notImplemented,
11
+ getScriptExecutionResults: () => {
12
+ // wait for page to load before collecting script execution results
13
+ const htmlReadyPromise = new Promise((resolve) => {
14
+ if (document.readyState === "complete") {
15
+ resolve()
16
+ return
17
+ }
18
+ const loadCallback = () => {
19
+ window.removeEventListener("load", loadCallback)
20
+ resolve()
21
+ }
22
+ window.addEventListener("load", loadCallback)
23
+ })
24
+ return htmlReadyPromise.then(() => {
25
+ return supervisor.collectScriptResults()
26
+ })
27
+ },
28
+ executionResults,
29
+ }
30
+
31
+ supervisor.setupReportException = ({
32
+ rootDirectoryUrl,
33
+ errorNotification,
34
+ errorOverlay,
35
+ errorBaseUrl,
36
+ openInEditor,
37
+ }) => {
38
+ const DYNAMIC_IMPORT_FETCH_ERROR = "dynamic_import_fetch_error"
39
+ const DYNAMIC_IMPORT_EXPORT_MISSING = "dynamic_import_export_missing"
40
+ const DYNAMIC_IMPORT_SYNTAX_ERROR = "dynamic_import_syntax_error"
41
+
42
+ const createException = ({
43
+ reason,
44
+ reportedBy,
45
+ url,
46
+ line,
47
+ column,
48
+ } = {}) => {
49
+ const exception = {
50
+ reason,
51
+ reportedBy,
52
+ isError: false, // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/throw
53
+ code: null,
54
+ message: null,
55
+ stack: null,
56
+ stackFormatIsV8: null,
57
+ stackSourcemapped: null,
58
+ originalStack: null,
59
+ meta: null,
60
+ site: {
61
+ isInline: null,
62
+ url: null,
63
+ line: null,
64
+ column: null,
65
+ originalUrl: null,
66
+ },
67
+ }
68
+
69
+ const writeBasicProperties = () => {
70
+ if (reason === undefined) {
71
+ exception.message = "undefined"
72
+ return
73
+ }
74
+ if (reason === null) {
75
+ exception.message = "null"
76
+ return
77
+ }
78
+ if (typeof reason === "string") {
79
+ exception.message = reason
80
+ return
81
+ }
82
+ if (reason instanceof Error) {
83
+ const error = reason
84
+ let message = error.message
85
+ exception.isError = true
86
+ if (Error.captureStackTrace) {
87
+ // stackTrace formatted by V8
88
+ exception.message = message
89
+ exception.stack = getErrorStackWithoutErrorMessage(error)
90
+ exception.stackFormatIsV8 = true
91
+ exception.stackSourcemapped = true
92
+ } else {
93
+ exception.message = message
94
+ exception.stack = error.stack ? ` ${error.stack}` : null
95
+ exception.stackFormatIsV8 = false
96
+ exception.stackSourcemapped = false
97
+ }
98
+ if (error.reportedBy) {
99
+ exception.reportedBy = error.reportedBy
100
+ }
101
+ if (error.url) {
102
+ Object.assign(exception.site, resolveUrlSite({ url: error.url }))
103
+ }
104
+ if (error.needsReport) {
105
+ exception.needsReport = true
106
+ }
107
+ export_missing: {
108
+ // chrome
109
+ if (message.includes("does not provide an export named")) {
110
+ exception.code = DYNAMIC_IMPORT_EXPORT_MISSING
111
+ return
112
+ }
113
+ // firefox
114
+ if (message.startsWith("import not found:")) {
115
+ exception.code = DYNAMIC_IMPORT_EXPORT_MISSING
116
+ return
117
+ }
118
+ // safari
119
+ if (message.startsWith("import binding name")) {
120
+ exception.code = DYNAMIC_IMPORT_EXPORT_MISSING
121
+ return
122
+ }
123
+ }
124
+ js_syntax_error: {
125
+ if (error.name === "SyntaxError" && typeof line === "number") {
126
+ exception.code = DYNAMIC_IMPORT_SYNTAX_ERROR
127
+ return
128
+ }
129
+ }
130
+ return
131
+ }
132
+ if (typeof reason === "object") {
133
+ exception.code = reason.code
134
+ exception.message = reason.message
135
+ exception.stack = reason.stack
136
+ if (reason.reportedBy) {
137
+ exception.reportedBy = reason.reportedBy
138
+ }
139
+ if (reason.url) {
140
+ Object.assign(exception.site, resolveUrlSite({ url: reason.url }))
141
+ }
142
+ if (reason.needsReport) {
143
+ exception.needsReport = true
144
+ }
145
+ return
146
+ }
147
+ exception.message = JSON.stringify(reason)
148
+ }
149
+ writeBasicProperties()
150
+
151
+ // first create a version of the stack with file://
152
+ // (and use it to locate exception url+line+column)
153
+ if (exception.stack) {
154
+ exception.originalStack = exception.stack
155
+ exception.stack = replaceUrls(
156
+ exception.originalStack,
157
+ (serverUrlSite) => {
158
+ const fileUrlSite = resolveUrlSite(serverUrlSite)
159
+ if (exception.site.url === null) {
160
+ Object.assign(exception.site, fileUrlSite)
161
+ }
162
+ return stringifyUrlSite(fileUrlSite)
163
+ },
164
+ )
165
+ }
166
+ // then if it fails, use url+line+column passed
167
+ if (exception.site.url === null && url) {
168
+ if (typeof line === "string") {
169
+ line = parseInt(line)
170
+ }
171
+ if (typeof column === "string") {
172
+ column = parseInt(column)
173
+ }
174
+ const fileUrlSite = resolveUrlSite({ url, line, column })
175
+ if (
176
+ fileUrlSite.isInline &&
177
+ exception.code === DYNAMIC_IMPORT_SYNTAX_ERROR
178
+ ) {
179
+ // syntax error on inline script need line-1 for some reason
180
+ if (Error.captureStackTrace) {
181
+ fileUrlSite.line--
182
+ } else {
183
+ // firefox and safari need line-2
184
+ fileUrlSite.line -= 2
185
+ }
186
+ }
187
+ Object.assign(exception.site, fileUrlSite)
188
+ }
189
+ exception.text = stringifyMessageAndStack(exception)
190
+ return exception
191
+ }
192
+
193
+ const stringifyMessageAndStack = ({ message, stack }) => {
194
+ if (message && stack) {
195
+ return `${message}\n${stack}`
196
+ }
197
+ if (stack) {
198
+ return stack
199
+ }
200
+ return message
201
+ }
202
+
203
+ const stringifyUrlSite = ({ url, line, column }) => {
204
+ return typeof line === "number" && typeof column === "number"
205
+ ? `${url}:${line}:${column}`
206
+ : typeof line === "number"
207
+ ? `${url}:${line}`
208
+ : url
209
+ }
210
+
211
+ const resolveUrlSite = ({ url, line, column }) => {
212
+ const inlineUrlMatch = url.match(
213
+ /@L([0-9]+)C([0-9]+)\-L([0-9]+)C([0-9]+)(\.[\w]+)$/,
214
+ )
215
+ if (inlineUrlMatch) {
216
+ const htmlUrl = url.slice(0, inlineUrlMatch.index)
217
+ const tagLineStart = parseInt(inlineUrlMatch[1])
218
+ const tagColumnStart = parseInt(inlineUrlMatch[2])
219
+ const tagLineEnd = parseInt(inlineUrlMatch[3])
220
+ const tagColumnEnd = parseInt(inlineUrlMatch[4])
221
+ const extension = inlineUrlMatch[5]
222
+ url = htmlUrl
223
+ line = tagLineStart + (typeof line === "number" ? line : 0)
224
+ // stackTrace formatted by V8 (chrome)
225
+ if (Error.captureStackTrace) {
226
+ line--
227
+ }
228
+ column = tagColumnStart + (typeof column === "number" ? column : 0)
229
+ const fileUrl = resolveFileUrl(url)
230
+ return {
231
+ isInline: true,
232
+ serverUrl: url,
233
+ originalUrl: `${fileUrl}@L${tagLineStart}C${tagColumnStart}-L${tagLineEnd}C${tagColumnEnd}${extension}`,
234
+ url: fileUrl,
235
+ line,
236
+ column,
237
+ }
238
+ }
239
+ return {
240
+ isInline: false,
241
+ serverUrl: url,
242
+ url: resolveFileUrl(url),
243
+ line,
244
+ column,
245
+ }
246
+ }
247
+
248
+ const getErrorStackWithoutErrorMessage = (error) => {
249
+ let stack = error.stack
250
+ const messageInStack = `${error.name}: ${error.message}`
251
+ if (stack.startsWith(messageInStack)) {
252
+ stack = stack.slice(messageInStack.length)
253
+ }
254
+ const nextLineIndex = stack.indexOf("\n")
255
+ if (nextLineIndex > -1) {
256
+ stack = stack.slice(nextLineIndex + 1)
257
+ }
258
+ return stack
259
+ }
260
+
261
+ const resolveFileUrl = (url) => {
262
+ let urlObject = new URL(url)
263
+ if (urlObject.origin === window.origin) {
264
+ urlObject = new URL(
265
+ `${urlObject.pathname.slice(1)}${urlObject.search}`,
266
+ rootDirectoryUrl,
267
+ )
268
+ }
269
+ if (urlObject.href.startsWith("file:")) {
270
+ const atFsIndex = urlObject.pathname.indexOf("/@fs/")
271
+ if (atFsIndex > -1) {
272
+ const afterAtFs = urlObject.pathname.slice(atFsIndex + "/@fs/".length)
273
+ return new URL(afterAtFs, "file:///").href
274
+ }
275
+ }
276
+ return urlObject.href
277
+ }
278
+
279
+ // `Error: yo
280
+ // at Object.execute (http://127.0.0.1:57300/build/src/__test__/file-throw.js:9:13)
281
+ // at doExec (http://127.0.0.1:3000/src/__test__/file-throw.js:452:38)
282
+ // at postOrderExec (http://127.0.0.1:3000/src/__test__/file-throw.js:448:16)
283
+ // at http://127.0.0.1:3000/src/__test__/file-throw.js:399:18`.replace(/(?:https?|ftp|file):\/\/(.*+)$/gm, (...args) => {
284
+ // debugger
285
+ // })
286
+ const replaceUrls = (source, replace) => {
287
+ return source.replace(/(?:https?|ftp|file):\/\/\S+/gm, (match) => {
288
+ let replacement = ""
289
+ const lastChar = match[match.length - 1]
290
+
291
+ // hotfix because our url regex sucks a bit
292
+ const endsWithSeparationChar = lastChar === ")" || lastChar === ":"
293
+ if (endsWithSeparationChar) {
294
+ match = match.slice(0, -1)
295
+ }
296
+
297
+ const lineAndColumnPattern = /:([0-9]+):([0-9]+)$/
298
+ const lineAndColumMatch = match.match(lineAndColumnPattern)
299
+ if (lineAndColumMatch) {
300
+ const lineAndColumnString = lineAndColumMatch[0]
301
+ const lineString = lineAndColumMatch[1]
302
+ const columnString = lineAndColumMatch[2]
303
+ replacement = replace({
304
+ url: match.slice(0, -lineAndColumnString.length),
305
+ line: lineString ? parseInt(lineString) : null,
306
+ column: columnString ? parseInt(columnString) : null,
307
+ })
308
+ } else {
309
+ const linePattern = /:([0-9]+)$/
310
+ const lineMatch = match.match(linePattern)
311
+ if (lineMatch) {
312
+ const lineString = lineMatch[0]
313
+ replacement = replace({
314
+ url: match.slice(0, -lineString.length),
315
+ line: lineString ? parseInt(lineString) : null,
316
+ })
317
+ } else {
318
+ replacement = replace({
319
+ url: match,
320
+ })
321
+ }
322
+ }
323
+ if (endsWithSeparationChar) {
324
+ return `${replacement}${lastChar}`
325
+ }
326
+ return replacement
327
+ })
328
+ }
329
+
330
+ let formatError
331
+ error_formatter: {
332
+ formatError = (exceptionInfo) => {
333
+ const errorParts = {
334
+ theme: "dark",
335
+ title: "An error occured",
336
+ text: "",
337
+ tip: "",
338
+ errorDetailsPromise: null,
339
+ }
340
+ const tips = []
341
+ tips.push("Click outside to close.")
342
+ errorParts.tip = tips.join(`\n <br />\n `)
343
+
344
+ const generateClickableText = (text) => {
345
+ const textWithHtmlLinks = makeLinksClickable(text, {
346
+ createLink: ({ url, line, column }) => {
347
+ const urlSite = resolveUrlSite({ url, line, column })
348
+ if (errorBaseUrl) {
349
+ if (urlSite.url.startsWith(rootDirectoryUrl)) {
350
+ urlSite.url = `${errorBaseUrl}${urlSite.url.slice(
351
+ rootDirectoryUrl.length,
352
+ )}`
353
+ } else {
354
+ urlSite.url = "file:///mocked_for_snapshots"
355
+ }
356
+ }
357
+ const urlWithLineAndColumn = stringifyUrlSite(urlSite)
358
+ return {
359
+ href:
360
+ urlSite.url.startsWith("file:") && openInEditor
361
+ ? `javascript:window.fetch('/__open_in_editor__/${encodeURIComponent(
362
+ urlWithLineAndColumn,
363
+ )}')`
364
+ : urlSite.url,
365
+ text: urlWithLineAndColumn,
366
+ }
367
+ },
368
+ })
369
+ return textWithHtmlLinks
370
+ }
371
+
372
+ errorParts.text = stringifyMessageAndStack({
373
+ message: exceptionInfo.message
374
+ ? generateClickableText(exceptionInfo.message)
375
+ : "",
376
+ stack: exceptionInfo.stack
377
+ ? generateClickableText(exceptionInfo.stack)
378
+ : "",
379
+ })
380
+ if (exceptionInfo.site.url) {
381
+ errorParts.errorDetailsPromise = (async () => {
382
+ try {
383
+ if (
384
+ exceptionInfo.code === DYNAMIC_IMPORT_FETCH_ERROR ||
385
+ exceptionInfo.reportedBy === "script_error_event"
386
+ ) {
387
+ const response = await window.fetch(
388
+ `/__get_error_cause__/${encodeURIComponent(
389
+ exceptionInfo.site.isInline
390
+ ? exceptionInfo.site.originalUrl
391
+ : exceptionInfo.site.url,
392
+ )}`,
393
+ )
394
+ if (response.status !== 200) {
395
+ return null
396
+ }
397
+ const causeInfo = await response.json()
398
+ if (!causeInfo) {
399
+ return null
400
+ }
401
+ const causeText =
402
+ causeInfo.code === "NOT_FOUND"
403
+ ? stringifyMessageAndStack({
404
+ message: generateClickableText(causeInfo.reason),
405
+ stack: generateClickableText(causeInfo.codeFrame),
406
+ })
407
+ : stringifyMessageAndStack({
408
+ message: generateClickableText(causeInfo.stack),
409
+ stack: generateClickableText(causeInfo.codeFrame),
410
+ })
411
+ return {
412
+ cause: causeText,
413
+ }
414
+ }
415
+ if (
416
+ exceptionInfo.site.line !== undefined &&
417
+ // code frame showing internal window.reportError is pointless
418
+ !exceptionInfo.site.url.endsWith(
419
+ `script_type_module_supervisor.js`,
420
+ )
421
+ ) {
422
+ const urlToFetch = new URL(
423
+ `/__get_code_frame__/${encodeURIComponent(
424
+ stringifyUrlSite(exceptionInfo.site),
425
+ )}`,
426
+ window.origin,
427
+ )
428
+ if (!exceptionInfo.stackSourcemapped) {
429
+ urlToFetch.searchParams.set("remap", "")
430
+ }
431
+ const response = await window.fetch(urlToFetch)
432
+ if (response.status !== 200) {
433
+ return null
434
+ }
435
+ const codeFrame = await response.text()
436
+ return {
437
+ codeFrame: generateClickableText(codeFrame),
438
+ }
439
+ }
440
+ } catch (e) {
441
+ // happens if server is closed for instance
442
+ return null
443
+ }
444
+ return null
445
+ })()
446
+ }
447
+ return errorParts
448
+ }
449
+
450
+ const makeLinksClickable = (
451
+ string,
452
+ { createLink = ({ url }) => url },
453
+ ) => {
454
+ // normalize line breaks
455
+ string = string.replace(/\n/g, "\n")
456
+ string = escapeHtml(string)
457
+ // render links
458
+ string = replaceUrls(string, ({ url, line, column }) => {
459
+ const { href, text } = createLink({ url, line, column })
460
+ return link({ href, text })
461
+ })
462
+ return string
463
+ }
464
+
465
+ const escapeHtml = (string) => {
466
+ return string
467
+ .replace(/&/g, "&amp;")
468
+ .replace(/</g, "&lt;")
469
+ .replace(/>/g, "&gt;")
470
+ .replace(/"/g, "&quot;")
471
+ .replace(/'/g, "&#039;")
472
+ }
473
+
474
+ const link = ({ href, text = href }) => `<a href="${href}">${text}</a>`
475
+ }
476
+
477
+ let displayErrorNotification
478
+ error_notification: {
479
+ const { Notification } = window
480
+ displayErrorNotification =
481
+ typeof Notification === "function"
482
+ ? ({ title, text, icon }) => {
483
+ if (Notification.permission === "granted") {
484
+ const notification = new Notification(title, {
485
+ lang: "en",
486
+ body: text,
487
+ icon,
488
+ })
489
+ notification.onclick = () => {
490
+ window.focus()
491
+ }
492
+ }
493
+ }
494
+ : () => {}
495
+ }
496
+
497
+ const JSENV_ERROR_OVERLAY_TAGNAME = "jsenv-error-overlay"
498
+ let displayJsenvErrorOverlay
499
+ error_overlay: {
500
+ displayJsenvErrorOverlay = (params) => {
501
+ let jsenvErrorOverlay = new JsenvErrorOverlay(params)
502
+ document
503
+ .querySelectorAll(JSENV_ERROR_OVERLAY_TAGNAME)
504
+ .forEach((node) => {
505
+ node.parentNode.removeChild(node)
506
+ })
507
+ document.body.appendChild(jsenvErrorOverlay)
508
+ const removeErrorOverlay = () => {
509
+ if (jsenvErrorOverlay && jsenvErrorOverlay.parentNode) {
510
+ document.body.removeChild(jsenvErrorOverlay)
511
+ jsenvErrorOverlay = null
512
+ }
513
+ }
514
+ return removeErrorOverlay
515
+ }
516
+
517
+ class JsenvErrorOverlay extends HTMLElement {
518
+ constructor({ theme, title, text, tip, errorDetailsPromise }) {
519
+ super()
520
+ this.root = this.attachShadow({ mode: "open" })
521
+ this.root.innerHTML = `
522
+ <style>
523
+ ${overlayCSS}
524
+ </style>
525
+ <div class="backdrop"></div>
526
+ <div class="overlay" data-theme=${theme}>
527
+ <h1 class="title">
528
+ ${title}
529
+ </h1>
530
+ <pre class="text">${text}</pre>
531
+ <div class="tip">
532
+ ${tip}
533
+ </div>
534
+ </div>`
535
+ this.root.querySelector(".backdrop").onclick = () => {
536
+ if (!this.parentNode) {
537
+ // not in document anymore
538
+ return
539
+ }
540
+ this.root.querySelector(".backdrop").onclick = null
541
+ this.parentNode.removeChild(this)
542
+ }
543
+ if (errorDetailsPromise) {
544
+ errorDetailsPromise.then((errorDetails) => {
545
+ if (!errorDetails || !this.parentNode) {
546
+ return
547
+ }
548
+ const { codeFrame, cause } = errorDetails
549
+ if (codeFrame) {
550
+ this.root.querySelector(".text").innerHTML += `\n\n${codeFrame}`
551
+ }
552
+ if (cause) {
553
+ const causeIndented = prefixRemainingLines(cause, " ")
554
+ this.root.querySelector(
555
+ ".text",
556
+ ).innerHTML += `\n [cause]: ${causeIndented}`
557
+ }
558
+ })
559
+ }
560
+ }
561
+ }
562
+
563
+ const prefixRemainingLines = (text, prefix) => {
564
+ const lines = text.split(/\r?\n/)
565
+ const firstLine = lines.shift()
566
+ let result = firstLine
567
+ let i = 0
568
+ while (i < lines.length) {
569
+ const line = lines[i]
570
+ i++
571
+ result += line.length ? `\n${prefix}${line}` : `\n`
572
+ }
573
+ return result
574
+ }
575
+
576
+ if (customElements && !customElements.get(JSENV_ERROR_OVERLAY_TAGNAME)) {
577
+ customElements.define(JSENV_ERROR_OVERLAY_TAGNAME, JsenvErrorOverlay)
578
+ }
579
+
580
+ const overlayCSS = `
581
+ :host {
582
+ position: fixed;
583
+ z-index: 99999;
584
+ top: 0;
585
+ left: 0;
586
+ width: 100%;
587
+ height: 100%;
588
+ overflow-y: scroll;
589
+ margin: 0;
590
+ background: rgba(0, 0, 0, 0.66);
591
+ }
592
+
593
+ .backdrop {
594
+ position: absolute;
595
+ left: 0;
596
+ right: 0;
597
+ top: 0;
598
+ bottom: 0;
599
+ }
600
+
601
+ .overlay {
602
+ position: relative;
603
+ background: rgba(0, 0, 0, 0.95);
604
+ width: 800px;
605
+ margin: 30px auto;
606
+ padding: 25px 40px;
607
+ padding-top: 0;
608
+ overflow: hidden; /* for h1 margins */
609
+ border-radius: 4px 8px;
610
+ box-shadow: 0 20px 40px rgb(0 0 0 / 30%), 0 15px 12px rgb(0 0 0 / 20%);
611
+ box-sizing: border-box;
612
+ font-family: monospace;
613
+ direction: ltr;
614
+ }
615
+
616
+ h1 {
617
+ color: red;
618
+ text-align: center;
619
+ }
620
+
621
+ pre {
622
+ overflow: auto;
623
+ max-width: 100%;
624
+ /* padding is nice + prevents scrollbar from hiding the text behind it */
625
+ /* does not work nicely on firefox though https://bugzilla.mozilla.org/show_bug.cgi?id=748518 */
626
+ padding: 20px;
627
+ }
628
+
629
+ .tip {
630
+ border-top: 1px solid #999;
631
+ padding-top: 12px;
632
+ }
633
+
634
+ [data-theme="dark"] {
635
+ color: #999;
636
+ }
637
+ [data-theme="dark"] pre {
638
+ background: #111;
639
+ border: 1px solid #333;
640
+ color: #eee;
641
+ }
642
+
643
+ [data-theme="light"] {
644
+ color: #EEEEEE;
645
+ }
646
+ [data-theme="light"] pre {
647
+ background: #1E1E1E;
648
+ border: 1px solid white;
649
+ color: #EEEEEE;
650
+ }
651
+
652
+ pre a {
653
+ color: inherit;
654
+ }`
655
+ }
656
+
657
+ supervisor.createException = createException
658
+ supervisor.reportException = (exception) => {
659
+ const { theme, title, text, tip, errorDetailsPromise } =
660
+ formatError(exception)
661
+
662
+ if (errorOverlay) {
663
+ const removeErrorOverlay = displayJsenvErrorOverlay({
664
+ theme,
665
+ title,
666
+ text,
667
+ tip,
668
+ errorDetailsPromise,
669
+ })
670
+ if (window.__reloader__) {
671
+ window.__reloader__.onstatuschange = () => {
672
+ if (window.__reloader__.status === "reloading") {
673
+ removeErrorOverlay()
674
+ }
675
+ }
676
+ }
677
+ }
678
+ if (errorNotification) {
679
+ displayErrorNotification({
680
+ title,
681
+ text,
682
+ })
683
+ }
684
+ return exception
685
+ }
686
+ window.addEventListener("error", (errorEvent) => {
687
+ if (!errorEvent.isTrusted) {
688
+ // ignore custom error event (not sent by browser)
689
+ return
690
+ }
691
+ const { error, message, filename, lineno, colno } = errorEvent
692
+ const exception = supervisor.createException({
693
+ // when error is reported within a worker error is null
694
+ // but there is a message property on errorEvent
695
+ reason: error || message,
696
+ reportedBy: "window_error_event",
697
+ url: filename,
698
+ line: lineno,
699
+ column: colno,
700
+ })
701
+ supervisor.reportException(exception)
702
+ })
703
+ window.addEventListener("unhandledrejection", (event) => {
704
+ if (event.defaultPrevented) {
705
+ return
706
+ }
707
+ const exception = supervisor.createException({
708
+ reason: event.reason,
709
+ reportedBy: "window_unhandledrejection_event",
710
+ })
711
+ supervisor.reportException(exception)
712
+ })
713
+ }
714
+
715
+ supervisor.setup = ({
716
+ rootDirectoryUrl,
717
+ logs,
718
+ measurePerf,
719
+ errorOverlay,
720
+ errorBaseUrl,
721
+ openInEditor,
722
+ }) => {
723
+ supervisor.setupReportException({
724
+ rootDirectoryUrl,
725
+ errorOverlay,
726
+ errorBaseUrl,
727
+ openInEditor,
728
+ })
729
+
730
+ const supervisedScripts = []
731
+ const executionPromises = []
732
+ supervisor.createExecution = ({ type, src, async, execute }) => {
733
+ const execution = {
734
+ type,
735
+ src,
736
+ async,
737
+ execute,
738
+ }
739
+ execution.start = () => {
740
+ return superviseExecution(execution, { isReload: false })
741
+ }
742
+ execution.reload = () => {
743
+ return superviseExecution(execution, { isReload: true })
744
+ }
745
+ supervisedScripts.push(execution)
746
+ return execution
747
+ }
748
+ const superviseExecution = async (execution, { isReload }) => {
749
+ if (logs) {
750
+ console.group(`[jsenv] loading ${execution.type} ${execution.src}`)
751
+ }
752
+ if (measurePerf) {
753
+ performance.mark(`execution_start`)
754
+ }
755
+ const executionResult = {
756
+ status: "pending",
757
+ exception: null,
758
+ namespace: null,
759
+ coverage: null,
760
+ }
761
+ executionResults[execution.src] = executionResult
762
+ let resolveExecutionPromise
763
+ const promise = new Promise((resolve) => {
764
+ resolveExecutionPromise = () => {
765
+ const index = executionPromises.indexOf(promise)
766
+ if (index > -1) {
767
+ executionPromises.splice(index, 1)
768
+ }
769
+ resolve()
770
+ }
771
+ })
772
+ promise.execution = execution
773
+ executionPromises.push(promise)
774
+ try {
775
+ const result = await execution.execute({ isReload })
776
+ if (measurePerf) {
777
+ performance.measure(`execution`, `execution_start`)
778
+ }
779
+ executionResult.status = "completed"
780
+ executionResult.namespace = result
781
+ executionResult.coverage = window.__coverage__
782
+ if (logs) {
783
+ console.log(`${execution.type} load ended`)
784
+ console.groupEnd()
785
+ }
786
+ resolveExecutionPromise()
787
+ } catch (e) {
788
+ if (measurePerf) {
789
+ performance.measure(`execution`, `execution_start`)
790
+ }
791
+ executionResult.status = "errored"
792
+ const exception = supervisor.createException({
793
+ reason: e,
794
+ })
795
+ if (exception.needsReport) {
796
+ supervisor.reportException(exception)
797
+ }
798
+ executionResult.exception = exception
799
+ executionResult.coverage = window.__coverage__
800
+ if (logs) {
801
+ console.groupEnd()
802
+ }
803
+ resolveExecutionPromise()
804
+ }
805
+ }
806
+
807
+ // respect execution order
808
+ // - wait for classic scripts to be done (non async)
809
+ // - wait module script previous execution (non async)
810
+ // see https://gist.github.com/jakub-g/385ee6b41085303a53ad92c7c8afd7a6#typemodule-vs-non-module-typetextjavascript-vs-script-nomodule
811
+ supervisor.getPreviousExecutionDonePromise = async () => {
812
+ const previousNonAsyncExecutions = executionPromises.filter(
813
+ (promise) => !promise.execution.async,
814
+ )
815
+ await Promise.all(previousNonAsyncExecutions)
816
+ }
817
+ supervisor.superviseScript = async ({ src, async }) => {
818
+ const { currentScript } = document
819
+ const parentNode = currentScript.parentNode
820
+ if (!async) {
821
+ await supervisor.getPreviousExecutionDonePromise()
822
+ }
823
+ let nodeToReplace
824
+ let currentScriptClone
825
+ const execution = supervisor.createExecution({
826
+ src,
827
+ type: "js_classic",
828
+ async,
829
+ execute: async ({ isReload }) => {
830
+ const urlObject = new URL(src, window.location)
831
+ const loadPromise = new Promise((resolve, reject) => {
832
+ // do not use script.cloneNode()
833
+ // bcause https://stackoverflow.com/questions/28771542/why-dont-clonenode-script-tags-execute
834
+ currentScriptClone = document.createElement("script")
835
+ // browsers set async by default when creating script(s)
836
+ // we want an exact copy to preserves how code is executed
837
+ currentScriptClone.async = false
838
+ Array.from(currentScript.attributes).forEach((attribute) => {
839
+ currentScriptClone.setAttribute(
840
+ attribute.nodeName,
841
+ attribute.nodeValue,
842
+ )
843
+ })
844
+ if (isReload) {
845
+ urlObject.searchParams.set("hmr", Date.now())
846
+ nodeToReplace = currentScriptClone
847
+ currentScriptClone.src = urlObject.href
848
+ } else {
849
+ currentScriptClone.removeAttribute("jsenv-plugin-owner")
850
+ currentScriptClone.removeAttribute("jsenv-plugin-action")
851
+ currentScriptClone.removeAttribute("inlined-from-src")
852
+ currentScriptClone.removeAttribute("original-position")
853
+ currentScriptClone.removeAttribute("original-src-position")
854
+ nodeToReplace = currentScript
855
+ currentScriptClone.src = src
856
+ }
857
+ currentScriptClone.addEventListener("error", reject)
858
+ currentScriptClone.addEventListener("load", resolve)
859
+ parentNode.replaceChild(currentScriptClone, nodeToReplace)
860
+ })
861
+ try {
862
+ await loadPromise
863
+ } catch (e) {
864
+ // eslint-disable-next-line no-throw-literal
865
+ throw {
866
+ message: `Failed to fetch script: ${urlObject.href}`,
867
+ reportedBy: "script_error_event",
868
+ url: urlObject.href,
869
+ // window.error won't be dispatched for this error
870
+ needsReport: true,
871
+ }
872
+ }
873
+ },
874
+ })
875
+ return execution.start()
876
+ }
877
+ supervisor.reloadSupervisedScript = ({ type, src }) => {
878
+ const supervisedScript = supervisedScripts.find(
879
+ (supervisedScriptCandidate) => {
880
+ if (type && supervisedScriptCandidate.type !== type) {
881
+ return false
882
+ }
883
+ if (supervisedScriptCandidate.src !== src) {
884
+ return false
885
+ }
886
+ return true
887
+ },
888
+ )
889
+ if (supervisedScript) {
890
+ supervisedScript.reload()
891
+ }
892
+ }
893
+ supervisor.collectScriptResults = async () => {
894
+ // just to be super safe and ensure any <script type="module"> got a chance to execute
895
+ const scriptTypeModuleLoaded = new Promise((resolve) => {
896
+ const scriptTypeModule = document.createElement("script")
897
+ scriptTypeModule.type = "module"
898
+ scriptTypeModule.innerText =
899
+ "window.__supervisor__.scriptModuleCallback()"
900
+ window.__supervisor__.scriptModuleCallback = () => {
901
+ scriptTypeModule.parentNode.removeChild(scriptTypeModule)
902
+ resolve()
903
+ }
904
+ document.body.appendChild(scriptTypeModule)
905
+ })
906
+ await scriptTypeModuleLoaded
907
+
908
+ const waitPendingExecutions = async () => {
909
+ if (executionPromises.length) {
910
+ await Promise.all(executionPromises)
911
+ await waitPendingExecutions()
912
+ }
913
+ }
914
+ await waitPendingExecutions()
915
+ return {
916
+ status: "completed",
917
+ executionResults,
918
+ startTime: getNavigationStartTime(),
919
+ endTime: Date.now(),
920
+ }
921
+ }
922
+
923
+ const getNavigationStartTime = () => {
924
+ try {
925
+ return window.performance.timing.navigationStart
926
+ } catch (e) {
927
+ return Date.now()
928
+ }
929
+ }
930
+ }
931
+
932
+ return supervisor
933
+ })()