@jsenv/core 38.4.17 → 38.4.18

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.
@@ -1,3 +1,6 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { urlToRelativeUrl } from "@jsenv/urls";
3
+ import { generateContentFrame } from "@jsenv/humanize";
1
4
  import {
2
5
  parseHtml,
3
6
  visitHtmlNodes,
@@ -20,6 +23,11 @@ import {
20
23
  normalizeImportMap,
21
24
  } from "@jsenv/importmap";
22
25
 
26
+ const htmlSyntaxErrorFileUrl = new URL(
27
+ "./html_syntax_error.html",
28
+ import.meta.url,
29
+ );
30
+
23
31
  export const jsenvPluginHtmlReferenceAnalysis = ({
24
32
  inlineContent,
25
33
  inlineConvertedScript,
@@ -127,14 +135,43 @@ export const jsenvPluginHtmlReferenceAnalysis = ({
127
135
  },
128
136
  html: async (urlInfo) => {
129
137
  let importmapFound = false;
130
- const importmapLoaded = startLoadingImportmap(urlInfo);
131
138
 
139
+ let htmlAst;
132
140
  try {
133
- const htmlAst = parseHtml({
141
+ htmlAst = parseHtml({
134
142
  html: urlInfo.content,
135
143
  url: urlInfo.url,
136
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
+ console.error(`Error while handling ${urlInfo.context.request ? urlInfo.context.request.url : urlInfo.url}:
155
+ ${e.reasonCode}
156
+ ${urlInfo.url}:${line}:${column}
157
+ ${htmlErrorContentFrame}`);
158
+ const html = generateHtmlForSyntaxError(e, {
159
+ htmlUrl: urlInfo.url,
160
+ rootDirectoryUrl: urlInfo.context.rootDirectoryUrl,
161
+ htmlErrorContentFrame,
162
+ });
163
+ htmlAst = parseHtml({
164
+ html,
165
+ url: htmlSyntaxErrorFileUrl,
166
+ });
167
+ } else {
168
+ throw e;
169
+ }
170
+ }
171
+
172
+ const importmapLoaded = startLoadingImportmap(urlInfo);
137
173
 
174
+ try {
138
175
  const mutations = [];
139
176
  const actions = [];
140
177
  const finalizeCallbacks = [];
@@ -313,7 +350,7 @@ export const jsenvPluginHtmlReferenceAnalysis = ({
313
350
  });
314
351
  };
315
352
 
316
- visitHtmlNodes(htmlAst, {
353
+ visitNonIgnoredHtmlNode(htmlAst, {
317
354
  link: (linkNode) => {
318
355
  const rel = getHtmlNodeAttribute(linkNode, "rel");
319
356
  const type = getHtmlNodeAttribute(linkNode, "type");
@@ -569,6 +606,61 @@ export const jsenvPluginHtmlReferenceAnalysis = ({
569
606
  };
570
607
  };
571
608
 
609
+ const visitNonIgnoredHtmlNode = (htmlAst, visitors) => {
610
+ const visitorsInstrumented = {};
611
+ for (const key of Object.keys(visitors)) {
612
+ visitorsInstrumented[key] = (node) => {
613
+ const jsenvIgnoreAttribute = getHtmlNodeAttribute(node, "jsenv-ignore");
614
+ if (jsenvIgnoreAttribute !== undefined) {
615
+ return;
616
+ }
617
+ visitors[key](node);
618
+ };
619
+ }
620
+ visitHtmlNodes(htmlAst, visitorsInstrumented);
621
+ };
622
+
623
+ const generateHtmlForSyntaxError = (
624
+ htmlSyntaxError,
625
+ { htmlUrl, rootDirectoryUrl, htmlErrorContentFrame },
626
+ ) => {
627
+ const htmlForSyntaxError = String(readFileSync(htmlSyntaxErrorFileUrl));
628
+ const htmlRelativeUrl = urlToRelativeUrl(htmlUrl, rootDirectoryUrl);
629
+ const { line, column } = htmlSyntaxError;
630
+ const urlWithLineAndColumn = `${htmlUrl}:${line}:${column}`;
631
+ const replacers = {
632
+ fileRelativeUrl: htmlRelativeUrl,
633
+ reasonCode: htmlSyntaxError.reasonCode,
634
+ errorLinkHref: `javascript:window.fetch('/__open_in_editor__/${encodeURIComponent(
635
+ urlWithLineAndColumn,
636
+ )}')`,
637
+ errorLinkText: `${htmlRelativeUrl}:${line}:${column}`,
638
+ syntaxError: escapeHtml(htmlErrorContentFrame),
639
+ };
640
+ const html = replacePlaceholders(htmlForSyntaxError, replacers);
641
+ return html;
642
+ };
643
+ const escapeHtml = (string) => {
644
+ return string
645
+ .replace(/&/g, "&")
646
+ .replace(/</g, "&lt;")
647
+ .replace(/>/g, "&gt;")
648
+ .replace(/"/g, "&quot;")
649
+ .replace(/'/g, "&#039;");
650
+ };
651
+ const replacePlaceholders = (html, replacers) => {
652
+ return html.replace(/\${([\w]+)}/g, (match, name) => {
653
+ const replacer = replacers[name];
654
+ if (replacer === undefined) {
655
+ return match;
656
+ }
657
+ if (typeof replacer === "function") {
658
+ return replacer();
659
+ }
660
+ return replacer;
661
+ });
662
+ };
663
+
572
664
  const crossOriginCompatibleTagNames = ["script", "link", "img", "source"];
573
665
  const integrityCompatibleTagNames = ["script", "link", "img", "source"];
574
666
  const readFetchMetas = (node) => {
@@ -62,7 +62,7 @@ export const jsenvPluginRibbon = ({
62
62
  createHtmlNode({
63
63
  tagName: "script",
64
64
  type: "module",
65
- textContent: `import { injectRibbon } from "${ribbonClientFileReference.generatedSpecifier}"
65
+ textContent: `import { injectRibbon } from "${ribbonClientFileReference.generatedSpecifier}";
66
66
 
67
67
  injectRibbon(${paramsJson});`,
68
68
  }),