@jsenv/core 27.5.1 → 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 +524 -344
- package/dist/main.js +179 -95
- package/package.json +3 -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 +413 -0
- package/src/plugins/html_supervisor/client/error_overlay.js +62 -272
- package/src/plugins/html_supervisor/client/error_site_remap.js +85 -0
- package/src/plugins/html_supervisor/client/html_supervisor_installer.js +26 -77
- package/src/plugins/html_supervisor/jsenv_plugin_html_supervisor.js +103 -14
- package/src/test/execute_test_plan.js +1 -2
|
@@ -10,213 +10,308 @@ const unevalException = value => {
|
|
|
10
10
|
});
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
const
|
|
14
|
-
const displayErrorInDocument = (error, {
|
|
13
|
+
const formatError = (error, {
|
|
15
14
|
rootDirectoryUrl,
|
|
15
|
+
errorBaseUrl,
|
|
16
16
|
openInEditor,
|
|
17
17
|
url,
|
|
18
18
|
line,
|
|
19
|
-
column
|
|
20
|
-
reportedBy,
|
|
21
|
-
requestedRessource
|
|
19
|
+
column
|
|
22
20
|
}) => {
|
|
23
|
-
|
|
24
|
-
node.parentNode.removeChild(node);
|
|
25
|
-
});
|
|
26
|
-
const {
|
|
27
|
-
theme,
|
|
28
|
-
title,
|
|
21
|
+
let {
|
|
29
22
|
message,
|
|
30
|
-
stack
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
stack
|
|
24
|
+
} = normalizeErrorParts(error);
|
|
25
|
+
let errorDetailsPromiseReference = {
|
|
26
|
+
current: null
|
|
27
|
+
};
|
|
28
|
+
let tip = `Reported by the browser while executing <code>${window.location.pathname}${window.location.search}</code>.`;
|
|
29
|
+
let errorUrlSite;
|
|
30
|
+
const errorMeta = extractErrorMeta(error, {
|
|
33
31
|
url,
|
|
34
32
|
line,
|
|
35
|
-
column
|
|
36
|
-
reportedBy,
|
|
37
|
-
requestedRessource
|
|
38
|
-
});
|
|
39
|
-
let jsenvErrorOverlay = new JsenvErrorOverlay({
|
|
40
|
-
theme,
|
|
41
|
-
title,
|
|
42
|
-
text: createErrorText({
|
|
43
|
-
rootDirectoryUrl,
|
|
44
|
-
openInEditor,
|
|
45
|
-
message,
|
|
46
|
-
stack
|
|
47
|
-
}),
|
|
48
|
-
tip
|
|
33
|
+
column
|
|
49
34
|
});
|
|
50
|
-
document.body.appendChild(jsenvErrorOverlay);
|
|
51
35
|
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
36
|
+
const resolveUrlSite = ({
|
|
37
|
+
url,
|
|
38
|
+
line,
|
|
39
|
+
column
|
|
40
|
+
}) => {
|
|
41
|
+
const inlineUrlMatch = url.match(/@L([0-9]+)C([0-9]+)\-L([0-9]+)C([0-9]+)(\.[\w]+)$/);
|
|
42
|
+
|
|
43
|
+
if (inlineUrlMatch) {
|
|
44
|
+
const htmlUrl = url.slice(0, inlineUrlMatch.index);
|
|
45
|
+
const tagLineStart = parseInt(inlineUrlMatch[1]);
|
|
46
|
+
const tagColumnStart = parseInt(inlineUrlMatch[2]);
|
|
47
|
+
const tagLineEnd = parseInt(inlineUrlMatch[3]);
|
|
48
|
+
const tagColumnEnd = parseInt(inlineUrlMatch[4]);
|
|
49
|
+
const extension = inlineUrlMatch[5];
|
|
50
|
+
url = htmlUrl;
|
|
51
|
+
line = tagLineStart + (line === undefined ? 0 : parseInt(line)); // stackTrace formatted by V8 (chrome)
|
|
52
|
+
|
|
53
|
+
if (Error.captureStackTrace) {
|
|
54
|
+
line--;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (errorMeta.type === "dynamic_import_syntax_error") {
|
|
58
|
+
// syntax error on inline script need line-1 for some reason
|
|
59
|
+
if (Error.captureStackTrace) {
|
|
60
|
+
line--;
|
|
61
|
+
} else {
|
|
62
|
+
// firefox and safari need line-2
|
|
63
|
+
line -= 2;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
column = tagColumnStart + (column === undefined ? 0 : parseInt(column));
|
|
68
|
+
const fileUrl = resolveFileUrl(url);
|
|
69
|
+
return {
|
|
70
|
+
isInline: true,
|
|
71
|
+
originalUrl: `${fileUrl}@L${tagLineStart}C${tagColumnStart}-L${tagLineEnd}C${tagColumnEnd}${extension}`,
|
|
72
|
+
url: fileUrl,
|
|
73
|
+
line,
|
|
74
|
+
column
|
|
75
|
+
};
|
|
56
76
|
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
isInline: false,
|
|
80
|
+
url: resolveFileUrl(url),
|
|
81
|
+
line,
|
|
82
|
+
column
|
|
83
|
+
};
|
|
57
84
|
};
|
|
58
85
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
86
|
+
const resolveFileUrl = url => {
|
|
87
|
+
let urlObject = new URL(url);
|
|
88
|
+
|
|
89
|
+
if (urlObject.origin === window.origin) {
|
|
90
|
+
urlObject = new URL(`${urlObject.pathname.slice(1)}${urlObject.search}`, rootDirectoryUrl);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (urlObject.href.startsWith("file:")) {
|
|
94
|
+
const atFsIndex = urlObject.pathname.indexOf("/@fs/");
|
|
95
|
+
|
|
96
|
+
if (atFsIndex > -1) {
|
|
97
|
+
const afterAtFs = urlObject.pathname.slice(atFsIndex + "/@fs/".length);
|
|
98
|
+
return new URL(afterAtFs, "file:///").href;
|
|
63
99
|
}
|
|
64
|
-
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return urlObject.href;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const generateClickableText = text => {
|
|
106
|
+
const textWithHtmlLinks = makeLinksClickable(text, {
|
|
107
|
+
createLink: (url, {
|
|
108
|
+
line,
|
|
109
|
+
column
|
|
110
|
+
}) => {
|
|
111
|
+
const urlSite = resolveUrlSite({
|
|
112
|
+
url,
|
|
113
|
+
line,
|
|
114
|
+
column
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if (!errorUrlSite && text === stack) {
|
|
118
|
+
onErrorLocated(urlSite);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (errorBaseUrl) {
|
|
122
|
+
if (urlSite.url.startsWith(rootDirectoryUrl)) {
|
|
123
|
+
urlSite.url = `${errorBaseUrl}${urlSite.url.slice(rootDirectoryUrl.length)}`;
|
|
124
|
+
} else {
|
|
125
|
+
urlSite.url = "file:///mocked_for_snapshots";
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const urlWithLineAndColumn = formatUrlWithLineAndColumn(urlSite);
|
|
130
|
+
return {
|
|
131
|
+
href: url.startsWith("file:") && openInEditor ? `javascript:window.fetch('/__open_in_editor__/${urlWithLineAndColumn}')` : urlSite.url,
|
|
132
|
+
text: urlWithLineAndColumn
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
return textWithHtmlLinks;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const formatErrorText = ({
|
|
140
|
+
message,
|
|
141
|
+
stack,
|
|
142
|
+
codeFrame
|
|
143
|
+
}) => {
|
|
144
|
+
let text;
|
|
145
|
+
|
|
146
|
+
if (message && stack) {
|
|
147
|
+
text = `${generateClickableText(message)}\n${generateClickableText(stack)}`;
|
|
148
|
+
} else if (stack) {
|
|
149
|
+
text = generateClickableText(stack);
|
|
150
|
+
} else {
|
|
151
|
+
text = generateClickableText(message);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (codeFrame) {
|
|
155
|
+
text += `\n\n${generateClickableText(codeFrame)}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return text;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const onErrorLocated = urlSite => {
|
|
162
|
+
errorUrlSite = urlSite;
|
|
163
|
+
|
|
164
|
+
errorDetailsPromiseReference.current = (async () => {
|
|
165
|
+
if (errorMeta.type === "dynamic_import_fetch_error") {
|
|
166
|
+
const response = await window.fetch(`/__get_error_cause__/${urlSite.isInline ? urlSite.originalUrl : urlSite.url}`);
|
|
167
|
+
|
|
168
|
+
if (response.status !== 200) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const causeInfo = await response.json();
|
|
173
|
+
|
|
174
|
+
if (!causeInfo) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const causeText = causeInfo.code === "NOT_FOUND" ? formatErrorText({
|
|
179
|
+
message: causeInfo.reason,
|
|
180
|
+
stack: causeInfo.codeFrame
|
|
181
|
+
}) : formatErrorText({
|
|
182
|
+
message: causeInfo.stack,
|
|
183
|
+
stack: causeInfo.codeFrame
|
|
184
|
+
});
|
|
185
|
+
return {
|
|
186
|
+
cause: causeText
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (urlSite.line !== undefined) {
|
|
191
|
+
const response = await window.fetch(`/__get_code_frame__/${formatUrlWithLineAndColumn(urlSite)}`);
|
|
192
|
+
const codeFrame = await response.text();
|
|
193
|
+
return {
|
|
194
|
+
codeFrame: formatErrorText({
|
|
195
|
+
message: codeFrame
|
|
196
|
+
})
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return null;
|
|
201
|
+
})();
|
|
202
|
+
}; // error.stack is more reliable than url/line/column reported on window error events
|
|
203
|
+
// so use it only when error.stack is not available
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
if (url && !stack && // ignore window.reportError() it gives no valuable info
|
|
207
|
+
!url.endsWith("html_supervisor_installer.js")) {
|
|
208
|
+
onErrorLocated(resolveUrlSite({
|
|
209
|
+
url,
|
|
210
|
+
line,
|
|
211
|
+
column
|
|
212
|
+
}));
|
|
213
|
+
} else if (errorMeta.url) {
|
|
214
|
+
onErrorLocated(resolveUrlSite(errorMeta));
|
|
65
215
|
}
|
|
66
216
|
|
|
67
|
-
return
|
|
217
|
+
return {
|
|
218
|
+
theme: error && error.cause && error.cause.code === "PARSE_ERROR" ? "light" : "dark",
|
|
219
|
+
title: "An error occured",
|
|
220
|
+
text: formatErrorText({
|
|
221
|
+
message,
|
|
222
|
+
stack
|
|
223
|
+
}),
|
|
224
|
+
tip: `${tip}
|
|
225
|
+
<br />
|
|
226
|
+
Click outside to close.`,
|
|
227
|
+
errorDetailsPromise: errorDetailsPromiseReference.current
|
|
228
|
+
};
|
|
68
229
|
};
|
|
69
230
|
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
openInEditor,
|
|
73
|
-
message,
|
|
74
|
-
stack
|
|
231
|
+
const extractErrorMeta = (error, {
|
|
232
|
+
line
|
|
75
233
|
}) => {
|
|
76
|
-
if (
|
|
77
|
-
return
|
|
78
|
-
rootDirectoryUrl,
|
|
79
|
-
openInEditor
|
|
80
|
-
})}\n${replaceLinks(stack, {
|
|
81
|
-
rootDirectoryUrl,
|
|
82
|
-
openInEditor
|
|
83
|
-
})}`;
|
|
234
|
+
if (!error) {
|
|
235
|
+
return {};
|
|
84
236
|
}
|
|
85
237
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
238
|
+
const {
|
|
239
|
+
message
|
|
240
|
+
} = error;
|
|
241
|
+
|
|
242
|
+
if (!message) {
|
|
243
|
+
return {};
|
|
91
244
|
}
|
|
92
245
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
246
|
+
{
|
|
247
|
+
// chrome
|
|
248
|
+
if (message.includes("does not provide an export named")) {
|
|
249
|
+
return {
|
|
250
|
+
type: "dynamic_import_export_missing"
|
|
251
|
+
};
|
|
252
|
+
} // firefox
|
|
98
253
|
|
|
99
|
-
class JsenvErrorOverlay extends HTMLElement {
|
|
100
|
-
constructor({
|
|
101
|
-
theme,
|
|
102
|
-
title,
|
|
103
|
-
text,
|
|
104
|
-
tip
|
|
105
|
-
}) {
|
|
106
|
-
super();
|
|
107
|
-
this.root = this.attachShadow({
|
|
108
|
-
mode: "open"
|
|
109
|
-
});
|
|
110
|
-
this.root.innerHTML = overlayHtml;
|
|
111
254
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
255
|
+
if (message.startsWith("import not found:")) {
|
|
256
|
+
return {
|
|
257
|
+
type: "dynamic_import_export_missing",
|
|
258
|
+
browser: "firefox"
|
|
259
|
+
};
|
|
260
|
+
} // safari
|
|
117
261
|
|
|
118
|
-
this.root.querySelector(".backdrop").onclick = null;
|
|
119
|
-
this.parentNode.removeChild(this);
|
|
120
|
-
};
|
|
121
262
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
263
|
+
if (message.startsWith("import binding name")) {
|
|
264
|
+
return {
|
|
265
|
+
type: "dynamic_import_export_missing"
|
|
266
|
+
};
|
|
267
|
+
}
|
|
126
268
|
}
|
|
127
269
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
270
|
+
{
|
|
271
|
+
if (error.name === "SyntaxError" && typeof line === "number") {
|
|
272
|
+
return {
|
|
273
|
+
type: "dynamic_import_syntax_error"
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
}
|
|
133
277
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
:
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
overflow-y: scroll;
|
|
144
|
-
margin: 0;
|
|
145
|
-
background: rgba(0, 0, 0, 0.66);
|
|
146
|
-
}
|
|
278
|
+
{
|
|
279
|
+
// chrome
|
|
280
|
+
if (message.startsWith("Failed to fetch dynamically imported module: ")) {
|
|
281
|
+
const url = error.message.slice("Failed to fetch dynamically imported module: ".length);
|
|
282
|
+
return {
|
|
283
|
+
type: "dynamic_import_fetch_error",
|
|
284
|
+
url
|
|
285
|
+
};
|
|
286
|
+
} // firefox
|
|
147
287
|
|
|
148
|
-
.backdrop {
|
|
149
|
-
position: absolute;
|
|
150
|
-
left: 0;
|
|
151
|
-
right: 0;
|
|
152
|
-
top: 0;
|
|
153
|
-
bottom: 0;
|
|
154
|
-
}
|
|
155
288
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
padding: 25px 40px;
|
|
162
|
-
padding-top: 0;
|
|
163
|
-
overflow: hidden; /* for h1 margins */
|
|
164
|
-
border-radius: 4px 8px;
|
|
165
|
-
box-shadow: 0 20px 40px rgb(0 0 0 / 30%), 0 15px 12px rgb(0 0 0 / 20%);
|
|
166
|
-
box-sizing: border-box;
|
|
167
|
-
font-family: monospace;
|
|
168
|
-
direction: ltr;
|
|
169
|
-
}
|
|
289
|
+
if (message === "error loading dynamically imported module") {
|
|
290
|
+
return {
|
|
291
|
+
type: "dynamic_import_fetch_error"
|
|
292
|
+
};
|
|
293
|
+
} // safari
|
|
170
294
|
|
|
171
|
-
h1 {
|
|
172
|
-
color: red;
|
|
173
|
-
text-align: center;
|
|
174
|
-
}
|
|
175
295
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
.tip {
|
|
185
|
-
border-top: 1px solid #999;
|
|
186
|
-
padding-top: 12px;
|
|
187
|
-
}
|
|
296
|
+
if (message === "Importing a module script failed.") {
|
|
297
|
+
return {
|
|
298
|
+
type: "dynamic_import_fetch_error"
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
}
|
|
188
302
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
[data-theme="dark"] pre {
|
|
193
|
-
background: #111;
|
|
194
|
-
border: 1px solid #333;
|
|
195
|
-
color: #eee;
|
|
196
|
-
}
|
|
303
|
+
return {};
|
|
304
|
+
};
|
|
197
305
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
306
|
+
const formatUrlWithLineAndColumn = ({
|
|
307
|
+
url,
|
|
308
|
+
line,
|
|
309
|
+
column
|
|
310
|
+
}) => {
|
|
311
|
+
return line === undefined && column === undefined ? url : column === undefined ? `${url}:${line}` : `${url}:${line}:${column}`;
|
|
312
|
+
};
|
|
206
313
|
|
|
207
|
-
|
|
208
|
-
color: inherit;
|
|
209
|
-
}
|
|
210
|
-
</style>
|
|
211
|
-
<div class="backdrop"></div>
|
|
212
|
-
<div class="overlay">
|
|
213
|
-
<h1 class="title"></h1>
|
|
214
|
-
<pre class="text"></pre>
|
|
215
|
-
<div class="tip"></div>
|
|
216
|
-
</div>
|
|
217
|
-
`;
|
|
218
|
-
|
|
219
|
-
const parseErrorInfo = error => {
|
|
314
|
+
const normalizeErrorParts = error => {
|
|
220
315
|
if (error === undefined) {
|
|
221
316
|
return {
|
|
222
317
|
message: "undefined"
|
|
@@ -294,56 +389,8 @@ const getErrorStackWithoutErrorMessage = error => {
|
|
|
294
389
|
return stack;
|
|
295
390
|
};
|
|
296
391
|
|
|
297
|
-
const
|
|
298
|
-
url
|
|
299
|
-
line,
|
|
300
|
-
column,
|
|
301
|
-
reportedBy,
|
|
302
|
-
requestedRessource
|
|
303
|
-
}) => {
|
|
304
|
-
let {
|
|
305
|
-
message,
|
|
306
|
-
stack
|
|
307
|
-
} = parseErrorInfo(error);
|
|
308
|
-
|
|
309
|
-
if (url) {
|
|
310
|
-
if (!stack || error && error.name === "SyntaxError") {
|
|
311
|
-
stack = ` at ${appendLineAndColumn(url, {
|
|
312
|
-
line,
|
|
313
|
-
column
|
|
314
|
-
})}`;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
let tip = formatTip({
|
|
319
|
-
reportedBy,
|
|
320
|
-
requestedRessource
|
|
321
|
-
});
|
|
322
|
-
return {
|
|
323
|
-
theme: error && error.cause && error.cause.code === "PARSE_ERROR" ? "light" : "dark",
|
|
324
|
-
title: "An error occured",
|
|
325
|
-
message,
|
|
326
|
-
stack,
|
|
327
|
-
tip: `${tip}
|
|
328
|
-
<br />
|
|
329
|
-
Click outside to close.`
|
|
330
|
-
};
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
const formatTip = ({
|
|
334
|
-
reportedBy,
|
|
335
|
-
requestedRessource
|
|
336
|
-
}) => {
|
|
337
|
-
if (reportedBy === "browser") {
|
|
338
|
-
return `Reported by the browser while executing <code>${window.location.pathname}${window.location.search}</code>.`;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
return `Reported by the server while serving <code>${requestedRessource}</code>`;
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
const replaceLinks = (string, {
|
|
345
|
-
rootDirectoryUrl,
|
|
346
|
-
openInEditor
|
|
392
|
+
const makeLinksClickable = (string, {
|
|
393
|
+
createLink = url => url
|
|
347
394
|
}) => {
|
|
348
395
|
// normalize line breaks
|
|
349
396
|
string = string.replace(/\n/g, "\n");
|
|
@@ -354,44 +401,16 @@ const replaceLinks = (string, {
|
|
|
354
401
|
line,
|
|
355
402
|
column
|
|
356
403
|
}) => {
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
const afterAtFs = fileUrlObject.pathname.slice(atFsIndex + "/@fs/".length);
|
|
365
|
-
fileUrl = new URL(afterAtFs, "file:///").href;
|
|
366
|
-
} else {
|
|
367
|
-
fileUrl = fileUrlObject.href;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
fileUrl = appendLineAndColumn(fileUrl, {
|
|
371
|
-
line,
|
|
372
|
-
column
|
|
373
|
-
});
|
|
374
|
-
return link({
|
|
375
|
-
href: openInEditor ? `javascript:window.fetch('/__open_in_editor__/${fileUrl}')` : fileUrl,
|
|
376
|
-
text: fileUrl
|
|
377
|
-
});
|
|
378
|
-
};
|
|
379
|
-
|
|
380
|
-
if (urlObject.origin === window.origin) {
|
|
381
|
-
const fileUrlObject = new URL(`${urlObject.pathname.slice(1)}${urlObject.search}`, rootDirectoryUrl);
|
|
382
|
-
return onFileUrl(fileUrlObject);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
if (urlObject.href.startsWith("file:")) {
|
|
386
|
-
return onFileUrl(urlObject);
|
|
387
|
-
}
|
|
388
|
-
|
|
404
|
+
const {
|
|
405
|
+
href,
|
|
406
|
+
text
|
|
407
|
+
} = createLink(url, {
|
|
408
|
+
line,
|
|
409
|
+
column
|
|
410
|
+
});
|
|
389
411
|
return link({
|
|
390
|
-
href
|
|
391
|
-
text
|
|
392
|
-
line,
|
|
393
|
-
column
|
|
394
|
-
})
|
|
412
|
+
href,
|
|
413
|
+
text
|
|
395
414
|
});
|
|
396
415
|
}
|
|
397
416
|
});
|
|
@@ -400,21 +419,6 @@ const replaceLinks = (string, {
|
|
|
400
419
|
|
|
401
420
|
const escapeHtml = string => {
|
|
402
421
|
return string.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
403
|
-
};
|
|
404
|
-
|
|
405
|
-
const appendLineAndColumn = (url, {
|
|
406
|
-
line,
|
|
407
|
-
column
|
|
408
|
-
}) => {
|
|
409
|
-
if (line !== undefined && column !== undefined) {
|
|
410
|
-
return `${url}:${line}:${column}`;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
if (line !== undefined) {
|
|
414
|
-
return `${url}:${line}`;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
return url;
|
|
418
422
|
}; // `Error: yo
|
|
419
423
|
// at Object.execute (http://127.0.0.1:57300/build/src/__test__/file-throw.js:9:13)
|
|
420
424
|
// at doExec (http://127.0.0.1:3000/src/__test__/file-throw.js:452:38)
|
|
@@ -481,6 +485,218 @@ const link = ({
|
|
|
481
485
|
text = href
|
|
482
486
|
}) => `<a href="${href}">${text}</a>`;
|
|
483
487
|
|
|
488
|
+
const JSENV_ERROR_OVERLAY_TAGNAME = "jsenv-error-overlay";
|
|
489
|
+
const displayErrorInDocument = (error, {
|
|
490
|
+
rootDirectoryUrl,
|
|
491
|
+
errorBaseUrl,
|
|
492
|
+
openInEditor,
|
|
493
|
+
url,
|
|
494
|
+
line,
|
|
495
|
+
column,
|
|
496
|
+
codeFrame
|
|
497
|
+
}) => {
|
|
498
|
+
const {
|
|
499
|
+
theme,
|
|
500
|
+
title,
|
|
501
|
+
text,
|
|
502
|
+
tip,
|
|
503
|
+
errorDetailsPromise
|
|
504
|
+
} = formatError(error, {
|
|
505
|
+
rootDirectoryUrl,
|
|
506
|
+
errorBaseUrl,
|
|
507
|
+
openInEditor,
|
|
508
|
+
url,
|
|
509
|
+
line,
|
|
510
|
+
column,
|
|
511
|
+
codeFrame
|
|
512
|
+
});
|
|
513
|
+
let jsenvErrorOverlay = new JsenvErrorOverlay({
|
|
514
|
+
theme,
|
|
515
|
+
title,
|
|
516
|
+
text,
|
|
517
|
+
tip,
|
|
518
|
+
errorDetailsPromise
|
|
519
|
+
});
|
|
520
|
+
document.querySelectorAll(JSENV_ERROR_OVERLAY_TAGNAME).forEach(node => {
|
|
521
|
+
node.parentNode.removeChild(node);
|
|
522
|
+
});
|
|
523
|
+
document.body.appendChild(jsenvErrorOverlay);
|
|
524
|
+
|
|
525
|
+
const removeErrorOverlay = () => {
|
|
526
|
+
if (jsenvErrorOverlay && jsenvErrorOverlay.parentNode) {
|
|
527
|
+
document.body.removeChild(jsenvErrorOverlay);
|
|
528
|
+
jsenvErrorOverlay = null;
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
if (window.__reloader__) {
|
|
533
|
+
window.__reloader__.onstatuschange = () => {
|
|
534
|
+
if (window.__reloader__.status === "reloading") {
|
|
535
|
+
removeErrorOverlay();
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return removeErrorOverlay;
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
class JsenvErrorOverlay extends HTMLElement {
|
|
544
|
+
constructor({
|
|
545
|
+
theme,
|
|
546
|
+
title,
|
|
547
|
+
text,
|
|
548
|
+
tip,
|
|
549
|
+
errorDetailsPromise
|
|
550
|
+
}) {
|
|
551
|
+
super();
|
|
552
|
+
this.root = this.attachShadow({
|
|
553
|
+
mode: "open"
|
|
554
|
+
});
|
|
555
|
+
this.root.innerHTML = `
|
|
556
|
+
<style>
|
|
557
|
+
${overlayCSS}
|
|
558
|
+
</style>
|
|
559
|
+
<div class="backdrop"></div>
|
|
560
|
+
<div class="overlay" data-theme=${theme}>
|
|
561
|
+
<h1 class="title">
|
|
562
|
+
${title}
|
|
563
|
+
</h1>
|
|
564
|
+
<pre class="text">${text}</pre>
|
|
565
|
+
<div class="tip">
|
|
566
|
+
${tip}
|
|
567
|
+
</div>
|
|
568
|
+
</div>`;
|
|
569
|
+
|
|
570
|
+
this.root.querySelector(".backdrop").onclick = () => {
|
|
571
|
+
if (!this.parentNode) {
|
|
572
|
+
// not in document anymore
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
this.root.querySelector(".backdrop").onclick = null;
|
|
577
|
+
this.parentNode.removeChild(this);
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
if (errorDetailsPromise) {
|
|
581
|
+
errorDetailsPromise.then(errorDetails => {
|
|
582
|
+
if (!errorDetails || !this.parentNode) {
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const {
|
|
587
|
+
codeFrame,
|
|
588
|
+
cause
|
|
589
|
+
} = errorDetails;
|
|
590
|
+
|
|
591
|
+
if (codeFrame) {
|
|
592
|
+
this.root.querySelector(".text").innerHTML += `\n\n${codeFrame}`;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (cause) {
|
|
596
|
+
const causeIndented = prefixRemainingLines(cause, " ");
|
|
597
|
+
this.root.querySelector(".text").innerHTML += `\n [cause]: ${causeIndented}`;
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const prefixRemainingLines = (text, prefix) => {
|
|
606
|
+
const lines = text.split(/\r?\n/);
|
|
607
|
+
const firstLine = lines.shift();
|
|
608
|
+
let result = firstLine;
|
|
609
|
+
let i = 0;
|
|
610
|
+
|
|
611
|
+
while (i < lines.length) {
|
|
612
|
+
const line = lines[i];
|
|
613
|
+
i++;
|
|
614
|
+
result += line.length ? `\n${prefix}${line}` : `\n`;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
return result;
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
if (customElements && !customElements.get(JSENV_ERROR_OVERLAY_TAGNAME)) {
|
|
621
|
+
customElements.define(JSENV_ERROR_OVERLAY_TAGNAME, JsenvErrorOverlay);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const overlayCSS = `
|
|
625
|
+
:host {
|
|
626
|
+
position: fixed;
|
|
627
|
+
z-index: 99999;
|
|
628
|
+
top: 0;
|
|
629
|
+
left: 0;
|
|
630
|
+
width: 100%;
|
|
631
|
+
height: 100%;
|
|
632
|
+
overflow-y: scroll;
|
|
633
|
+
margin: 0;
|
|
634
|
+
background: rgba(0, 0, 0, 0.66);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
.backdrop {
|
|
638
|
+
position: absolute;
|
|
639
|
+
left: 0;
|
|
640
|
+
right: 0;
|
|
641
|
+
top: 0;
|
|
642
|
+
bottom: 0;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
.overlay {
|
|
646
|
+
position: relative;
|
|
647
|
+
background: rgba(0, 0, 0, 0.95);
|
|
648
|
+
width: 800px;
|
|
649
|
+
margin: 30px auto;
|
|
650
|
+
padding: 25px 40px;
|
|
651
|
+
padding-top: 0;
|
|
652
|
+
overflow: hidden; /* for h1 margins */
|
|
653
|
+
border-radius: 4px 8px;
|
|
654
|
+
box-shadow: 0 20px 40px rgb(0 0 0 / 30%), 0 15px 12px rgb(0 0 0 / 20%);
|
|
655
|
+
box-sizing: border-box;
|
|
656
|
+
font-family: monospace;
|
|
657
|
+
direction: ltr;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
h1 {
|
|
661
|
+
color: red;
|
|
662
|
+
text-align: center;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
pre {
|
|
666
|
+
overflow: auto;
|
|
667
|
+
max-width: 100%;
|
|
668
|
+
/* padding is nice + prevents scrollbar from hiding the text behind it */
|
|
669
|
+
/* does not work nicely on firefox though https://bugzilla.mozilla.org/show_bug.cgi?id=748518 */
|
|
670
|
+
padding: 20px;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
.tip {
|
|
674
|
+
border-top: 1px solid #999;
|
|
675
|
+
padding-top: 12px;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
[data-theme="dark"] {
|
|
679
|
+
color: #999;
|
|
680
|
+
}
|
|
681
|
+
[data-theme="dark"] pre {
|
|
682
|
+
background: #111;
|
|
683
|
+
border: 1px solid #333;
|
|
684
|
+
color: #eee;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
[data-theme="light"] {
|
|
688
|
+
color: #EEEEEE;
|
|
689
|
+
}
|
|
690
|
+
[data-theme="light"] pre {
|
|
691
|
+
background: #1E1E1E;
|
|
692
|
+
border: 1px solid white;
|
|
693
|
+
color: #EEEEEE;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
pre a {
|
|
697
|
+
color: inherit;
|
|
698
|
+
}`;
|
|
699
|
+
|
|
484
700
|
const {
|
|
485
701
|
Notification
|
|
486
702
|
} = window;
|
|
@@ -514,6 +730,7 @@ const installHtmlSupervisor = ({
|
|
|
514
730
|
logs,
|
|
515
731
|
measurePerf,
|
|
516
732
|
errorOverlay,
|
|
733
|
+
errorBaseUrl,
|
|
517
734
|
openInEditor
|
|
518
735
|
}) => {
|
|
519
736
|
|
|
@@ -601,14 +818,18 @@ const installHtmlSupervisor = ({
|
|
|
601
818
|
let completed;
|
|
602
819
|
let result;
|
|
603
820
|
let error;
|
|
821
|
+
const urlObject = new URL(src, window.location);
|
|
604
822
|
|
|
605
|
-
|
|
606
|
-
|
|
823
|
+
if (reload) {
|
|
824
|
+
urlObject.searchParams.set("hmr", Date.now());
|
|
825
|
+
}
|
|
607
826
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
827
|
+
__html_supervisor__.currentExecution = {
|
|
828
|
+
type: type === "module" ? "dynamic_import" : "script_injection",
|
|
829
|
+
url: urlObject.href
|
|
830
|
+
};
|
|
611
831
|
|
|
832
|
+
try {
|
|
612
833
|
result = await execute(urlObject.href);
|
|
613
834
|
completed = true;
|
|
614
835
|
} catch (e) {
|
|
@@ -629,6 +850,7 @@ const installHtmlSupervisor = ({
|
|
|
629
850
|
console.groupEnd();
|
|
630
851
|
}
|
|
631
852
|
|
|
853
|
+
__html_supervisor__.currentExecution = null;
|
|
632
854
|
return;
|
|
633
855
|
}
|
|
634
856
|
|
|
@@ -656,6 +878,8 @@ const installHtmlSupervisor = ({
|
|
|
656
878
|
if (logs) {
|
|
657
879
|
console.groupEnd();
|
|
658
880
|
}
|
|
881
|
+
|
|
882
|
+
__html_supervisor__.currentExecution = null;
|
|
659
883
|
};
|
|
660
884
|
|
|
661
885
|
const classicExecutionQueue = createExecutionQueue(performExecution);
|
|
@@ -744,6 +968,21 @@ const installHtmlSupervisor = ({
|
|
|
744
968
|
});
|
|
745
969
|
|
|
746
970
|
if (errorOverlay) {
|
|
971
|
+
const onErrorReportedByBrowser = (error, {
|
|
972
|
+
url,
|
|
973
|
+
line,
|
|
974
|
+
column
|
|
975
|
+
}) => {
|
|
976
|
+
displayErrorInDocument(error, {
|
|
977
|
+
rootDirectoryUrl,
|
|
978
|
+
errorBaseUrl,
|
|
979
|
+
openInEditor,
|
|
980
|
+
url,
|
|
981
|
+
line,
|
|
982
|
+
column
|
|
983
|
+
});
|
|
984
|
+
};
|
|
985
|
+
|
|
747
986
|
window.addEventListener("error", errorEvent => {
|
|
748
987
|
if (!errorEvent.isTrusted) {
|
|
749
988
|
// ignore custom error event (not sent by browser)
|
|
@@ -751,76 +990,17 @@ const installHtmlSupervisor = ({
|
|
|
751
990
|
}
|
|
752
991
|
|
|
753
992
|
const {
|
|
754
|
-
error
|
|
993
|
+
error,
|
|
994
|
+
filename,
|
|
995
|
+
lineno,
|
|
996
|
+
colno
|
|
755
997
|
} = errorEvent;
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
line: errorEvent.lineno,
|
|
761
|
-
column: errorEvent.colno,
|
|
762
|
-
reportedBy: "browser"
|
|
998
|
+
onErrorReportedByBrowser(error, {
|
|
999
|
+
url: filename,
|
|
1000
|
+
line: lineno,
|
|
1001
|
+
column: colno
|
|
763
1002
|
});
|
|
764
1003
|
});
|
|
765
|
-
|
|
766
|
-
if (window.__server_events__) {
|
|
767
|
-
const isExecuting = () => {
|
|
768
|
-
if (pendingExecutionCount > 0) {
|
|
769
|
-
return true;
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
if (document.readyState === "loading" || document.readyState === "interactive") {
|
|
773
|
-
return true;
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
if (window.__reloader__ && window.__reloader__.status === "reloading") {
|
|
777
|
-
return true;
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
return false;
|
|
781
|
-
};
|
|
782
|
-
|
|
783
|
-
window.__server_events__.addEventCallbacks({
|
|
784
|
-
error_while_serving_file: serverErrorEvent => {
|
|
785
|
-
if (!isExecuting()) {
|
|
786
|
-
return;
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
const {
|
|
790
|
-
message,
|
|
791
|
-
stack,
|
|
792
|
-
traceUrl,
|
|
793
|
-
traceLine,
|
|
794
|
-
traceColumn,
|
|
795
|
-
traceMessage,
|
|
796
|
-
requestedRessource,
|
|
797
|
-
isFaviconAutoRequest
|
|
798
|
-
} = JSON.parse(serverErrorEvent.data);
|
|
799
|
-
|
|
800
|
-
if (isFaviconAutoRequest) {
|
|
801
|
-
return;
|
|
802
|
-
} // setTimeout is to ensure the error
|
|
803
|
-
// dispatched on window by browser is displayed first,
|
|
804
|
-
// then the server error replaces it (because it contains more information)
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
setTimeout(() => {
|
|
808
|
-
displayErrorInDocument({
|
|
809
|
-
message,
|
|
810
|
-
stack: stack && traceMessage ? `${stack}\n\n${traceMessage}` : stack ? stack : traceMessage ? `\n${traceMessage}` : ""
|
|
811
|
-
}, {
|
|
812
|
-
rootDirectoryUrl,
|
|
813
|
-
openInEditor,
|
|
814
|
-
url: traceUrl,
|
|
815
|
-
line: traceLine,
|
|
816
|
-
column: traceColumn,
|
|
817
|
-
reportedBy: "server",
|
|
818
|
-
requestedRessource
|
|
819
|
-
});
|
|
820
|
-
}, 10);
|
|
821
|
-
}
|
|
822
|
-
});
|
|
823
|
-
}
|
|
824
1004
|
}
|
|
825
1005
|
};
|
|
826
1006
|
|