@jsenv/core 27.5.3 → 27.7.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.
Files changed (30) hide show
  1. package/dist/js/autoreload.js +10 -6
  2. package/dist/js/html_supervisor_installer.js +272 -163
  3. package/dist/js/html_supervisor_setup.js +10 -2
  4. package/dist/js/server_events_client.js +249 -216
  5. package/dist/js/wrapper.mjs +4233 -0
  6. package/dist/main.js +21342 -21629
  7. package/package.json +4 -4
  8. package/src/build/build.js +15 -13
  9. package/src/dev/start_dev_server.js +10 -10
  10. package/src/execute/runtimes/browsers/chromium.js +1 -1
  11. package/src/execute/runtimes/browsers/firefox.js +1 -1
  12. package/src/execute/runtimes/browsers/webkit.js +1 -1
  13. package/src/omega/kitchen.js +13 -15
  14. package/src/omega/omega_server.js +15 -0
  15. package/src/omega/server/file_service.js +11 -84
  16. package/src/omega/url_graph/url_graph_load.js +0 -2
  17. package/src/omega/url_graph/url_info_transformations.js +27 -0
  18. package/src/omega/url_graph.js +1 -0
  19. package/src/plugins/autoreload/client/autoreload.js +10 -4
  20. package/src/plugins/html_supervisor/client/error_formatter.js +187 -66
  21. package/src/plugins/html_supervisor/client/error_overlay.js +29 -31
  22. package/src/plugins/html_supervisor/client/html_supervisor_installer.js +22 -67
  23. package/src/plugins/html_supervisor/client/html_supervisor_setup.js +10 -2
  24. package/src/plugins/html_supervisor/jsenv_plugin_html_supervisor.js +96 -25
  25. package/src/plugins/server_events/client/connection_manager.js +165 -0
  26. package/src/plugins/server_events/client/event_source_connection.js +50 -256
  27. package/src/plugins/server_events/client/events_manager.js +75 -0
  28. package/src/plugins/server_events/client/server_events_client.js +12 -11
  29. package/src/plugins/server_events/client/web_socket_connection.js +81 -0
  30. package/src/plugins/server_events/server_events_dispatcher.js +70 -54
