@jsenv/core 27.5.2 → 27.6.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.
- package/dist/js/autoreload.js +7 -0
- package/dist/js/html_supervisor_installer.js +275 -154
- package/dist/main.js +171 -109
- package/package.json +2 -2
- package/src/dev/start_dev_server.js +4 -0
- package/src/omega/kitchen.js +13 -15
- package/src/omega/omega_server.js +4 -0
- package/src/omega/server/file_service.js +9 -66
- package/src/omega/url_graph/url_graph_load.js +0 -2
- package/src/omega/url_graph/url_info_transformations.js +27 -0
- package/src/omega/url_graph.js +1 -0
- package/src/plugins/autoreload/client/autoreload.js +7 -0
- package/src/plugins/html_supervisor/client/error_formatter.js +187 -67
- package/src/plugins/html_supervisor/client/error_overlay.js +29 -10
- package/src/plugins/html_supervisor/client/html_supervisor_installer.js +25 -73
- package/src/plugins/html_supervisor/jsenv_plugin_html_supervisor.js +94 -25
- package/src/test/execute_test_plan.js +1 -2
|
@@ -34,17 +34,11 @@ export const createFileService = ({
|
|
|
34
34
|
cooldownBetweenFileEvents,
|
|
35
35
|
explorer,
|
|
36
36
|
sourcemaps,
|
|
37
|
+
sourcemapsSourcesProtocol,
|
|
38
|
+
sourcemapsSourcesContent,
|
|
37
39
|
writeGeneratedFiles,
|
|
38
40
|
}) => {
|
|
39
41
|
const jsenvDirectoryUrl = new URL(".jsenv/", rootDirectoryUrl).href
|
|
40
|
-
const onErrorWhileServingFileCallbacks = []
|
|
41
|
-
const onErrorWhileServingFile = (data) => {
|
|
42
|
-
onErrorWhileServingFileCallbacks.forEach(
|
|
43
|
-
(onErrorWhileServingFileCallback) => {
|
|
44
|
-
onErrorWhileServingFileCallback(data)
|
|
45
|
-
},
|
|
46
|
-
)
|
|
47
|
-
}
|
|
48
42
|
|
|
49
43
|
const clientFileChangeCallbackList = []
|
|
50
44
|
const clientFilesPruneCallbackList = []
|
|
@@ -113,6 +107,9 @@ export const createFileService = ({
|
|
|
113
107
|
rootDirectoryUrl,
|
|
114
108
|
scenario,
|
|
115
109
|
runtimeCompat,
|
|
110
|
+
clientRuntimeCompat: {
|
|
111
|
+
[runtimeName]: runtimeVersion,
|
|
112
|
+
},
|
|
116
113
|
urlGraph,
|
|
117
114
|
plugins: [
|
|
118
115
|
...plugins,
|
|
@@ -134,6 +131,8 @@ export const createFileService = ({
|
|
|
134
131
|
}),
|
|
135
132
|
],
|
|
136
133
|
sourcemaps,
|
|
134
|
+
sourcemapsSourcesProtocol,
|
|
135
|
+
sourcemapsSourcesContent,
|
|
137
136
|
writeGeneratedFiles,
|
|
138
137
|
})
|
|
139
138
|
serverStopCallbacks.push(() => {
|
|
@@ -170,28 +169,6 @@ export const createFileService = ({
|
|
|
170
169
|
},
|
|
171
170
|
})
|
|
172
171
|
})
|
|
173
|
-
onErrorWhileServingFileCallbacks.push((data) => {
|
|
174
|
-
serverEventsDispatcher.dispatchToRoomsMatching(
|
|
175
|
-
{
|
|
176
|
-
type: "error_while_serving_file",
|
|
177
|
-
data,
|
|
178
|
-
},
|
|
179
|
-
(room) => {
|
|
180
|
-
// send only to page depending on this file
|
|
181
|
-
const errorFileUrl = data.url
|
|
182
|
-
const roomEntryPointUrl = new URL(
|
|
183
|
-
room.request.ressource.slice(1),
|
|
184
|
-
rootDirectoryUrl,
|
|
185
|
-
).href
|
|
186
|
-
const isErrorRelatedToEntryPoint = Boolean(
|
|
187
|
-
urlGraph.findDependent(errorFileUrl, (dependentUrlInfo) => {
|
|
188
|
-
return dependentUrlInfo.url === roomEntryPointUrl
|
|
189
|
-
}),
|
|
190
|
-
)
|
|
191
|
-
return isErrorRelatedToEntryPoint
|
|
192
|
-
},
|
|
193
|
-
)
|
|
194
|
-
})
|
|
195
172
|
// "unshift" because serve must come first to catch event source client request
|
|
196
173
|
kitchen.pluginController.unshiftPlugin({
|
|
197
174
|
name: "jsenv:provide_server_events",
|
|
@@ -286,6 +263,7 @@ export const createFileService = ({
|
|
|
286
263
|
!urlInfo.isInline &&
|
|
287
264
|
urlInfo.type !== "sourcemap"
|
|
288
265
|
) {
|
|
266
|
+
urlInfo.error = null
|
|
289
267
|
urlInfo.sourcemap = null
|
|
290
268
|
urlInfo.sourcemapReference = null
|
|
291
269
|
urlInfo.content = null
|
|
@@ -298,9 +276,6 @@ export const createFileService = ({
|
|
|
298
276
|
await kitchen.cook(urlInfo, {
|
|
299
277
|
request,
|
|
300
278
|
reference,
|
|
301
|
-
clientRuntimeCompat: {
|
|
302
|
-
[runtimeName]: runtimeVersion,
|
|
303
|
-
},
|
|
304
279
|
outDirectoryUrl:
|
|
305
280
|
scenario === "dev"
|
|
306
281
|
? `${rootDirectoryUrl}.jsenv/${runtimeName}@${runtimeVersion}/`
|
|
@@ -333,18 +308,9 @@ export const createFileService = ({
|
|
|
333
308
|
)
|
|
334
309
|
return response
|
|
335
310
|
} catch (e) {
|
|
311
|
+
urlInfo.error = e
|
|
336
312
|
const code = e.code
|
|
337
313
|
if (code === "PARSE_ERROR") {
|
|
338
|
-
onErrorWhileServingFile({
|
|
339
|
-
requestedRessource: request.ressource,
|
|
340
|
-
code: "PARSE_ERROR",
|
|
341
|
-
message: e.reason,
|
|
342
|
-
url: e.url,
|
|
343
|
-
traceUrl: e.traceUrl,
|
|
344
|
-
traceLine: e.traceLine,
|
|
345
|
-
traceColumn: e.traceColumn,
|
|
346
|
-
traceMessage: e.traceMessage,
|
|
347
|
-
})
|
|
348
314
|
return {
|
|
349
315
|
url: reference.url,
|
|
350
316
|
status: 200, // let the browser re-throw the syntax error
|
|
@@ -375,19 +341,6 @@ export const createFileService = ({
|
|
|
375
341
|
}
|
|
376
342
|
}
|
|
377
343
|
if (code === "NOT_FOUND") {
|
|
378
|
-
onErrorWhileServingFile({
|
|
379
|
-
requestedRessource: request.ressource,
|
|
380
|
-
isFaviconAutoRequest:
|
|
381
|
-
request.ressource === "/favicon.ico" &&
|
|
382
|
-
reference.type === "http_request",
|
|
383
|
-
code: "NOT_FOUND",
|
|
384
|
-
message: e.reason,
|
|
385
|
-
url: e.url,
|
|
386
|
-
traceUrl: e.traceUrl,
|
|
387
|
-
traceLine: e.traceLine,
|
|
388
|
-
traceColumn: e.traceColumn,
|
|
389
|
-
traceMessage: e.traceMessage,
|
|
390
|
-
})
|
|
391
344
|
return {
|
|
392
345
|
url: reference.url,
|
|
393
346
|
status: 404,
|
|
@@ -395,16 +348,6 @@ export const createFileService = ({
|
|
|
395
348
|
statusMessage: e.message,
|
|
396
349
|
}
|
|
397
350
|
}
|
|
398
|
-
onErrorWhileServingFile({
|
|
399
|
-
requestedRessource: request.ressource,
|
|
400
|
-
code: "UNEXPECTED",
|
|
401
|
-
stack: e.stack,
|
|
402
|
-
url: e.url,
|
|
403
|
-
traceUrl: e.traceUrl,
|
|
404
|
-
traceLine: e.traceLine,
|
|
405
|
-
traceColumn: e.traceColumn,
|
|
406
|
-
traceMessage: e.traceMessage,
|
|
407
|
-
})
|
|
408
351
|
return {
|
|
409
352
|
url: reference.url,
|
|
410
353
|
status: 500,
|
|
@@ -7,7 +7,6 @@ export const loadUrlGraph = async ({
|
|
|
7
7
|
startLoading,
|
|
8
8
|
writeGeneratedFiles,
|
|
9
9
|
outDirectoryUrl,
|
|
10
|
-
clientRuntimeCompat,
|
|
11
10
|
}) => {
|
|
12
11
|
if (writeGeneratedFiles && outDirectoryUrl) {
|
|
13
12
|
await ensureEmptyDirectory(outDirectoryUrl)
|
|
@@ -19,7 +18,6 @@ export const loadUrlGraph = async ({
|
|
|
19
18
|
if (promiseFromData) return promiseFromData
|
|
20
19
|
const promise = _cook(urlInfo, {
|
|
21
20
|
outDirectoryUrl,
|
|
22
|
-
clientRuntimeCompat,
|
|
23
21
|
...context,
|
|
24
22
|
})
|
|
25
23
|
promises.push(promise)
|
|
@@ -11,12 +11,29 @@ import {
|
|
|
11
11
|
export const createUrlInfoTransformer = ({
|
|
12
12
|
logger,
|
|
13
13
|
sourcemaps,
|
|
14
|
+
sourcemapsSourcesProtocol,
|
|
14
15
|
sourcemapsSourcesContent,
|
|
15
16
|
sourcemapsRelativeSources,
|
|
16
17
|
urlGraph,
|
|
18
|
+
clientRuntimeCompat,
|
|
17
19
|
injectSourcemapPlaceholder,
|
|
18
20
|
foundSourcemap,
|
|
19
21
|
}) => {
|
|
22
|
+
const runtimeNames = Object.keys(clientRuntimeCompat)
|
|
23
|
+
const chromeAsSingleRuntime =
|
|
24
|
+
runtimeNames.length === 1 && runtimeNames[0] === "chrome"
|
|
25
|
+
if (sourcemapsSourcesProtocol === undefined) {
|
|
26
|
+
sourcemapsSourcesProtocol = "file:///"
|
|
27
|
+
}
|
|
28
|
+
if (sourcemapsSourcesContent === undefined) {
|
|
29
|
+
if (chromeAsSingleRuntime && sourcemapsSourcesProtocol === "file:///") {
|
|
30
|
+
// chrome is able to fetch source when referenced with "file:"
|
|
31
|
+
sourcemapsSourcesContent = false
|
|
32
|
+
} else {
|
|
33
|
+
sourcemapsSourcesContent = true
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
20
37
|
const sourcemapsEnabled =
|
|
21
38
|
sourcemaps === "inline" ||
|
|
22
39
|
sourcemaps === "file" ||
|
|
@@ -172,6 +189,16 @@ export const createUrlInfoTransformer = ({
|
|
|
172
189
|
return sourceRelative || "."
|
|
173
190
|
})
|
|
174
191
|
}
|
|
192
|
+
if (sourcemapsSourcesProtocol !== "file:///") {
|
|
193
|
+
sourcemap.sources = sourcemap.sources.map((source) => {
|
|
194
|
+
if (source.startsWith("file:///")) {
|
|
195
|
+
return `${sourcemapsSourcesProtocol}${source.slice(
|
|
196
|
+
"file:///".length,
|
|
197
|
+
)}`
|
|
198
|
+
}
|
|
199
|
+
return source
|
|
200
|
+
})
|
|
201
|
+
}
|
|
175
202
|
sourcemapUrlInfo.content = JSON.stringify(sourcemap, null, " ")
|
|
176
203
|
if (!urlInfo.sourcemapIsWrong) {
|
|
177
204
|
if (sourcemaps === "inline") {
|
package/src/omega/url_graph.js
CHANGED
|
@@ -15,6 +15,7 @@ const reloader = {
|
|
|
15
15
|
isAutoreloadEnabled,
|
|
16
16
|
setAutoreloadPreference,
|
|
17
17
|
status: "idle",
|
|
18
|
+
currentExecution: null,
|
|
18
19
|
onstatuschange: () => {},
|
|
19
20
|
setStatus: (status) => {
|
|
20
21
|
reloader.status = status
|
|
@@ -49,6 +50,7 @@ const reloader = {
|
|
|
49
50
|
promise.then(
|
|
50
51
|
() => {
|
|
51
52
|
onApplied(reloadMessage)
|
|
53
|
+
reloader.currentExecution = null
|
|
52
54
|
},
|
|
53
55
|
(e) => {
|
|
54
56
|
reloader.setStatus("failed")
|
|
@@ -61,6 +63,7 @@ const reloader = {
|
|
|
61
63
|
`[jsenv] Hot reload failed after ${reloadMessage.reason}.
|
|
62
64
|
This could be due to syntax errors or importing non-existent modules (see errors in console)`,
|
|
63
65
|
)
|
|
66
|
+
reloader.currentExecution = null
|
|
64
67
|
},
|
|
65
68
|
)
|
|
66
69
|
}
|
|
@@ -146,6 +149,10 @@ const applyHotReload = async ({ hotInstructions }) => {
|
|
|
146
149
|
await urlHotMeta.disposeCallback()
|
|
147
150
|
}
|
|
148
151
|
console.log(`importing js module`)
|
|
152
|
+
reloader.currentExecution = {
|
|
153
|
+
type: "dynamic_import",
|
|
154
|
+
url: urlToFetch,
|
|
155
|
+
}
|
|
149
156
|
const namespace = await reloadJsImport(urlToFetch)
|
|
150
157
|
if (urlHotMeta.acceptCallback) {
|
|
151
158
|
await urlHotMeta.acceptCallback(namespace)
|
|
@@ -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
|
|
17
|
-
let tip =
|
|
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
|
-
|
|
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
|
|
25
|
-
const
|
|
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 =
|
|
28
|
-
|
|
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
|
-
|
|
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,42 +103,82 @@ export const formatError = (
|
|
|
83
103
|
return textWithHtmlLinks
|
|
84
104
|
}
|
|
85
105
|
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
if (
|
|
89
|
-
|
|
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 (
|
|
92
|
-
|
|
117
|
+
if (codeFrame) {
|
|
118
|
+
text += `\n\n${generateClickableText(codeFrame)}`
|
|
93
119
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
120
|
+
return text
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const onErrorLocated = (urlSite) => {
|
|
124
|
+
errorUrlSite = urlSite
|
|
125
|
+
errorDetailsPromiseReference.current = (async () => {
|
|
126
|
+
if (errorMeta.type === "dynamic_import_fetch_error") {
|
|
127
|
+
const response = await window.fetch(
|
|
128
|
+
`/__get_error_cause__/${
|
|
129
|
+
urlSite.isInline ? urlSite.originalUrl : urlSite.url
|
|
130
|
+
}`,
|
|
131
|
+
)
|
|
132
|
+
if (response.status !== 200) {
|
|
133
|
+
return null
|
|
134
|
+
}
|
|
135
|
+
const causeInfo = await response.json()
|
|
136
|
+
if (!causeInfo) {
|
|
137
|
+
return null
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const causeText =
|
|
141
|
+
causeInfo.code === "NOT_FOUND"
|
|
142
|
+
? formatErrorText({
|
|
143
|
+
message: causeInfo.reason,
|
|
144
|
+
stack: causeInfo.codeFrame,
|
|
145
|
+
})
|
|
146
|
+
: formatErrorText({
|
|
147
|
+
message: causeInfo.stack,
|
|
148
|
+
stack: causeInfo.codeFrame,
|
|
149
|
+
})
|
|
150
|
+
return {
|
|
151
|
+
cause: causeText,
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (urlSite.line !== undefined) {
|
|
155
|
+
let ressourceToFetch = `/__get_code_frame__/${formatUrlWithLineAndColumn(
|
|
156
|
+
urlSite,
|
|
157
|
+
)}`
|
|
158
|
+
if (!Error.captureStackTrace) {
|
|
159
|
+
ressourceToFetch += `?remap`
|
|
160
|
+
}
|
|
161
|
+
const response = await window.fetch(ressourceToFetch)
|
|
162
|
+
const codeFrame = await response.text()
|
|
163
|
+
return {
|
|
164
|
+
codeFrame: formatErrorText({ message: codeFrame }),
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return null
|
|
101
168
|
})()
|
|
102
169
|
}
|
|
103
170
|
|
|
104
171
|
// error.stack is more reliable than url/line/column reported on window error events
|
|
105
172
|
// so use it only when error.stack is not available
|
|
106
|
-
if (
|
|
173
|
+
if (
|
|
174
|
+
url &&
|
|
175
|
+
!stack &&
|
|
176
|
+
// ignore window.reportError() it gives no valuable info
|
|
177
|
+
!url.endsWith("html_supervisor_installer.js")
|
|
178
|
+
) {
|
|
107
179
|
onErrorLocated(resolveUrlSite({ url, line, column }))
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
let text
|
|
111
|
-
|
|
112
|
-
if (message && stack) {
|
|
113
|
-
text = `${generateClickableText(message)}\n${generateClickableText(stack)}`
|
|
114
|
-
} else if (stack) {
|
|
115
|
-
text = generateClickableText(stack)
|
|
116
|
-
} else {
|
|
117
|
-
text = generateClickableText(message)
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (codeFrame) {
|
|
121
|
-
text += `\n\n${generateClickableText(codeFrame)}`
|
|
180
|
+
} else if (errorMeta.url) {
|
|
181
|
+
onErrorLocated(resolveUrlSite(errorMeta))
|
|
122
182
|
}
|
|
123
183
|
|
|
124
184
|
return {
|
|
@@ -127,12 +187,79 @@ export const formatError = (
|
|
|
127
187
|
? "light"
|
|
128
188
|
: "dark",
|
|
129
189
|
title: "An error occured",
|
|
130
|
-
text,
|
|
131
|
-
codeFramePromise: codeFramePromiseReference.current,
|
|
190
|
+
text: formatErrorText({ message, stack }),
|
|
132
191
|
tip: `${tip}
|
|
133
192
|
<br />
|
|
134
193
|
Click outside to close.`,
|
|
194
|
+
errorDetailsPromise: errorDetailsPromiseReference.current,
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const extractErrorMeta = (error, { line }) => {
|
|
199
|
+
if (!error) {
|
|
200
|
+
return {}
|
|
201
|
+
}
|
|
202
|
+
const { message } = error
|
|
203
|
+
if (!message) {
|
|
204
|
+
return {}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export_missing: {
|
|
208
|
+
// chrome
|
|
209
|
+
if (message.includes("does not provide an export named")) {
|
|
210
|
+
return {
|
|
211
|
+
type: "dynamic_import_export_missing",
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// firefox
|
|
215
|
+
if (message.startsWith("import not found:")) {
|
|
216
|
+
return {
|
|
217
|
+
type: "dynamic_import_export_missing",
|
|
218
|
+
browser: "firefox",
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// safari
|
|
222
|
+
if (message.startsWith("import binding name")) {
|
|
223
|
+
return {
|
|
224
|
+
type: "dynamic_import_export_missing",
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
js_syntax_error: {
|
|
230
|
+
if (error.name === "SyntaxError" && typeof line === "number") {
|
|
231
|
+
return {
|
|
232
|
+
type: "dynamic_import_syntax_error",
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
fetch_error: {
|
|
238
|
+
// chrome
|
|
239
|
+
if (message.startsWith("Failed to fetch dynamically imported module: ")) {
|
|
240
|
+
const url = error.message.slice(
|
|
241
|
+
"Failed to fetch dynamically imported module: ".length,
|
|
242
|
+
)
|
|
243
|
+
return {
|
|
244
|
+
type: "dynamic_import_fetch_error",
|
|
245
|
+
url,
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// firefox
|
|
249
|
+
if (message === "error loading dynamically imported module") {
|
|
250
|
+
return {
|
|
251
|
+
type: "dynamic_import_fetch_error",
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// safari
|
|
255
|
+
if (message === "Importing a module script failed.") {
|
|
256
|
+
return {
|
|
257
|
+
type: "dynamic_import_fetch_error",
|
|
258
|
+
}
|
|
259
|
+
}
|
|
135
260
|
}
|
|
261
|
+
|
|
262
|
+
return {}
|
|
136
263
|
}
|
|
137
264
|
|
|
138
265
|
const formatUrlWithLineAndColumn = ({ url, line, column }) => {
|
|
@@ -208,13 +335,6 @@ const getErrorStackWithoutErrorMessage = (error) => {
|
|
|
208
335
|
return stack
|
|
209
336
|
}
|
|
210
337
|
|
|
211
|
-
const formatTip = ({ reportedBy, requestedRessource }) => {
|
|
212
|
-
if (reportedBy === "browser") {
|
|
213
|
-
return `Reported by the browser while executing <code>${window.location.pathname}${window.location.search}</code>.`
|
|
214
|
-
}
|
|
215
|
-
return `Reported by the server while serving <code>${requestedRessource}</code>`
|
|
216
|
-
}
|
|
217
|
-
|
|
218
338
|
const makeLinksClickable = (string, { createLink = (url) => url }) => {
|
|
219
339
|
// normalize line breaks
|
|
220
340
|
string = string.replace(/\n/g, "\n")
|
|
@@ -12,11 +12,9 @@ export const displayErrorInDocument = (
|
|
|
12
12
|
line,
|
|
13
13
|
column,
|
|
14
14
|
codeFrame,
|
|
15
|
-
reportedBy,
|
|
16
|
-
requestedRessource,
|
|
17
15
|
},
|
|
18
16
|
) => {
|
|
19
|
-
const { theme, title, text,
|
|
17
|
+
const { theme, title, text, tip, errorDetailsPromise } = formatError(error, {
|
|
20
18
|
rootDirectoryUrl,
|
|
21
19
|
errorBaseUrl,
|
|
22
20
|
openInEditor,
|
|
@@ -24,16 +22,14 @@ export const displayErrorInDocument = (
|
|
|
24
22
|
line,
|
|
25
23
|
column,
|
|
26
24
|
codeFrame,
|
|
27
|
-
reportedBy,
|
|
28
|
-
requestedRessource,
|
|
29
25
|
})
|
|
30
26
|
|
|
31
27
|
let jsenvErrorOverlay = new JsenvErrorOverlay({
|
|
32
28
|
theme,
|
|
33
29
|
title,
|
|
34
30
|
text,
|
|
35
|
-
codeFramePromise,
|
|
36
31
|
tip,
|
|
32
|
+
errorDetailsPromise,
|
|
37
33
|
})
|
|
38
34
|
document.querySelectorAll(JSENV_ERROR_OVERLAY_TAGNAME).forEach((node) => {
|
|
39
35
|
node.parentNode.removeChild(node)
|
|
@@ -56,7 +52,7 @@ export const displayErrorInDocument = (
|
|
|
56
52
|
}
|
|
57
53
|
|
|
58
54
|
class JsenvErrorOverlay extends HTMLElement {
|
|
59
|
-
constructor({ theme, title, text,
|
|
55
|
+
constructor({ theme, title, text, tip, errorDetailsPromise }) {
|
|
60
56
|
super()
|
|
61
57
|
this.root = this.attachShadow({ mode: "open" })
|
|
62
58
|
this.root.innerHTML = `
|
|
@@ -81,16 +77,39 @@ class JsenvErrorOverlay extends HTMLElement {
|
|
|
81
77
|
this.root.querySelector(".backdrop").onclick = null
|
|
82
78
|
this.parentNode.removeChild(this)
|
|
83
79
|
}
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
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) {
|
|
87
87
|
this.root.querySelector(".text").innerHTML += `\n\n${codeFrame}`
|
|
88
88
|
}
|
|
89
|
+
if (cause) {
|
|
90
|
+
const causeIndented = prefixRemainingLines(cause, " ")
|
|
91
|
+
this.root.querySelector(
|
|
92
|
+
".text",
|
|
93
|
+
).innerHTML += `\n [cause]: ${causeIndented}`
|
|
94
|
+
}
|
|
89
95
|
})
|
|
90
96
|
}
|
|
91
97
|
}
|
|
92
98
|
}
|
|
93
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
|
+
|
|
94
113
|
if (customElements && !customElements.get(JSENV_ERROR_OVERLAY_TAGNAME)) {
|
|
95
114
|
customElements.define(JSENV_ERROR_OVERLAY_TAGNAME, JsenvErrorOverlay)
|
|
96
115
|
}
|