@jsenv/core 39.0.5 → 39.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/js/autoreload.js +152 -146
- package/dist/js/ws.js +246 -61
- package/dist/jsenv_core.js +170 -218
- package/package.json +6 -6
- package/src/build/version_mappings_injection.js +1 -1
- package/src/dev/start_dev_server.js +1 -2
- package/src/helpers/command/command.js +2 -2
- package/src/kitchen/kitchen.js +2 -0
- package/src/kitchen/prepend_content.js +1 -1
- package/src/plugins/autoreload/client/autoreload.js +155 -6
- package/src/plugins/autoreload/jsenv_plugin_autoreload_client.js +12 -24
- package/src/plugins/clean_html/jsenv_plugin_clean_html.js +2 -1
- package/src/plugins/html_syntax_error_fallback/jsenv_plugin_html_syntax_error_fallback.js +93 -0
- package/src/plugins/injections/internal/inject_globals.js +5 -14
- package/src/plugins/plugin_controller.js +33 -96
- package/src/plugins/reference_analysis/html/jsenv_plugin_html_reference_analysis.js +11 -85
- package/src/plugins/ribbon/jsenv_plugin_ribbon.js +13 -23
- package/src/plugins/server_events/jsenv_plugin_server_events_client_injection.js +17 -13
- package/src/plugins/autoreload/client/reload.js +0 -136
- package/src/plugins/autoreload/client/url_helpers.js +0 -23
- /package/src/plugins/{reference_analysis/html → html_syntax_error_fallback/client}/html_syntax_error.html +0 -0
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { urlHotMetas } from "../../import_meta_hot/client/import_meta_hot.js";
|
|
2
|
-
import { compareTwoUrlPaths } from "./url_helpers.js";
|
|
3
1
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
} from "
|
|
2
|
+
parseSrcSet,
|
|
3
|
+
stringifySrcSet,
|
|
4
|
+
} from "@jsenv/ast/src/html/html_src_set.js";
|
|
5
|
+
import { urlHotMetas } from "../../import_meta_hot/client/import_meta_hot.js";
|
|
8
6
|
|
|
9
7
|
export const initAutoreload = ({ mainFilePath }) => {
|
|
10
8
|
let debug = false;
|
|
@@ -229,3 +227,154 @@ This could be due to syntax errors or importing non-existent modules (see errors
|
|
|
229
227
|
},
|
|
230
228
|
});
|
|
231
229
|
};
|
|
230
|
+
|
|
231
|
+
const reloadHtmlPage = () => {
|
|
232
|
+
window.location.reload(true);
|
|
233
|
+
};
|
|
234
|
+
// This function can consider everything as hot reloadable:
|
|
235
|
+
// - no need to check [hot-accept]and [hot-decline] attributes for instance
|
|
236
|
+
// This is because if something should full reload, we receive "full_reload"
|
|
237
|
+
// from server and this function is not called
|
|
238
|
+
const getDOMNodesUsingUrl = (urlToReload) => {
|
|
239
|
+
const nodes = [];
|
|
240
|
+
const shouldReloadUrl = (urlCandidate) => {
|
|
241
|
+
return compareTwoUrlPaths(urlCandidate, urlToReload);
|
|
242
|
+
};
|
|
243
|
+
const visitNodeAttributeAsUrl = (node, attributeName) => {
|
|
244
|
+
let attribute = node[attributeName];
|
|
245
|
+
if (!attribute) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (SVGAnimatedString && attribute instanceof SVGAnimatedString) {
|
|
249
|
+
attribute = attribute.animVal;
|
|
250
|
+
}
|
|
251
|
+
if (!shouldReloadUrl(attribute)) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
nodes.push({
|
|
255
|
+
node,
|
|
256
|
+
reload: (hot) => {
|
|
257
|
+
if (node.nodeName === "SCRIPT") {
|
|
258
|
+
const copy = document.createElement("script");
|
|
259
|
+
Array.from(node.attributes).forEach((attribute) => {
|
|
260
|
+
copy.setAttribute(attribute.nodeName, attribute.nodeValue);
|
|
261
|
+
});
|
|
262
|
+
copy.src = injectQuery(node.src, { hot });
|
|
263
|
+
if (node.parentNode) {
|
|
264
|
+
node.parentNode.replaceChild(copy, node);
|
|
265
|
+
} else {
|
|
266
|
+
document.body.appendChild(copy);
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
node[attributeName] = injectQuery(attribute, { hot });
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
};
|
|
274
|
+
Array.from(document.querySelectorAll(`link[rel="stylesheet"]`)).forEach(
|
|
275
|
+
(link) => {
|
|
276
|
+
visitNodeAttributeAsUrl(link, "href");
|
|
277
|
+
},
|
|
278
|
+
);
|
|
279
|
+
Array.from(document.querySelectorAll(`link[rel="icon"]`)).forEach((link) => {
|
|
280
|
+
visitNodeAttributeAsUrl(link, "href");
|
|
281
|
+
});
|
|
282
|
+
Array.from(document.querySelectorAll("script")).forEach((script) => {
|
|
283
|
+
visitNodeAttributeAsUrl(script, "src");
|
|
284
|
+
const inlinedFromSrc = script.getAttribute("inlined-from-src");
|
|
285
|
+
if (inlinedFromSrc) {
|
|
286
|
+
const inlinedFromUrl = new URL(inlinedFromSrc, window.location.origin)
|
|
287
|
+
.href;
|
|
288
|
+
if (shouldReloadUrl(inlinedFromUrl)) {
|
|
289
|
+
nodes.push({
|
|
290
|
+
node: script,
|
|
291
|
+
reload: () =>
|
|
292
|
+
window.__supervisor__.reloadSupervisedScript(inlinedFromSrc),
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
// There is no real need to update a.href because the resource will be fetched when clicked.
|
|
298
|
+
// But in a scenario where the resource was already visited and is in browser cache, adding
|
|
299
|
+
// the dynamic query param ensure the cache is invalidated
|
|
300
|
+
Array.from(document.querySelectorAll("a")).forEach((a) => {
|
|
301
|
+
visitNodeAttributeAsUrl(a, "href");
|
|
302
|
+
});
|
|
303
|
+
// About iframes:
|
|
304
|
+
// - By default iframe itself and everything inside trigger a parent page full-reload
|
|
305
|
+
// - Adding [hot-accept] on the iframe means parent page won't reload when iframe full/hot reload
|
|
306
|
+
// In that case and if there is code in the iframe and parent doing post message communication:
|
|
307
|
+
// you must put import.meta.hot.decline() for code involved in communication.
|
|
308
|
+
// (both in parent and iframe)
|
|
309
|
+
Array.from(document.querySelectorAll("img")).forEach((img) => {
|
|
310
|
+
visitNodeAttributeAsUrl(img, "src");
|
|
311
|
+
const srcset = img.srcset;
|
|
312
|
+
if (srcset) {
|
|
313
|
+
nodes.push({
|
|
314
|
+
node: img,
|
|
315
|
+
reload: (hot) => {
|
|
316
|
+
const srcCandidates = parseSrcSet(srcset);
|
|
317
|
+
srcCandidates.forEach((srcCandidate) => {
|
|
318
|
+
const url = new URL(
|
|
319
|
+
srcCandidate.specifier,
|
|
320
|
+
`${window.location.href}`,
|
|
321
|
+
);
|
|
322
|
+
if (shouldReloadUrl(url)) {
|
|
323
|
+
srcCandidate.specifier = injectQuery(url, { hot });
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
img.srcset = stringifySrcSet(srcCandidates);
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
Array.from(document.querySelectorAll("source")).forEach((source) => {
|
|
332
|
+
visitNodeAttributeAsUrl(source, "src");
|
|
333
|
+
});
|
|
334
|
+
// svg image tag
|
|
335
|
+
Array.from(document.querySelectorAll("image")).forEach((image) => {
|
|
336
|
+
visitNodeAttributeAsUrl(image, "href");
|
|
337
|
+
});
|
|
338
|
+
// svg use
|
|
339
|
+
Array.from(document.querySelectorAll("use")).forEach((use) => {
|
|
340
|
+
visitNodeAttributeAsUrl(use, "href");
|
|
341
|
+
});
|
|
342
|
+
return nodes;
|
|
343
|
+
};
|
|
344
|
+
const reloadJsImport = async (url, hot) => {
|
|
345
|
+
const urlWithHotSearchParam = injectQuery(url, { hot });
|
|
346
|
+
const namespace = await import(urlWithHotSearchParam);
|
|
347
|
+
return namespace;
|
|
348
|
+
};
|
|
349
|
+
// const reloadAllCss = () => {
|
|
350
|
+
// const links = Array.from(document.getElementsByTagName("link"));
|
|
351
|
+
// links.forEach((link) => {
|
|
352
|
+
// if (link.rel === "stylesheet") {
|
|
353
|
+
// link.href = injectQuery(link.href, { hot: Date.now() });
|
|
354
|
+
// }
|
|
355
|
+
// });
|
|
356
|
+
// };
|
|
357
|
+
|
|
358
|
+
const compareTwoUrlPaths = (url, otherUrl) => {
|
|
359
|
+
if (url === otherUrl) {
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
const urlObject = new URL(url);
|
|
363
|
+
const otherUrlObject = new URL(otherUrl);
|
|
364
|
+
if (urlObject.origin !== otherUrlObject.origin) {
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
if (urlObject.pathname !== otherUrlObject.pathname) {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
return true;
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
const injectQuery = (url, query) => {
|
|
374
|
+
const urlObject = new URL(url);
|
|
375
|
+
const { searchParams } = urlObject;
|
|
376
|
+
Object.keys(query).forEach((key) => {
|
|
377
|
+
searchParams.set(key, query[key]);
|
|
378
|
+
});
|
|
379
|
+
return String(urlObject);
|
|
380
|
+
};
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
parseHtml,
|
|
3
|
-
stringifyHtmlAst,
|
|
4
|
-
injectHtmlNodeAsEarlyAsPossible,
|
|
5
|
-
createHtmlNode,
|
|
6
|
-
} from "@jsenv/ast";
|
|
1
|
+
import { parseHtml, injectJsenvScript, stringifyHtmlAst } from "@jsenv/ast";
|
|
7
2
|
|
|
8
3
|
export const jsenvPluginAutoreloadClient = () => {
|
|
9
4
|
const autoreloadClientFileUrl = new URL(
|
|
@@ -21,29 +16,22 @@ export const jsenvPluginAutoreloadClient = () => {
|
|
|
21
16
|
url: htmlUrlInfo.url,
|
|
22
17
|
});
|
|
23
18
|
const autoreloadClientReference = htmlUrlInfo.dependencies.inject({
|
|
24
|
-
type: "
|
|
19
|
+
type: "js_import",
|
|
25
20
|
subtype: "js_module",
|
|
26
21
|
expectedType: "js_module",
|
|
27
22
|
specifier: autoreloadClientFileUrl,
|
|
28
23
|
});
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
injectJsenvScript(htmlAst, {
|
|
25
|
+
type: "module",
|
|
26
|
+
src: autoreloadClientReference.generatedSpecifier,
|
|
27
|
+
initCall: {
|
|
28
|
+
callee: "initAutoreload",
|
|
29
|
+
params: {
|
|
30
|
+
mainFilePath: `/${htmlUrlInfo.kitchen.context.mainFilePath}`,
|
|
31
|
+
},
|
|
32
32
|
},
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
);
|
|
36
|
-
injectHtmlNodeAsEarlyAsPossible(
|
|
37
|
-
htmlAst,
|
|
38
|
-
createHtmlNode({
|
|
39
|
-
tagName: "script",
|
|
40
|
-
type: "module",
|
|
41
|
-
textContent: `import { initAutoreload } from "${autoreloadClientReference.generatedSpecifier}";
|
|
42
|
-
|
|
43
|
-
initAutoreload(${paramsJson});`,
|
|
44
|
-
}),
|
|
45
|
-
"jsenv:autoreload_client",
|
|
46
|
-
);
|
|
33
|
+
pluginName: "jsenv:autoreload_client",
|
|
34
|
+
});
|
|
47
35
|
const htmlModified = stringifyHtmlAst(htmlAst);
|
|
48
36
|
return {
|
|
49
37
|
content: htmlModified,
|
|
@@ -10,9 +10,10 @@ export const jsenvPluginCleanHTML = () => {
|
|
|
10
10
|
html: urlInfo.content,
|
|
11
11
|
url: urlInfo.url,
|
|
12
12
|
});
|
|
13
|
-
|
|
13
|
+
const htmlClean = stringifyHtmlAst(htmlAst, {
|
|
14
14
|
cleanupPositionAttributes: true,
|
|
15
15
|
});
|
|
16
|
+
return htmlClean;
|
|
16
17
|
},
|
|
17
18
|
},
|
|
18
19
|
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { urlToRelativeUrl } from "@jsenv/urls";
|
|
3
|
+
import { parseHtml } from "@jsenv/ast";
|
|
4
|
+
|
|
5
|
+
import { generateContentFrame } from "@jsenv/humanize";
|
|
6
|
+
|
|
7
|
+
export const jsenvPluginHtmlSyntaxErrorFallback = () => {
|
|
8
|
+
const htmlSyntaxErrorFileUrl = new URL(
|
|
9
|
+
"./client/html_syntax_error.html",
|
|
10
|
+
import.meta.url,
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
mustStayFirst: true,
|
|
15
|
+
name: "jsenv:html_syntax_error_fallback",
|
|
16
|
+
appliesDuring: "dev",
|
|
17
|
+
transformUrlContent: {
|
|
18
|
+
html: (urlInfo) => {
|
|
19
|
+
try {
|
|
20
|
+
parseHtml({
|
|
21
|
+
html: urlInfo.content,
|
|
22
|
+
url: urlInfo.url,
|
|
23
|
+
});
|
|
24
|
+
return null;
|
|
25
|
+
} catch (e) {
|
|
26
|
+
if (e.code !== "PARSE_ERROR") {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const line = e.line;
|
|
30
|
+
const column = e.column;
|
|
31
|
+
const htmlErrorContentFrame = generateContentFrame({
|
|
32
|
+
content: urlInfo.content,
|
|
33
|
+
line,
|
|
34
|
+
column,
|
|
35
|
+
});
|
|
36
|
+
urlInfo.kitchen.context.logger
|
|
37
|
+
.error(`Error while handling ${urlInfo.context.request ? urlInfo.context.request.url : urlInfo.url}:
|
|
38
|
+
${e.reasonCode}
|
|
39
|
+
${urlInfo.url}:${line}:${column}
|
|
40
|
+
${htmlErrorContentFrame}`);
|
|
41
|
+
const html = generateHtmlForSyntaxError(e, {
|
|
42
|
+
htmlUrl: urlInfo.url,
|
|
43
|
+
rootDirectoryUrl: urlInfo.context.rootDirectoryUrl,
|
|
44
|
+
htmlErrorContentFrame,
|
|
45
|
+
htmlSyntaxErrorFileUrl,
|
|
46
|
+
});
|
|
47
|
+
return html;
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const generateHtmlForSyntaxError = (
|
|
55
|
+
htmlSyntaxError,
|
|
56
|
+
{ htmlUrl, rootDirectoryUrl, htmlErrorContentFrame, htmlSyntaxErrorFileUrl },
|
|
57
|
+
) => {
|
|
58
|
+
const htmlForSyntaxError = String(readFileSync(htmlSyntaxErrorFileUrl));
|
|
59
|
+
const htmlRelativeUrl = urlToRelativeUrl(htmlUrl, rootDirectoryUrl);
|
|
60
|
+
const { line, column } = htmlSyntaxError;
|
|
61
|
+
const urlWithLineAndColumn = `${htmlUrl}:${line}:${column}`;
|
|
62
|
+
const replacers = {
|
|
63
|
+
fileRelativeUrl: htmlRelativeUrl,
|
|
64
|
+
reasonCode: htmlSyntaxError.reasonCode,
|
|
65
|
+
errorLinkHref: `javascript:window.fetch('/__open_in_editor__/${encodeURIComponent(
|
|
66
|
+
urlWithLineAndColumn,
|
|
67
|
+
)}')`,
|
|
68
|
+
errorLinkText: `${htmlRelativeUrl}:${line}:${column}`,
|
|
69
|
+
syntaxError: escapeHtml(htmlErrorContentFrame),
|
|
70
|
+
};
|
|
71
|
+
const html = replacePlaceholders(htmlForSyntaxError, replacers);
|
|
72
|
+
return html;
|
|
73
|
+
};
|
|
74
|
+
const escapeHtml = (string) => {
|
|
75
|
+
return string
|
|
76
|
+
.replace(/&/g, "&")
|
|
77
|
+
.replace(/</g, "<")
|
|
78
|
+
.replace(/>/g, ">")
|
|
79
|
+
.replace(/"/g, """)
|
|
80
|
+
.replace(/'/g, "'");
|
|
81
|
+
};
|
|
82
|
+
const replacePlaceholders = (html, replacers) => {
|
|
83
|
+
return html.replace(/\$\{(\w+)\}/g, (match, name) => {
|
|
84
|
+
const replacer = replacers[name];
|
|
85
|
+
if (replacer === undefined) {
|
|
86
|
+
return match;
|
|
87
|
+
}
|
|
88
|
+
if (typeof replacer === "function") {
|
|
89
|
+
return replacer();
|
|
90
|
+
}
|
|
91
|
+
return replacer;
|
|
92
|
+
});
|
|
93
|
+
};
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { createMagicSource } from "@jsenv/sourcemap";
|
|
2
|
-
import {
|
|
3
|
-
parseHtml,
|
|
4
|
-
injectHtmlNodeAsEarlyAsPossible,
|
|
5
|
-
createHtmlNode,
|
|
6
|
-
stringifyHtmlAst,
|
|
7
|
-
} from "@jsenv/ast";
|
|
2
|
+
import { parseHtml, injectJsenvScript, stringifyHtmlAst } from "@jsenv/ast";
|
|
8
3
|
|
|
9
4
|
export const injectGlobals = (content, globals, urlInfo) => {
|
|
10
5
|
if (urlInfo.type === "html") {
|
|
@@ -28,14 +23,10 @@ const globalInjectorOnHtml = (content, globals, urlInfo) => {
|
|
|
28
23
|
const clientCode = generateClientCodeForGlobals(globals, {
|
|
29
24
|
isWebWorker: false,
|
|
30
25
|
});
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
textContent: clientCode,
|
|
36
|
-
}),
|
|
37
|
-
"jsenv:inject_globals",
|
|
38
|
-
);
|
|
26
|
+
injectJsenvScript(htmlAst, {
|
|
27
|
+
content: clientCode,
|
|
28
|
+
pluginName: "jsenv:inject_globals",
|
|
29
|
+
});
|
|
39
30
|
return stringifyHtmlAst(htmlAst);
|
|
40
31
|
};
|
|
41
32
|
|
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
import { performance } from "node:perf_hooks";
|
|
2
|
-
import {
|
|
3
|
-
parseHtml,
|
|
4
|
-
stringifyHtmlAst,
|
|
5
|
-
injectHtmlNodeAsEarlyAsPossible,
|
|
6
|
-
createHtmlNode,
|
|
7
|
-
} from "@jsenv/ast";
|
|
8
2
|
|
|
9
3
|
const HOOK_NAMES = [
|
|
10
4
|
"init",
|
|
@@ -81,7 +75,8 @@ export const createPluginController = (
|
|
|
81
75
|
key === "name" ||
|
|
82
76
|
key === "appliesDuring" ||
|
|
83
77
|
key === "init" ||
|
|
84
|
-
key === "serverEvents"
|
|
78
|
+
key === "serverEvents" ||
|
|
79
|
+
key === "mustStayFirst"
|
|
85
80
|
) {
|
|
86
81
|
continue;
|
|
87
82
|
}
|
|
@@ -100,7 +95,15 @@ export const createPluginController = (
|
|
|
100
95
|
value: hookValue,
|
|
101
96
|
};
|
|
102
97
|
if (position === "start") {
|
|
103
|
-
|
|
98
|
+
let i = 0;
|
|
99
|
+
while (i < group.length) {
|
|
100
|
+
const before = group[i];
|
|
101
|
+
if (!before.plugin.mustStayFirst) {
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
i++;
|
|
105
|
+
}
|
|
106
|
+
group.splice(i, 0, hook);
|
|
104
107
|
} else {
|
|
105
108
|
group.push(hook);
|
|
106
109
|
}
|
|
@@ -225,11 +228,11 @@ export const createPluginController = (
|
|
|
225
228
|
}
|
|
226
229
|
}
|
|
227
230
|
};
|
|
228
|
-
const callAsyncHooks = async (hookName, info, callback) => {
|
|
231
|
+
const callAsyncHooks = async (hookName, info, callback, options) => {
|
|
229
232
|
const hooks = hookGroups[hookName];
|
|
230
233
|
if (hooks) {
|
|
231
234
|
for (const hook of hooks) {
|
|
232
|
-
const returnValue = await callAsyncHook(hook, info);
|
|
235
|
+
const returnValue = await callAsyncHook(hook, info, options);
|
|
233
236
|
if (returnValue && callback) {
|
|
234
237
|
await callback(returnValue, hook.plugin);
|
|
235
238
|
}
|
|
@@ -249,7 +252,7 @@ export const createPluginController = (
|
|
|
249
252
|
}
|
|
250
253
|
return null;
|
|
251
254
|
};
|
|
252
|
-
const callAsyncHooksUntil = (hookName, info) => {
|
|
255
|
+
const callAsyncHooksUntil = async (hookName, info, options) => {
|
|
253
256
|
const hooks = hookGroups[hookName];
|
|
254
257
|
if (!hooks) {
|
|
255
258
|
return null;
|
|
@@ -257,22 +260,23 @@ export const createPluginController = (
|
|
|
257
260
|
if (hooks.length === 0) {
|
|
258
261
|
return null;
|
|
259
262
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
263
|
+
let result;
|
|
264
|
+
let index = 0;
|
|
265
|
+
const visit = async () => {
|
|
266
|
+
if (index >= hooks.length) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const hook = hooks[index];
|
|
270
|
+
const returnValue = await callAsyncHook(hook, info, options);
|
|
271
|
+
if (returnValue) {
|
|
272
|
+
result = returnValue;
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
index++;
|
|
276
|
+
await visit();
|
|
277
|
+
};
|
|
278
|
+
await visit(0);
|
|
279
|
+
return result;
|
|
276
280
|
};
|
|
277
281
|
|
|
278
282
|
return {
|
|
@@ -320,11 +324,7 @@ const assertAndNormalizeReturnValue = (hook, returnValue, info) => {
|
|
|
320
324
|
if (!returnValueAssertion.appliesTo.includes(hook.name)) {
|
|
321
325
|
continue;
|
|
322
326
|
}
|
|
323
|
-
const assertionResult = returnValueAssertion.assertion(
|
|
324
|
-
returnValue,
|
|
325
|
-
info,
|
|
326
|
-
hook,
|
|
327
|
-
);
|
|
327
|
+
const assertionResult = returnValueAssertion.assertion(returnValue, info);
|
|
328
328
|
if (assertionResult !== undefined) {
|
|
329
329
|
// normalization
|
|
330
330
|
returnValue = assertionResult;
|
|
@@ -358,7 +358,7 @@ const returnValueAssertions = [
|
|
|
358
358
|
"finalizeUrlContent",
|
|
359
359
|
"optimizeUrlContent",
|
|
360
360
|
],
|
|
361
|
-
assertion: (valueReturned, urlInfo
|
|
361
|
+
assertion: (valueReturned, urlInfo) => {
|
|
362
362
|
if (typeof valueReturned === "string" || Buffer.isBuffer(valueReturned)) {
|
|
363
363
|
return { content: valueReturned };
|
|
364
364
|
}
|
|
@@ -367,12 +367,6 @@ const returnValueAssertions = [
|
|
|
367
367
|
if (urlInfo.url.startsWith("ignore:")) {
|
|
368
368
|
return undefined;
|
|
369
369
|
}
|
|
370
|
-
if (urlInfo.type === "html") {
|
|
371
|
-
const { scriptInjections } = valueReturned;
|
|
372
|
-
if (scriptInjections) {
|
|
373
|
-
return applyScriptInjections(urlInfo, scriptInjections, hook);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
370
|
if (typeof content !== "string" && !Buffer.isBuffer(content) && !body) {
|
|
377
371
|
throw new Error(
|
|
378
372
|
`Unexpected "content" returned by plugin: it must be a string or a buffer; got ${content}`,
|
|
@@ -386,60 +380,3 @@ const returnValueAssertions = [
|
|
|
386
380
|
},
|
|
387
381
|
},
|
|
388
382
|
];
|
|
389
|
-
|
|
390
|
-
const applyScriptInjections = (htmlUrlInfo, scriptInjections, hook) => {
|
|
391
|
-
const htmlAst = parseHtml({
|
|
392
|
-
html: htmlUrlInfo.content,
|
|
393
|
-
url: htmlUrlInfo.url,
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
scriptInjections.reverse().forEach((scriptInjection) => {
|
|
397
|
-
const { setup } = scriptInjection;
|
|
398
|
-
if (setup) {
|
|
399
|
-
const setupGlobalName = setup.name;
|
|
400
|
-
const setupParamSource = stringifyParams(setup.param, " ");
|
|
401
|
-
const inlineJs = `${setupGlobalName}({${setupParamSource}})`;
|
|
402
|
-
injectHtmlNodeAsEarlyAsPossible(
|
|
403
|
-
htmlAst,
|
|
404
|
-
createHtmlNode({
|
|
405
|
-
tagName: "script",
|
|
406
|
-
textContent: inlineJs,
|
|
407
|
-
}),
|
|
408
|
-
hook.plugin.name,
|
|
409
|
-
);
|
|
410
|
-
}
|
|
411
|
-
const scriptReference = htmlUrlInfo.dependencies.inject({
|
|
412
|
-
type: "script",
|
|
413
|
-
subtype: scriptInjection.type === "module" ? "js_module" : "js_classic",
|
|
414
|
-
expectedType:
|
|
415
|
-
scriptInjection.type === "module" ? "js_module" : "js_classic",
|
|
416
|
-
specifier: scriptInjection.src,
|
|
417
|
-
});
|
|
418
|
-
injectHtmlNodeAsEarlyAsPossible(
|
|
419
|
-
htmlAst,
|
|
420
|
-
createHtmlNode({
|
|
421
|
-
tagName: "script",
|
|
422
|
-
...(scriptInjection.type === "module" ? { type: "module" } : {}),
|
|
423
|
-
src: scriptReference.generatedSpecifier,
|
|
424
|
-
}),
|
|
425
|
-
hook.plugin.name,
|
|
426
|
-
);
|
|
427
|
-
});
|
|
428
|
-
const htmlModified = stringifyHtmlAst(htmlAst);
|
|
429
|
-
return {
|
|
430
|
-
content: htmlModified,
|
|
431
|
-
};
|
|
432
|
-
};
|
|
433
|
-
|
|
434
|
-
const stringifyParams = (params, prefix = "") => {
|
|
435
|
-
const source = JSON.stringify(params, null, prefix);
|
|
436
|
-
if (prefix.length) {
|
|
437
|
-
// remove leading "{\n"
|
|
438
|
-
// remove leading prefix
|
|
439
|
-
// remove trailing "\n}"
|
|
440
|
-
return source.slice(2 + prefix.length, -2);
|
|
441
|
-
}
|
|
442
|
-
// remove leading "{"
|
|
443
|
-
// remove trailing "}"
|
|
444
|
-
return source.slice(1, -1);
|
|
445
|
-
};
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { urlToRelativeUrl } from "@jsenv/urls";
|
|
3
|
-
import { generateContentFrame } from "@jsenv/humanize";
|
|
4
1
|
import {
|
|
5
2
|
parseHtml,
|
|
6
3
|
visitHtmlNodes,
|
|
@@ -23,11 +20,6 @@ import {
|
|
|
23
20
|
normalizeImportMap,
|
|
24
21
|
} from "@jsenv/importmap";
|
|
25
22
|
|
|
26
|
-
const htmlSyntaxErrorFileUrl = new URL(
|
|
27
|
-
"./html_syntax_error.html",
|
|
28
|
-
import.meta.url,
|
|
29
|
-
);
|
|
30
|
-
|
|
31
23
|
export const jsenvPluginHtmlReferenceAnalysis = ({
|
|
32
24
|
inlineContent,
|
|
33
25
|
inlineConvertedScript,
|
|
@@ -135,41 +127,10 @@ export const jsenvPluginHtmlReferenceAnalysis = ({
|
|
|
135
127
|
},
|
|
136
128
|
html: async (urlInfo) => {
|
|
137
129
|
let importmapFound = false;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
html: urlInfo.content,
|
|
143
|
-
url: urlInfo.url,
|
|
144
|
-
});
|
|
145
|
-
} catch (e) {
|
|
146
|
-
if (e.code === "PARSE_ERROR") {
|
|
147
|
-
const line = e.line;
|
|
148
|
-
const column = e.column;
|
|
149
|
-
const htmlErrorContentFrame = generateContentFrame({
|
|
150
|
-
content: urlInfo.content,
|
|
151
|
-
line,
|
|
152
|
-
column,
|
|
153
|
-
});
|
|
154
|
-
urlInfo.kitchen.context.logger
|
|
155
|
-
.error(`Error while handling ${urlInfo.context.request ? urlInfo.context.request.url : urlInfo.url}:
|
|
156
|
-
${e.reasonCode}
|
|
157
|
-
${urlInfo.url}:${line}:${column}
|
|
158
|
-
${htmlErrorContentFrame}`);
|
|
159
|
-
const html = generateHtmlForSyntaxError(e, {
|
|
160
|
-
htmlUrl: urlInfo.url,
|
|
161
|
-
rootDirectoryUrl: urlInfo.context.rootDirectoryUrl,
|
|
162
|
-
htmlErrorContentFrame,
|
|
163
|
-
});
|
|
164
|
-
htmlAst = parseHtml({
|
|
165
|
-
html,
|
|
166
|
-
url: htmlSyntaxErrorFileUrl,
|
|
167
|
-
});
|
|
168
|
-
} else {
|
|
169
|
-
throw e;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
130
|
+
const htmlAst = parseHtml({
|
|
131
|
+
html: urlInfo.content,
|
|
132
|
+
url: urlInfo.url,
|
|
133
|
+
});
|
|
173
134
|
const importmapLoaded = startLoadingImportmap(urlInfo);
|
|
174
135
|
|
|
175
136
|
try {
|
|
@@ -316,7 +277,13 @@ ${htmlErrorContentFrame}`);
|
|
|
316
277
|
});
|
|
317
278
|
|
|
318
279
|
actions.push(async () => {
|
|
319
|
-
|
|
280
|
+
try {
|
|
281
|
+
await inlineReference.urlInfo.cook();
|
|
282
|
+
} catch (e) {
|
|
283
|
+
if (!e || e.code !== "PARSE_ERROR") {
|
|
284
|
+
throw e;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
320
287
|
mutations.push(() => {
|
|
321
288
|
if (hotAccept) {
|
|
322
289
|
removeHtmlNodeText(node);
|
|
@@ -621,47 +588,6 @@ const visitNonIgnoredHtmlNode = (htmlAst, visitors) => {
|
|
|
621
588
|
visitHtmlNodes(htmlAst, visitorsInstrumented);
|
|
622
589
|
};
|
|
623
590
|
|
|
624
|
-
const generateHtmlForSyntaxError = (
|
|
625
|
-
htmlSyntaxError,
|
|
626
|
-
{ htmlUrl, rootDirectoryUrl, htmlErrorContentFrame },
|
|
627
|
-
) => {
|
|
628
|
-
const htmlForSyntaxError = String(readFileSync(htmlSyntaxErrorFileUrl));
|
|
629
|
-
const htmlRelativeUrl = urlToRelativeUrl(htmlUrl, rootDirectoryUrl);
|
|
630
|
-
const { line, column } = htmlSyntaxError;
|
|
631
|
-
const urlWithLineAndColumn = `${htmlUrl}:${line}:${column}`;
|
|
632
|
-
const replacers = {
|
|
633
|
-
fileRelativeUrl: htmlRelativeUrl,
|
|
634
|
-
reasonCode: htmlSyntaxError.reasonCode,
|
|
635
|
-
errorLinkHref: `javascript:window.fetch('/__open_in_editor__/${encodeURIComponent(
|
|
636
|
-
urlWithLineAndColumn,
|
|
637
|
-
)}')`,
|
|
638
|
-
errorLinkText: `${htmlRelativeUrl}:${line}:${column}`,
|
|
639
|
-
syntaxError: escapeHtml(htmlErrorContentFrame),
|
|
640
|
-
};
|
|
641
|
-
const html = replacePlaceholders(htmlForSyntaxError, replacers);
|
|
642
|
-
return html;
|
|
643
|
-
};
|
|
644
|
-
const escapeHtml = (string) => {
|
|
645
|
-
return string
|
|
646
|
-
.replace(/&/g, "&")
|
|
647
|
-
.replace(/</g, "<")
|
|
648
|
-
.replace(/>/g, ">")
|
|
649
|
-
.replace(/"/g, """)
|
|
650
|
-
.replace(/'/g, "'");
|
|
651
|
-
};
|
|
652
|
-
const replacePlaceholders = (html, replacers) => {
|
|
653
|
-
return html.replace(/\$\{(\w+)\}/g, (match, name) => {
|
|
654
|
-
const replacer = replacers[name];
|
|
655
|
-
if (replacer === undefined) {
|
|
656
|
-
return match;
|
|
657
|
-
}
|
|
658
|
-
if (typeof replacer === "function") {
|
|
659
|
-
return replacer();
|
|
660
|
-
}
|
|
661
|
-
return replacer;
|
|
662
|
-
});
|
|
663
|
-
};
|
|
664
|
-
|
|
665
591
|
const crossOriginCompatibleTagNames = ["script", "link", "img", "source"];
|
|
666
592
|
const integrityCompatibleTagNames = ["script", "link", "img", "source"];
|
|
667
593
|
const readFetchMetas = (node) => {
|