@@ -1,33 +1,62 @@
1
1
  export const formatError = (
2
2
  error,
3
- {
4
- rootDirectoryUrl,
5
- errorBaseUrl,
6
- openInEditor,
7
- url,
8
- line,
9
- column,
10
- codeFrame,
11
- requestedRessource,
12
- reportedBy,
13
- },
3
+ { rootDirectoryUrl, errorBaseUrl, openInEditor, url, line, column },
14
4
  ) => {
15
5
  let { message, stack } = normalizeErrorParts(error)
16
- let codeFramePromiseReference = { current: null }
17
- let tip = formatTip({ reportedBy, requestedRessource })
6
+ let errorDetailsPromiseReference = { current: null }
7
+ let tip = `Reported by the browser while executing <code>${window.location.pathname}${window.location.search}</code>.`
18
8
  let errorUrlSite
19
9
 
10
+ const errorMeta = extractErrorMeta(error, { url, line, column })
11
+
20
12
  const resolveUrlSite = ({ url, line, column }) => {
21
- const inlineUrlMatch = url.match(/@L([0-9]+)\-L([0-9]+)\.[\w]+$/)
13
+ if (typeof line === "string") line = parseInt(line)
14
+ if (typeof column === "string") column = parseInt(column)
15
+
16
+ const inlineUrlMatch = url.match(
17
+ /@L([0-9]+)C([0-9]+)\-L([0-9]+)C([0-9]+)(\.[\w]+)$/,
18
+ )
22
19
  if (inlineUrlMatch) {
23
20
  const htmlUrl = url.slice(0, inlineUrlMatch.index)
24
- const tagLine = parseInt(inlineUrlMatch[1])
25
- const tagColumn = parseInt(inlineUrlMatch[2])
21
+ const tagLineStart = parseInt(inlineUrlMatch[1])
22
+ const tagColumnStart = parseInt(inlineUrlMatch[2])
23
+ const tagLineEnd = parseInt(inlineUrlMatch[3])
24
+ const tagColumnEnd = parseInt(inlineUrlMatch[4])
25
+ const extension = inlineUrlMatch[5]
26
26
  url = htmlUrl
27
- line = tagLine + parseInt(line) - 1
28
- column = tagColumn + parseInt(column)
27
+ line = tagLineStart + (typeof line === "number" ? line : 0)
28
+ // stackTrace formatted by V8 (chrome)
29
+ if (Error.captureStackTrace) {
30
+ line--
31
+ }
32
+ if (errorMeta.type === "dynamic_import_syntax_error") {
33
+ // syntax error on inline script need line-1 for some reason
34
+ if (Error.captureStackTrace) {
35
+ line--
36
+ } else {
37
+ // firefox and safari need line-2
38
+ line -= 2
39
+ }
40
+ }
41
+ column = tagColumnStart + (typeof column === "number" ? column : 0)
42
+ const fileUrl = resolveFileUrl(url)
43
+ return {
44
+ isInline: true,
45
+ originalUrl: `${fileUrl}@L${tagLineStart}C${tagColumnStart}-L${tagLineEnd}C${tagColumnEnd}${extension}`,
46
+ url: fileUrl,
47
+ line,
48
+ column,
49
+ }
29
50
  }
51
+ return {
52
+ isInline: false,
53
+ url: resolveFileUrl(url),
54
+ line,
55
+ column,
56
+ }
57
+ }
30
58
 
59
+ const resolveFileUrl = (url) => {
31
60
  let urlObject = new URL(url)
32
61
  if (urlObject.origin === window.origin) {
33
62
  urlObject = new URL(
@@ -39,19 +68,10 @@ export const formatError = (
39
68
  const atFsIndex = urlObject.pathname.indexOf("/@fs/")
40
69
  if (atFsIndex > -1) {
41
70
  const afterAtFs = urlObject.pathname.slice(atFsIndex + "/@fs/".length)
42
- url = new URL(afterAtFs, "file:///").href
43
- } else {
44
- url = urlObject.href
71
+ return new URL(afterAtFs, "file:///").href
45
72
  }
46
- } else {
47
- url = urlObject.href
48
- }
49
-
50
- return {
51
- url,
52
- line,
53
- column,
54
73
  }
74
+ return urlObject.href
55
75
  }
56
76
 
57
77
  const generateClickableText = (text) => {
@@ -59,7 +79,7 @@ export const formatError = (
59
79
  createLink: (url, { line, column }) => {
60
80
  const urlSite = resolveUrlSite({ url, line, column })
61
81
  if (!errorUrlSite && text === stack) {
62
- onErrorLocated(urlSite)
82
+ onErrorLocated(urlSite, "error.stack")
63
83
  }
64
84
  if (errorBaseUrl) {
65
85
  if (urlSite.url.startsWith(rootDirectoryUrl)) {
@@ -83,21 +103,74 @@ export const formatError = (
83
103
  return textWithHtmlLinks
84
104
  }
85
105
 
86
- const onErrorLocated = (urlSite) => {
87
- errorUrlSite = urlSite
88
- if (codeFrame) {
89
- return
106
+ const formatErrorText = ({ message, stack, codeFrame }) => {
107
+ let text
108
+ if (message && stack) {
109
+ text = `${generateClickableText(message)}\n${generateClickableText(
110
+ stack,
111
+ )}`
112
+ } else if (stack) {
113
+ text = generateClickableText(stack)
114
+ } else {
115
+ text = generateClickableText(message)
90
116
  }
91
- if (reportedBy !== "browser") {
92
- return
117
+ if (codeFrame) {
118
+ text += `\n\n${generateClickableText(codeFrame)}`
93
119
  }
94
- codeFramePromiseReference.current = (async () => {
95
- const response = await window.fetch(
96
- `/__get_code_frame__/${formatUrlWithLineAndColumn(urlSite)}`,
97
- )
98
- const codeFrame = await response.text()
99
- const codeFrameClickable = generateClickableText(codeFrame)
100
- return codeFrameClickable
120
+ return text
121
+ }
122
+
123
+ const onErrorLocated = (urlSite) => {
124
+ errorUrlSite = urlSite
125
+ errorDetailsPromiseReference.current = (async () => {
126
+ try {
127
+ if (errorMeta.type === "dynamic_import_fetch_error") {
128
+ const response = await window.fetch(
129
+ `/__get_error_cause__/${
130
+ urlSite.isInline ? urlSite.originalUrl : urlSite.url
131
+ }`,
132
+ )
133
+
134
+ if (response.status !== 200) {
135
+ return null
136
+ }
137
+ const causeInfo = await response.json()
138
+ if (!causeInfo) {
139
+ return null
140
+ }
141
+
142
+ const causeText =
143
+ causeInfo.code === "NOT_FOUND"
144
+ ? formatErrorText({
145
+ message: causeInfo.reason,
146
+ stack: causeInfo.codeFrame,
147
+ })
148
+ : formatErrorText({
149
+ message: causeInfo.stack,
150
+ stack: causeInfo.codeFrame,
151
+ })
152
+ return {
153
+ cause: causeText,
154
+ }
155
+ }
156
+ if (urlSite.line !== undefined) {
157
+ let ressourceToFetch = `/__get_code_frame__/${formatUrlWithLineAndColumn(
158
+ urlSite,
159
+ )}`
160
+ if (!Error.captureStackTrace) {
161
+ ressourceToFetch += `?remap`
162
+ }
163
+ const response = await window.fetch(ressourceToFetch)
164
+ const codeFrame = await response.text()
165
+ return {
166
+ codeFrame: formatErrorText({ message: codeFrame }),
167
+ }
168
+ }
169
+ } catch (e) {
170
+ // happens if server is closed for instance
171
+ return null
172
+ }
173
+ return null
101
174
  })()
102
175
  }
103
176
 
@@ -110,20 +183,8 @@ export const formatError = (
110
183
  !url.endsWith("html_supervisor_installer.js")
111
184
  ) {
112
185
  onErrorLocated(resolveUrlSite({ url, line, column }))
113
- }
114
-
115
- let text
116
-
117
- if (message && stack) {
118
- text = `${generateClickableText(message)}\n${generateClickableText(stack)}`
119
- } else if (stack) {
120
- text = generateClickableText(stack)
121
- } else {
122
- text = generateClickableText(message)
123
- }
124
-
125
- if (codeFrame) {
126
- text += `\n\n${generateClickableText(codeFrame)}`
186
+ } else if (errorMeta.url) {
187
+ onErrorLocated(resolveUrlSite(errorMeta))
127
188
  }
128
189
 
129
190
  return {
@@ -132,12 +193,79 @@ export const formatError = (
132
193
  ? "light"
133
194
  : "dark",
134
195
  title: "An error occured",
135
- text,
136
- codeFramePromise: codeFramePromiseReference.current,
196
+ text: formatErrorText({ message, stack }),
137
197
  tip: `${tip}
138
198
  <br />
139
199
  Click outside to close.`,
200
+ errorDetailsPromise: errorDetailsPromiseReference.current,
201
+ }
202
+ }
203
+
204
+ const extractErrorMeta = (error, { line }) => {
205
+ if (!error) {
206
+ return {}
207
+ }
208
+ const { message } = error
209
+ if (!message) {
210
+ return {}
211
+ }
212
+
213
+ export_missing: {
214
+ // chrome
215
+ if (message.includes("does not provide an export named")) {
216
+ return {
217
+ type: "dynamic_import_export_missing",
218
+ }
219
+ }
220
+ // firefox
221
+ if (message.startsWith("import not found:")) {
222
+ return {
223
+ type: "dynamic_import_export_missing",
224
+ browser: "firefox",
225
+ }
226
+ }
227
+ // safari
228
+ if (message.startsWith("import binding name")) {
229
+ return {
230
+ type: "dynamic_import_export_missing",
231
+ }
232
+ }
233
+ }
234
+
235
+ js_syntax_error: {
236
+ if (error.name === "SyntaxError" && typeof line === "number") {
237
+ return {
238
+ type: "dynamic_import_syntax_error",
239
+ }
240
+ }
241
+ }
242
+
243
+ fetch_error: {
244
+ // chrome
245
+ if (message.startsWith("Failed to fetch dynamically imported module: ")) {
246
+ const url = error.message.slice(
247
+ "Failed to fetch dynamically imported module: ".length,
248
+ )
249
+ return {
250
+ type: "dynamic_import_fetch_error",
251
+ url,
252
+ }
253
+ }
254
+ // firefox
255
+ if (message === "error loading dynamically imported module") {
256
+ return {
257
+ type: "dynamic_import_fetch_error",
258
+ }
259
+ }
260
+ // safari
261
+ if (message === "Importing a module script failed.") {
262
+ return {
263
+ type: "dynamic_import_fetch_error",
264
+ }
265
+ }
140
266
  }
267
+
268
+ return {}
141
269
  }
142
270
 
143
271
  const formatUrlWithLineAndColumn = ({ url, line, column }) => {
@@ -213,13 +341,6 @@ const getErrorStackWithoutErrorMessage = (error) => {
213
341
  return stack
214
342
  }
215
343
 
216
- const formatTip = ({ reportedBy, requestedRessource }) => {
217
- if (reportedBy === "browser") {
218
- return `Reported by the browser while executing <code>${window.location.pathname}${window.location.search}</code>.`
219
- }
220
- return `Reported by the server while serving <code>${requestedRessource}</code>`
221
- }
222
-
223
344
  const makeLinksClickable = (string, { createLink = (url) => url }) => {
224
345
  // normalize line breaks
225
346
  string = string.replace(/\n/g, "\n")
@@ -2,8 +2,6 @@ import { formatError } from "./error_formatter.js"
2
2
 
3
3
  const JSENV_ERROR_OVERLAY_TAGNAME = "jsenv-error-overlay"
4
4
 
5
- let previousErrorInfo = null
6
-
7
5
  export const displayErrorInDocument = (
8
6
  error,
9
7
  {
@@ -14,30 +12,9 @@ export const displayErrorInDocument = (
14
12
  line,
15
13
  column,
16
14
  codeFrame,
17
- reportedBy,
18
- requestedRessource,
19
15
  },
20
16
  ) => {
21
- const nowMs = Date.now()
22
- // ensure error dispatched on window by browser is displayed first
23
- // then the server error replaces it (because it contains more information)
24
- if (previousErrorInfo) {
25
- const previousErrorReportedBy = previousErrorInfo.reportedBy
26
- const msEllapsedSincePreviousError = nowMs - previousErrorInfo.ms
27
- if (
28
- previousErrorReportedBy === "server" &&
29
- reportedBy === "browser" &&
30
- msEllapsedSincePreviousError < 50
31
- ) {
32
- return () => {}
33
- }
34
- }
35
- previousErrorInfo = {
36
- ms: nowMs,
37
- reportedBy,
38
- }
39
-
40
- const { theme, title, text, codeFramePromise, tip } = formatError(error, {
17
+ const { theme, title, text, tip, errorDetailsPromise } = formatError(error, {
41
18
  rootDirectoryUrl,
42
19
  errorBaseUrl,
43
20
  openInEditor,
@@ -45,16 +22,14 @@ export const displayErrorInDocument = (
45
22
  line,
46
23
  column,
47
24
  codeFrame,
48
- reportedBy,
49
- requestedRessource,
50
25
  })
51
26
 
52
27
  let jsenvErrorOverlay = new JsenvErrorOverlay({
53
28
  theme,
54
29
  title,
55
30
  text,
56
- codeFramePromise,
57
31
  tip,
32
+ errorDetailsPromise,
58
33
  })
59
34
  document.querySelectorAll(JSENV_ERROR_OVERLAY_TAGNAME).forEach((node) => {
60
35
  node.parentNode.removeChild(node)
@@ -77,7 +52,7 @@ export const displayErrorInDocument = (
77
52
  }
78
53
 
79
54
  class JsenvErrorOverlay extends HTMLElement {
80
- constructor({ theme, title, text, codeFramePromise, tip }) {
55
+ constructor({ theme, title, text, tip, errorDetailsPromise }) {
81
56
  super()
82
57
  this.root = this.attachShadow({ mode: "open" })
83
58
  this.root.innerHTML = `
@@ -102,16 +77,39 @@ class JsenvErrorOverlay extends HTMLElement {
102
77
  this.root.querySelector(".backdrop").onclick = null
103
78
  this.parentNode.removeChild(this)
104
79
  }
105
- if (codeFramePromise) {
106
- codeFramePromise.then((codeFrame) => {
107
- if (this.parentNode) {
80
+ if (errorDetailsPromise) {
81
+ errorDetailsPromise.then((errorDetails) => {
82
+ if (!errorDetails || !this.parentNode) {
83
+ return
84
+ }
85
+ const { codeFrame, cause } = errorDetails
86
+ if (codeFrame) {
108
87
  this.root.querySelector(".text").innerHTML += `\n\n${codeFrame}`
109
88
  }
89
+ if (cause) {
90
+ const causeIndented = prefixRemainingLines(cause, " ")
91
+ this.root.querySelector(
92
+ ".text",
93
+ ).innerHTML += `\n [cause]: ${causeIndented}`
94
+ }
110
95
  })
111
96
  }
112
97
  }
113
98
  }
114
99
 
100
+ const prefixRemainingLines = (text, prefix) => {
101
+ const lines = text.split(/\r?\n/)
102
+ const firstLine = lines.shift()
103
+ let result = firstLine
104
+ let i = 0
105
+ while (i < lines.length) {
106
+ const line = lines[i]
107
+ i++
108
+ result += line.length ? `\n${prefix}${line}` : `\n`
109
+ }
110
+ return result
111
+ }
112
+
115
113
  if (customElements && !customElements.get(JSENV_ERROR_OVERLAY_TAGNAME)) {
116
114
  customElements.define(JSENV_ERROR_OVERLAY_TAGNAME, JsenvErrorOverlay)
117
115
  }
@@ -88,11 +88,15 @@ export const installHtmlSupervisor = ({
88
88
  let completed
89
89
  let result
90
90
  let error
91
+ const urlObject = new URL(src, window.location)
92
+ if (reload) {
93
+ urlObject.searchParams.set("hmr", Date.now())
94
+ }
95
+ __html_supervisor__.currentExecution = {
96
+ type: type === "module" ? "dynamic_import" : "script_injection",
97
+ url: urlObject.href,
98
+ }
91
99
  try {
92
- const urlObject = new URL(src, window.location)
93
- if (reload) {
94
- urlObject.searchParams.set("hmr", Date.now())
95
- }
96
100
  result = await execute(urlObject.href)
97
101
  completed = true
98
102
  } catch (e) {
@@ -110,6 +114,7 @@ export const installHtmlSupervisor = ({
110
114
  console.log(`${type} load ended`)
111
115
  console.groupEnd()
112
116
  }
117
+ __html_supervisor__.currentExecution = null
113
118
  return
114
119
  }
115
120
  const executionResult = {
@@ -127,9 +132,7 @@ export const installHtmlSupervisor = ({
127
132
  }
128
133
  executionResult.error = error
129
134
  onExecutionSettled(src, executionResult)
130
- onExecutionError(executionResult, {
131
- currentScript,
132
- })
135
+ onExecutionError(executionResult, { currentScript })
133
136
  if (errorExposureInConsole) {
134
137
  if (typeof window.reportError === "function") {
135
138
  window.reportError(error)
@@ -140,6 +143,7 @@ export const installHtmlSupervisor = ({
140
143
  if (logs) {
141
144
  console.groupEnd()
142
145
  }
146
+ __html_supervisor__.currentExecution = null
143
147
  }
144
148
 
145
149
  const classicExecutionQueue = createExecutionQueue(performExecution)
@@ -175,7 +179,6 @@ export const installHtmlSupervisor = ({
175
179
  const useDeferQueue =
176
180
  scriptToExecute.defer || scriptToExecute.type === "module"
177
181
  if (useDeferQueue) {
178
- // defer must wait for classic script to be done
179
182
  const classicExecutionPromise = classicExecutionQueue.getPromise()
180
183
  if (classicExecutionPromise) {
181
184
  deferedExecutionQueue.waitFor(classicExecutionPromise)
@@ -220,76 +223,28 @@ export const installHtmlSupervisor = ({
220
223
  })
221
224
 
222
225
  if (errorOverlay) {
226
+ const onErrorReportedByBrowser = (error, { url, line, column }) => {
227
+ displayErrorInDocument(error, {
228
+ rootDirectoryUrl,
229
+ errorBaseUrl,
230
+ openInEditor,
231
+ url,
232
+ line,
233
+ column,
234
+ })
235
+ }
223
236
  window.addEventListener("error", (errorEvent) => {
224
237
  if (!errorEvent.isTrusted) {
225
238
  // ignore custom error event (not sent by browser)
226
239
  return
227
240
  }
228
241
  const { error, filename, lineno, colno } = errorEvent
229
- displayErrorInDocument(error, {
230
- rootDirectoryUrl,
231
- errorBaseUrl,
232
- openInEditor,
242
+ onErrorReportedByBrowser(error, {
233
243
  url: filename,
234
244
  line: lineno,
235
245
  column: colno,
236
- reportedBy: "browser",
237
246
  })
238
247
  })
239
- if (window.__server_events__) {
240
- const isExecuting = () => {
241
- if (pendingExecutionCount > 0) {
242
- return true
243
- }
244
- if (
245
- document.readyState === "loading" ||
246
- document.readyState === "interactive"
247
- ) {
248
- return true
249
- }
250
- if (window.__reloader__ && window.__reloader__.status === "reloading") {
251
- return true
252
- }
253
- return false
254
- }
255
- window.__server_events__.addEventCallbacks({
256
- error_while_serving_file: (serverErrorEvent) => {
257
- if (!isExecuting()) {
258
- return
259
- }
260
- const {
261
- message,
262
- stack,
263
- traceUrl,
264
- traceLine,
265
- traceColumn,
266
- traceMessage,
267
- requestedRessource,
268
- isFaviconAutoRequest,
269
- } = JSON.parse(serverErrorEvent.data)
270
- if (isFaviconAutoRequest) {
271
- return
272
- }
273
- displayErrorInDocument(
274
- {
275
- message,
276
- stack,
277
- },
278
- {
279
- rootDirectoryUrl,
280
- errorBaseUrl,
281
- openInEditor,
282
- url: traceUrl,
283
- line: traceLine,
284
- column: traceColumn,
285
- codeFrame: traceMessage,
286
- reportedBy: "server",
287
- requestedRessource,
288
- },
289
- )
290
- },
291
- })
292
- }
293
248
  }
294
249
  }
295
250
 
@@ -9,11 +9,12 @@ window.__html_supervisor__ = {
9
9
  window.__html_supervisor__.scriptsToExecute.push(scriptToExecute)
10
10
  },
11
11
  superviseScript: ({ src, isInline, crossorigin, integrity }) => {
12
+ const { currentScript } = document
12
13
  window.__html_supervisor__.addScriptToExecute({
13
14
  src,
14
15
  type: "js_classic",
15
16
  isInline,
16
- currentScript: document.currentScript,
17
+ currentScript,
17
18
  execute: (url) => {
18
19
  return new Promise((resolve, reject) => {
19
20
  const script = document.createElement("script")
@@ -50,7 +51,14 @@ window.__html_supervisor__ = {
50
51
  resolve()
51
52
  }
52
53
  })
53
- document.body.appendChild(script)
54
+ if (currentScript) {
55
+ currentScript.parentNode.insertBefore(
56
+ script,
57
+ currentScript.nextSibling,
58
+ )
59
+ } else {
60
+ document.body.appendChild(script)
61
+ }
54
62
  })
55
63
  },
56
64
  })