@jsenv/core 27.5.3 → 27.6.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.
- package/dist/js/autoreload.js +7 -0
- package/dist/js/html_supervisor_installer.js +259 -162
- package/dist/main.js +142 -101
- 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 +174 -66
- package/src/plugins/html_supervisor/client/error_overlay.js +29 -31
- package/src/plugins/html_supervisor/client/error_site_remap.js +85 -0
- package/src/plugins/html_supervisor/client/html_supervisor_installer.js +21 -63
- package/src/plugins/html_supervisor/jsenv_plugin_html_supervisor.js +70 -19
|
@@ -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,59 @@
|
|
|
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
|
-
const inlineUrlMatch = url.match(
|
|
13
|
+
const inlineUrlMatch = url.match(
|
|
14
|
+
/@L([0-9]+)C([0-9]+)\-L([0-9]+)C([0-9]+)(\.[\w]+)$/,
|
|
15
|
+
)
|
|
22
16
|
if (inlineUrlMatch) {
|
|
23
17
|
const htmlUrl = url.slice(0, inlineUrlMatch.index)
|
|
24
|
-
const
|
|
25
|
-
const
|
|
18
|
+
const tagLineStart = parseInt(inlineUrlMatch[1])
|
|
19
|
+
const tagColumnStart = parseInt(inlineUrlMatch[2])
|
|
20
|
+
const tagLineEnd = parseInt(inlineUrlMatch[3])
|
|
21
|
+
const tagColumnEnd = parseInt(inlineUrlMatch[4])
|
|
22
|
+
const extension = inlineUrlMatch[5]
|
|
26
23
|
url = htmlUrl
|
|
27
|
-
line =
|
|
28
|
-
|
|
24
|
+
line = tagLineStart + (line === undefined ? 0 : parseInt(line))
|
|
25
|
+
// stackTrace formatted by V8 (chrome)
|
|
26
|
+
if (Error.captureStackTrace) {
|
|
27
|
+
line--
|
|
28
|
+
}
|
|
29
|
+
if (errorMeta.type === "dynamic_import_syntax_error") {
|
|
30
|
+
// syntax error on inline script need line-1 for some reason
|
|
31
|
+
if (Error.captureStackTrace) {
|
|
32
|
+
line--
|
|
33
|
+
} else {
|
|
34
|
+
// firefox and safari need line-2
|
|
35
|
+
line -= 2
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
column = tagColumnStart + (column === undefined ? 0 : parseInt(column))
|
|
39
|
+
const fileUrl = resolveFileUrl(url)
|
|
40
|
+
return {
|
|
41
|
+
isInline: true,
|
|
42
|
+
originalUrl: `${fileUrl}@L${tagLineStart}C${tagColumnStart}-L${tagLineEnd}C${tagColumnEnd}${extension}`,
|
|
43
|
+
url: fileUrl,
|
|
44
|
+
line,
|
|
45
|
+
column,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
isInline: false,
|
|
50
|
+
url: resolveFileUrl(url),
|
|
51
|
+
line,
|
|
52
|
+
column,
|
|
29
53
|
}
|
|
54
|
+
}
|
|
30
55
|
|
|
56
|
+
const resolveFileUrl = (url) => {
|
|
31
57
|
let urlObject = new URL(url)
|
|
32
58
|
if (urlObject.origin === window.origin) {
|
|
33
59
|
urlObject = new URL(
|
|
@@ -39,19 +65,10 @@ export const formatError = (
|
|
|
39
65
|
const atFsIndex = urlObject.pathname.indexOf("/@fs/")
|
|
40
66
|
if (atFsIndex > -1) {
|
|
41
67
|
const afterAtFs = urlObject.pathname.slice(atFsIndex + "/@fs/".length)
|
|
42
|
-
|
|
43
|
-
} else {
|
|
44
|
-
url = urlObject.href
|
|
68
|
+
return new URL(afterAtFs, "file:///").href
|
|
45
69
|
}
|
|
46
|
-
} else {
|
|
47
|
-
url = urlObject.href
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
url,
|
|
52
|
-
line,
|
|
53
|
-
column,
|
|
54
70
|
}
|
|
71
|
+
return urlObject.href
|
|
55
72
|
}
|
|
56
73
|
|
|
57
74
|
const generateClickableText = (text) => {
|
|
@@ -59,7 +76,7 @@ export const formatError = (
|
|
|
59
76
|
createLink: (url, { line, column }) => {
|
|
60
77
|
const urlSite = resolveUrlSite({ url, line, column })
|
|
61
78
|
if (!errorUrlSite && text === stack) {
|
|
62
|
-
onErrorLocated(urlSite)
|
|
79
|
+
onErrorLocated(urlSite, "error.stack")
|
|
63
80
|
}
|
|
64
81
|
if (errorBaseUrl) {
|
|
65
82
|
if (urlSite.url.startsWith(rootDirectoryUrl)) {
|
|
@@ -83,21 +100,64 @@ export const formatError = (
|
|
|
83
100
|
return textWithHtmlLinks
|
|
84
101
|
}
|
|
85
102
|
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
if (
|
|
89
|
-
|
|
103
|
+
const formatErrorText = ({ message, stack, codeFrame }) => {
|
|
104
|
+
let text
|
|
105
|
+
if (message && stack) {
|
|
106
|
+
text = `${generateClickableText(message)}\n${generateClickableText(
|
|
107
|
+
stack,
|
|
108
|
+
)}`
|
|
109
|
+
} else if (stack) {
|
|
110
|
+
text = generateClickableText(stack)
|
|
111
|
+
} else {
|
|
112
|
+
text = generateClickableText(message)
|
|
90
113
|
}
|
|
91
|
-
if (
|
|
92
|
-
|
|
114
|
+
if (codeFrame) {
|
|
115
|
+
text += `\n\n${generateClickableText(codeFrame)}`
|
|
93
116
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
117
|
+
return text
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const onErrorLocated = (urlSite) => {
|
|
121
|
+
errorUrlSite = urlSite
|
|
122
|
+
errorDetailsPromiseReference.current = (async () => {
|
|
123
|
+
if (errorMeta.type === "dynamic_import_fetch_error") {
|
|
124
|
+
const response = await window.fetch(
|
|
125
|
+
`/__get_error_cause__/${
|
|
126
|
+
urlSite.isInline ? urlSite.originalUrl : urlSite.url
|
|
127
|
+
}`,
|
|
128
|
+
)
|
|
129
|
+
if (response.status !== 200) {
|
|
130
|
+
return null
|
|
131
|
+
}
|
|
132
|
+
const causeInfo = await response.json()
|
|
133
|
+
if (!causeInfo) {
|
|
134
|
+
return null
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const causeText =
|
|
138
|
+
causeInfo.code === "NOT_FOUND"
|
|
139
|
+
? formatErrorText({
|
|
140
|
+
message: causeInfo.reason,
|
|
141
|
+
stack: causeInfo.codeFrame,
|
|
142
|
+
})
|
|
143
|
+
: formatErrorText({
|
|
144
|
+
message: causeInfo.stack,
|
|
145
|
+
stack: causeInfo.codeFrame,
|
|
146
|
+
})
|
|
147
|
+
return {
|
|
148
|
+
cause: causeText,
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (urlSite.line !== undefined) {
|
|
152
|
+
const response = await window.fetch(
|
|
153
|
+
`/__get_code_frame__/${formatUrlWithLineAndColumn(urlSite)}`,
|
|
154
|
+
)
|
|
155
|
+
const codeFrame = await response.text()
|
|
156
|
+
return {
|
|
157
|
+
codeFrame: formatErrorText({ message: codeFrame }),
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return null
|
|
101
161
|
})()
|
|
102
162
|
}
|
|
103
163
|
|
|
@@ -110,20 +170,8 @@ export const formatError = (
|
|
|
110
170
|
!url.endsWith("html_supervisor_installer.js")
|
|
111
171
|
) {
|
|
112
172
|
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)}`
|
|
173
|
+
} else if (errorMeta.url) {
|
|
174
|
+
onErrorLocated(resolveUrlSite(errorMeta))
|
|
127
175
|
}
|
|
128
176
|
|
|
129
177
|
return {
|
|
@@ -132,14 +180,81 @@ export const formatError = (
|
|
|
132
180
|
? "light"
|
|
133
181
|
: "dark",
|
|
134
182
|
title: "An error occured",
|
|
135
|
-
text,
|
|
136
|
-
codeFramePromise: codeFramePromiseReference.current,
|
|
183
|
+
text: formatErrorText({ message, stack }),
|
|
137
184
|
tip: `${tip}
|
|
138
185
|
<br />
|
|
139
186
|
Click outside to close.`,
|
|
187
|
+
errorDetailsPromise: errorDetailsPromiseReference.current,
|
|
140
188
|
}
|
|
141
189
|
}
|
|
142
190
|
|
|
191
|
+
const extractErrorMeta = (error, { line }) => {
|
|
192
|
+
if (!error) {
|
|
193
|
+
return {}
|
|
194
|
+
}
|
|
195
|
+
const { message } = error
|
|
196
|
+
if (!message) {
|
|
197
|
+
return {}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export_missing: {
|
|
201
|
+
// chrome
|
|
202
|
+
if (message.includes("does not provide an export named")) {
|
|
203
|
+
return {
|
|
204
|
+
type: "dynamic_import_export_missing",
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// firefox
|
|
208
|
+
if (message.startsWith("import not found:")) {
|
|
209
|
+
return {
|
|
210
|
+
type: "dynamic_import_export_missing",
|
|
211
|
+
browser: "firefox",
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// safari
|
|
215
|
+
if (message.startsWith("import binding name")) {
|
|
216
|
+
return {
|
|
217
|
+
type: "dynamic_import_export_missing",
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
js_syntax_error: {
|
|
223
|
+
if (error.name === "SyntaxError" && typeof line === "number") {
|
|
224
|
+
return {
|
|
225
|
+
type: "dynamic_import_syntax_error",
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
fetch_error: {
|
|
231
|
+
// chrome
|
|
232
|
+
if (message.startsWith("Failed to fetch dynamically imported module: ")) {
|
|
233
|
+
const url = error.message.slice(
|
|
234
|
+
"Failed to fetch dynamically imported module: ".length,
|
|
235
|
+
)
|
|
236
|
+
return {
|
|
237
|
+
type: "dynamic_import_fetch_error",
|
|
238
|
+
url,
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// firefox
|
|
242
|
+
if (message === "error loading dynamically imported module") {
|
|
243
|
+
return {
|
|
244
|
+
type: "dynamic_import_fetch_error",
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// safari
|
|
248
|
+
if (message === "Importing a module script failed.") {
|
|
249
|
+
return {
|
|
250
|
+
type: "dynamic_import_fetch_error",
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return {}
|
|
256
|
+
}
|
|
257
|
+
|
|
143
258
|
const formatUrlWithLineAndColumn = ({ url, line, column }) => {
|
|
144
259
|
return line === undefined && column === undefined
|
|
145
260
|
? url
|
|
@@ -213,13 +328,6 @@ const getErrorStackWithoutErrorMessage = (error) => {
|
|
|
213
328
|
return stack
|
|
214
329
|
}
|
|
215
330
|
|
|
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
331
|
const makeLinksClickable = (string, { createLink = (url) => url }) => {
|
|
224
332
|
// normalize line breaks
|
|
225
333
|
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
|
|
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,
|
|
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 (
|
|
106
|
-
|
|
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
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { memoize } from "@jsenv/utils/src/memoize/memoize.js"
|
|
2
|
+
import { remapSourcePosition } from "@jsenv/sourcemap/src/error_stack_remap/remap_source_position.js"
|
|
3
|
+
import { SOURCEMAP } from "@jsenv/sourcemap/src/sourcemap_comment.js"
|
|
4
|
+
import { DATA_URL } from "@jsenv/urls/src/data_url.js"
|
|
5
|
+
|
|
6
|
+
const loadSourceMapConsumer = memoize(async () => {
|
|
7
|
+
const script = document.createElement("script")
|
|
8
|
+
script.src = "https://unpkg.com/source-map@0.7.3/dist/source-map.js"
|
|
9
|
+
|
|
10
|
+
const scriptLoadedPromise = new Promise((resolve) => {
|
|
11
|
+
script.onload = resolve
|
|
12
|
+
})
|
|
13
|
+
document.head.appendChild(script)
|
|
14
|
+
await scriptLoadedPromise
|
|
15
|
+
const { SourceMapConsumer } = window.sourceMap
|
|
16
|
+
await SourceMapConsumer.initialize({
|
|
17
|
+
"lib/mappings.wasm": "https://unpkg.com/source-map@0.7.3/lib/mappings.wasm",
|
|
18
|
+
})
|
|
19
|
+
return SourceMapConsumer
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
export const remapErrorSite = async ({ url, line, column }) => {
|
|
23
|
+
const asServerUrl = (url) => {
|
|
24
|
+
if (url.startsWith("file:///")) {
|
|
25
|
+
url = `${window.origin}/@fs/${url.slice("file:///".length)}`
|
|
26
|
+
}
|
|
27
|
+
return url
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const SourceMapConsumer = await loadSourceMapConsumer()
|
|
31
|
+
const original = await remapSourcePosition({
|
|
32
|
+
source: url,
|
|
33
|
+
line,
|
|
34
|
+
column,
|
|
35
|
+
resolveFile: (specifier) => new URL(specifier, `${window.origin}/`).href,
|
|
36
|
+
urlToSourcemapConsumer: async (url) => {
|
|
37
|
+
const serverUrl = asServerUrl(url)
|
|
38
|
+
const fileResponse = await window.fetch(serverUrl)
|
|
39
|
+
const text = await fileResponse.text()
|
|
40
|
+
const jsSourcemapComment = SOURCEMAP.readComment({
|
|
41
|
+
contentType: "text/javascript",
|
|
42
|
+
content: text,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const jsSourcemapUrl = jsSourcemapComment.specifier
|
|
46
|
+
let sourcemapUrl
|
|
47
|
+
let sourcemapUrlContent
|
|
48
|
+
if (jsSourcemapUrl.startsWith("data:")) {
|
|
49
|
+
sourcemapUrl = url
|
|
50
|
+
sourcemapUrlContent = window.atob(DATA_URL.parse(jsSourcemapUrl).data)
|
|
51
|
+
} else {
|
|
52
|
+
sourcemapUrl = new URL(jsSourcemapUrl, url).href
|
|
53
|
+
const sourcemapResponse = await window.fetch(sourcemapUrl)
|
|
54
|
+
sourcemapUrlContent = await sourcemapResponse.text()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const sourceMap = JSON.parse(sourcemapUrlContent)
|
|
58
|
+
let { sourcesContent } = sourceMap
|
|
59
|
+
if (!sourcesContent) {
|
|
60
|
+
sourcesContent = []
|
|
61
|
+
sourceMap.sourcesContent = sourcesContent
|
|
62
|
+
}
|
|
63
|
+
let firstSourceMapSourceFailure = null
|
|
64
|
+
await Promise.all(
|
|
65
|
+
sourceMap.sources.map(async (source, index) => {
|
|
66
|
+
if (index in sourcesContent) return
|
|
67
|
+
let sourceUrl = new URL(source, sourcemapUrl).href
|
|
68
|
+
sourceUrl = asServerUrl(sourceUrl)
|
|
69
|
+
try {
|
|
70
|
+
const sourceResponse = await window.fetch(sourceUrl)
|
|
71
|
+
const sourceContent = await sourceResponse.text()
|
|
72
|
+
sourcesContent[index] = sourceContent
|
|
73
|
+
} catch (e) {
|
|
74
|
+
firstSourceMapSourceFailure = e
|
|
75
|
+
}
|
|
76
|
+
}),
|
|
77
|
+
)
|
|
78
|
+
if (firstSourceMapSourceFailure) {
|
|
79
|
+
return null
|
|
80
|
+
}
|
|
81
|
+
return new SourceMapConsumer(sourceMap)
|
|
82
|
+
},
|
|
83
|
+
})
|
|
84
|
+
return original
|
|
85
|
+
}
|