@jsenv/core 38.4.16 → 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,4 +1,4 @@
1
- import { readdir, chmod, stat, lstat, promises, writeFileSync as writeFileSync$1, mkdirSync, unlink, openSync, closeSync, rmdir, watch, readdirSync, statSync, createReadStream, readFile, existsSync, readFileSync, realpathSync } from "node:fs";
1
+ import { readdir, chmod, stat, lstat, promises, writeFileSync as writeFileSync$1, mkdirSync, unlink, openSync, closeSync, rmdir, watch, readdirSync, statSync, createReadStream, lstatSync, readFile, existsSync, readFileSync, realpathSync } from "node:fs";
2
2
  import process$1 from "node:process";
3
3
  import os, { networkInterfaces } from "node:os";
4
4
  import tty from "node:tty";
@@ -13,7 +13,7 @@ import http from "node:http";
13
13
  import { Readable, Stream, Writable } from "node:stream";
14
14
  import { Http2ServerResponse } from "node:http2";
15
15
  import { lookup } from "node:dns";
16
- import { injectJsImport, visitJsAstUntil, applyBabelPlugins, parseHtml, visitHtmlNodes, getHtmlNodeAttribute, analyzeScriptNode, getHtmlNodeText, stringifyHtmlAst, setHtmlNodeAttributes, parseJsUrls, injectHtmlNodeAsEarlyAsPossible, createHtmlNode, generateUrlForInlineContent, parseJsWithAcorn, getHtmlNodePosition, getUrlForContentInsideHtml, getHtmlNodeAttributePosition, parseSrcSet, removeHtmlNodeText, setHtmlNodeText, removeHtmlNode, parseCssUrls, getUrlForContentInsideJs, analyzeLinkNode, findHtmlNode, insertHtmlNodeAfter } from "@jsenv/ast";
16
+ import { injectJsImport, visitJsAstUntil, applyBabelPlugins, parseHtml, visitHtmlNodes, getHtmlNodeAttribute, analyzeScriptNode, getHtmlNodeText, stringifyHtmlAst, setHtmlNodeAttributes, parseJsUrls, injectHtmlNodeAsEarlyAsPossible, createHtmlNode, generateUrlForInlineContent, parseJsWithAcorn, getHtmlNodePosition, getHtmlNodeAttributePosition, parseSrcSet, getUrlForContentInsideHtml, removeHtmlNodeText, setHtmlNodeText, removeHtmlNode, parseCssUrls, getUrlForContentInsideJs, analyzeLinkNode, findHtmlNode, insertHtmlNodeAfter } from "@jsenv/ast";
17
17
  import { sourcemapConverter, createMagicSource, composeTwoSourcemaps, SOURCEMAP, generateSourcemapFileUrl, generateSourcemapDataUrl } from "@jsenv/sourcemap";
18
18
  import { createRequire } from "node:module";
19
19
  import { systemJsClientFileUrlDefault, convertJsModuleToJsClassic } from "@jsenv/js-module-fallback";
@@ -142,6 +142,19 @@ const applyMatching = (pattern, string) => {
142
142
  consumeRemainingString();
143
143
  return true;
144
144
  }
145
+ if (remainingPattern.slice(0, 4) === "/**/") {
146
+ consumePattern(3); // consumes "/**/"
147
+ const skipResult = skipUntilMatch({
148
+ pattern: remainingPattern,
149
+ string: remainingString,
150
+ canSkipSlash: true,
151
+ });
152
+ groups.push(...skipResult.groups);
153
+ consumePattern(skipResult.patternIndex);
154
+ consumeRemainingString();
155
+ restoreIndexes = false;
156
+ return skipResult.matched;
157
+ }
145
158
  // pattern leading **
146
159
  if (remainingPattern.slice(0, 2) === "**") {
147
160
  consumePattern(2); // consumes "**"
@@ -3159,6 +3172,14 @@ const readStat = (
3159
3172
  });
3160
3173
  };
3161
3174
 
3175
+ /*
3176
+ * - stats object documentation on Node.js
3177
+ * https://nodejs.org/docs/latest-v13.x/api/fs.html#fs_class_fs_stats
3178
+ */
3179
+
3180
+
3181
+ process.platform === "win32";
3182
+
3162
3183
  const statsToType = (stats) => {
3163
3184
  if (stats.isFile()) return "file";
3164
3185
  if (stats.isDirectory()) return "directory";
@@ -3471,14 +3492,6 @@ const removeDirectoryNaive = (
3471
3492
  });
3472
3493
  };
3473
3494
 
3474
- process.platform === "win32";
3475
-
3476
- /*
3477
- * - stats object documentation on Node.js
3478
- * https://nodejs.org/docs/latest-v13.x/api/fs.html#fs_class_fs_stats
3479
- */
3480
-
3481
-
3482
3495
  process.platform === "win32";
3483
3496
 
3484
3497
  process.platform === "win32";
@@ -7381,10 +7394,14 @@ const serveDirectory = (
7381
7394
  <h1>Content of directory ${url}</h1>
7382
7395
  <ul>
7383
7396
  ${directoryContentArray.map((filename) => {
7384
- const fileUrl = String(new URL(filename, url));
7385
- const fileUrlRelativeToServer = fileUrl.slice(
7397
+ const fileUrlObject = new URL(filename, url);
7398
+ const fileUrl = String(fileUrlObject);
7399
+ let fileUrlRelativeToServer = fileUrl.slice(
7386
7400
  String(rootDirectoryUrl).length,
7387
7401
  );
7402
+ if (lstatSync(fileUrlObject).isDirectory()) {
7403
+ fileUrlRelativeToServer += "/";
7404
+ }
7388
7405
  return `<li>
7389
7406
  <a href="/${fileUrlRelativeToServer}">${fileUrlRelativeToServer}</a>
7390
7407
  </li>`;
@@ -11528,13 +11545,14 @@ const createDependencies = (ownerUrlInfo) => {
11528
11545
  const parentContent = isOriginalPosition
11529
11546
  ? ownerUrlInfo.originalContent
11530
11547
  : ownerUrlInfo.content;
11548
+ const trace = traceFromUrlSite({
11549
+ url: parentUrl,
11550
+ content: parentContent,
11551
+ line: specifierLine,
11552
+ column: specifierColumn,
11553
+ });
11531
11554
  const reference = createResolveAndFinalize({
11532
- trace: traceFromUrlSite({
11533
- url: parentUrl,
11534
- content: parentContent,
11535
- line: specifierLine,
11536
- column: specifierColumn,
11537
- }),
11555
+ trace,
11538
11556
  isOriginalPosition,
11539
11557
  specifierLine,
11540
11558
  specifierColumn,
@@ -13747,9 +13765,12 @@ const createTransformUrlContentError = ({
13747
13765
  if (code === "PARSE_ERROR") {
13748
13766
  transformError.reason = `parse error on ${urlInfo.type}`;
13749
13767
  transformError.cause = error;
13768
+ let line = error.line;
13769
+ if (urlInfo.type === "js_module") {
13770
+ line = line - 1;
13771
+ }
13750
13772
  if (urlInfo.isInline) {
13751
- transformError.trace.line =
13752
- urlInfo.firstReference.trace.line + error.line - 1;
13773
+ transformError.trace.line = urlInfo.firstReference.trace.line + line;
13753
13774
  transformError.trace.column =
13754
13775
  urlInfo.firstReference.trace.column + error.column;
13755
13776
  transformError.trace.codeFrame = generateContentFrame({
@@ -13766,16 +13787,16 @@ const createTransformUrlContentError = ({
13766
13787
  } else {
13767
13788
  transformError.trace = {
13768
13789
  url: urlInfo.url,
13769
- line: error.line,
13790
+ line,
13770
13791
  column: error.column,
13771
13792
  codeFrame: generateContentFrame({
13772
- line: error.line - 1,
13793
+ line,
13773
13794
  column: error.column,
13774
13795
  content: urlInfo.content,
13775
13796
  }),
13776
13797
  message: stringifyUrlSite({
13777
13798
  url: urlInfo.url,
13778
- line: error.line - 1,
13799
+ line,
13779
13800
  column: error.column,
13780
13801
  content: urlInfo.content,
13781
13802
  }),
@@ -15779,6 +15800,11 @@ const applyDefaultExtension = ({ url, importer, defaultExtension }) => {
15779
15800
  return url
15780
15801
  };
15781
15802
 
15803
+ const htmlSyntaxErrorFileUrl = new URL(
15804
+ "./html/html_syntax_error.html",
15805
+ import.meta.url,
15806
+ );
15807
+
15782
15808
  const jsenvPluginHtmlReferenceAnalysis = ({
15783
15809
  inlineContent,
15784
15810
  inlineConvertedScript,
@@ -15886,427 +15912,532 @@ const jsenvPluginHtmlReferenceAnalysis = ({
15886
15912
  },
15887
15913
  html: async (urlInfo) => {
15888
15914
  let importmapFound = false;
15889
- const importmapLoaded = startLoadingImportmap(urlInfo);
15890
-
15891
- const htmlAst = parseHtml({
15892
- html: urlInfo.content,
15893
- url: urlInfo.url,
15894
- });
15895
15915
 
15896
- const mutations = [];
15897
- const actions = [];
15898
- const finalizeCallbacks = [];
15899
-
15900
- const createExternalReference = (
15901
- node,
15902
- attributeName,
15903
- attributeValue,
15904
- { type, subtype, expectedType, ...rest },
15905
- ) => {
15906
- let position;
15907
- if (getHtmlNodeAttribute(node, "jsenv-cooked-by")) {
15908
- // when generated from inline content,
15909
- // line, column is not "src" nor "inlined-from-src" but "original-position"
15910
- position = getHtmlNodePosition(node);
15916
+ let htmlAst;
15917
+ try {
15918
+ htmlAst = parseHtml({
15919
+ html: urlInfo.content,
15920
+ url: urlInfo.url,
15921
+ });
15922
+ } catch (e) {
15923
+ if (e.code === "PARSE_ERROR") {
15924
+ const line = e.line;
15925
+ const column = e.column;
15926
+ const htmlErrorContentFrame = generateContentFrame({
15927
+ content: urlInfo.content,
15928
+ line,
15929
+ column,
15930
+ });
15931
+ console.error(`Error while handling ${urlInfo.context.request ? urlInfo.context.request.url : urlInfo.url}:
15932
+ ${e.reasonCode}
15933
+ ${urlInfo.url}:${line}:${column}
15934
+ ${htmlErrorContentFrame}`);
15935
+ const html = generateHtmlForSyntaxError(e, {
15936
+ htmlUrl: urlInfo.url,
15937
+ rootDirectoryUrl: urlInfo.context.rootDirectoryUrl,
15938
+ htmlErrorContentFrame,
15939
+ });
15940
+ htmlAst = parseHtml({
15941
+ html,
15942
+ url: htmlSyntaxErrorFileUrl,
15943
+ });
15911
15944
  } else {
15912
- position = getHtmlNodeAttributePosition(node, attributeName);
15913
- }
15914
- const {
15915
- line,
15916
- column,
15917
- // originalLine, originalColumn
15918
- } = position;
15919
- const debug = getHtmlNodeAttribute(node, "jsenv-debug") !== undefined;
15920
-
15921
- const { crossorigin, integrity } = readFetchMetas(node);
15922
- const isResourceHint = [
15923
- "preconnect",
15924
- "dns-prefetch",
15925
- "prefetch",
15926
- "preload",
15927
- "modulepreload",
15928
- ].includes(subtype);
15929
- let attributeLocation = node.sourceCodeLocation.attrs[attributeName];
15930
- if (
15931
- !attributeLocation &&
15932
- attributeName === "href" &&
15933
- (node.tagName === "use" || node.tagName === "image")
15934
- ) {
15935
- attributeLocation = node.sourceCodeLocation.attrs["xlink:href"];
15945
+ throw e;
15936
15946
  }
15937
- const attributeStart = attributeLocation.startOffset;
15938
- const attributeValueStart = urlInfo.content.indexOf(
15947
+ }
15948
+
15949
+ const importmapLoaded = startLoadingImportmap(urlInfo);
15950
+
15951
+ try {
15952
+ const mutations = [];
15953
+ const actions = [];
15954
+ const finalizeCallbacks = [];
15955
+
15956
+ const createExternalReference = (
15957
+ node,
15958
+ attributeName,
15939
15959
  attributeValue,
15940
- attributeStart + `${attributeName}=`.length,
15941
- );
15942
- const attributeValueEnd = attributeValueStart + attributeValue.length;
15943
- const reference = urlInfo.dependencies.found({
15944
- type,
15945
- subtype,
15946
- expectedType,
15947
- specifier: attributeValue,
15948
- specifierLine: line,
15949
- specifierColumn: column,
15950
- specifierStart: attributeValueStart,
15951
- specifierEnd: attributeValueEnd,
15952
- isResourceHint,
15953
- isWeak: isResourceHint,
15954
- crossorigin,
15955
- integrity,
15956
- debug,
15957
- astInfo: { node, attributeName },
15958
- ...rest,
15959
- });
15960
- actions.push(async () => {
15961
- await reference.readGeneratedSpecifier();
15962
- mutations.push(() => {
15963
- setHtmlNodeAttributes(node, {
15964
- [attributeName]: reference.generatedSpecifier,
15960
+ { type, subtype, expectedType, ...rest },
15961
+ ) => {
15962
+ let position;
15963
+ if (getHtmlNodeAttribute(node, "jsenv-cooked-by")) {
15964
+ // when generated from inline content,
15965
+ // line, column is not "src" nor "inlined-from-src" but "original-position"
15966
+ position = getHtmlNodePosition(node);
15967
+ } else {
15968
+ position = getHtmlNodeAttributePosition(node, attributeName);
15969
+ }
15970
+ const {
15971
+ line,
15972
+ column,
15973
+ // originalLine, originalColumn
15974
+ } = position;
15975
+ const debug =
15976
+ getHtmlNodeAttribute(node, "jsenv-debug") !== undefined;
15977
+
15978
+ const { crossorigin, integrity } = readFetchMetas(node);
15979
+ const isResourceHint = [
15980
+ "preconnect",
15981
+ "dns-prefetch",
15982
+ "prefetch",
15983
+ "preload",
15984
+ "modulepreload",
15985
+ ].includes(subtype);
15986
+ let attributeLocation =
15987
+ node.sourceCodeLocation.attrs[attributeName];
15988
+ if (
15989
+ !attributeLocation &&
15990
+ attributeName === "href" &&
15991
+ (node.tagName === "use" || node.tagName === "image")
15992
+ ) {
15993
+ attributeLocation = node.sourceCodeLocation.attrs["xlink:href"];
15994
+ }
15995
+ const attributeStart = attributeLocation.startOffset;
15996
+ const attributeValueStart = urlInfo.content.indexOf(
15997
+ attributeValue,
15998
+ attributeStart + `${attributeName}=`.length,
15999
+ );
16000
+ const attributeValueEnd =
16001
+ attributeValueStart + attributeValue.length;
16002
+ const reference = urlInfo.dependencies.found({
16003
+ type,
16004
+ subtype,
16005
+ expectedType,
16006
+ specifier: attributeValue,
16007
+ specifierLine: line,
16008
+ specifierColumn: column,
16009
+ specifierStart: attributeValueStart,
16010
+ specifierEnd: attributeValueEnd,
16011
+ isResourceHint,
16012
+ isWeak: isResourceHint,
16013
+ crossorigin,
16014
+ integrity,
16015
+ debug,
16016
+ astInfo: { node, attributeName },
16017
+ ...rest,
16018
+ });
16019
+ actions.push(async () => {
16020
+ await reference.readGeneratedSpecifier();
16021
+ mutations.push(() => {
16022
+ setHtmlNodeAttributes(node, {
16023
+ [attributeName]: reference.generatedSpecifier,
16024
+ });
15965
16025
  });
15966
16026
  });
15967
- });
15968
- return reference;
15969
- };
15970
- const visitHref = (node, referenceProps) => {
15971
- const href = getHtmlNodeAttribute(node, "href");
15972
- if (href) {
15973
- return createExternalReference(node, "href", href, referenceProps);
15974
- }
15975
- return null;
15976
- };
15977
- const visitSrc = (node, referenceProps) => {
15978
- const src = getHtmlNodeAttribute(node, "src");
15979
- if (src) {
15980
- return createExternalReference(node, "src", src, referenceProps);
15981
- }
15982
- return null;
15983
- };
15984
- const visitSrcset = (node, referenceProps) => {
15985
- const srcset = getHtmlNodeAttribute(node, "srcset");
15986
- if (srcset) {
15987
- const srcCandidates = parseSrcSet(srcset);
15988
- return srcCandidates.map((srcCandidate) => {
16027
+ return reference;
16028
+ };
16029
+ const visitHref = (node, referenceProps) => {
16030
+ const href = getHtmlNodeAttribute(node, "href");
16031
+ if (href) {
15989
16032
  return createExternalReference(
15990
16033
  node,
15991
- "srcset",
15992
- srcCandidate.specifier,
16034
+ "href",
16035
+ href,
15993
16036
  referenceProps,
15994
16037
  );
16038
+ }
16039
+ return null;
16040
+ };
16041
+ const visitSrc = (node, referenceProps) => {
16042
+ const src = getHtmlNodeAttribute(node, "src");
16043
+ if (src) {
16044
+ return createExternalReference(node, "src", src, referenceProps);
16045
+ }
16046
+ return null;
16047
+ };
16048
+ const visitSrcset = (node, referenceProps) => {
16049
+ const srcset = getHtmlNodeAttribute(node, "srcset");
16050
+ if (srcset) {
16051
+ const srcCandidates = parseSrcSet(srcset);
16052
+ return srcCandidates.map((srcCandidate) => {
16053
+ return createExternalReference(
16054
+ node,
16055
+ "srcset",
16056
+ srcCandidate.specifier,
16057
+ referenceProps,
16058
+ );
16059
+ });
16060
+ }
16061
+ return null;
16062
+ };
16063
+ const createInlineReference = (
16064
+ node,
16065
+ inlineContent,
16066
+ { type, expectedType, contentType },
16067
+ ) => {
16068
+ const hotAccept =
16069
+ getHtmlNodeAttribute(node, "hot-accept") !== undefined;
16070
+ const { line, column, isOriginal } = getHtmlNodePosition(node, {
16071
+ preferOriginal: true,
15995
16072
  });
15996
- }
15997
- return null;
15998
- };
15999
-
16000
- const createInlineReference = (
16001
- node,
16002
- inlineContent,
16003
- { type, expectedType, contentType },
16004
- ) => {
16005
- const hotAccept =
16006
- getHtmlNodeAttribute(node, "hot-accept") !== undefined;
16007
- const { line, column, isOriginal } = getHtmlNodePosition(node, {
16008
- preferOriginal: true,
16009
- });
16010
- const inlineContentUrl = getUrlForContentInsideHtml(node, {
16011
- htmlUrl: urlInfo.url,
16012
- });
16013
- const debug = getHtmlNodeAttribute(node, "jsenv-debug") !== undefined;
16014
- const inlineReference = urlInfo.dependencies.foundInline({
16015
- type,
16016
- expectedType,
16017
- isOriginalPosition: isOriginal,
16018
- // we remove 1 to the line because imagine the following html:
16019
- // <style>body { color: red; }</style>
16020
- // -> content starts same line as <style> (same for <script>)
16021
- specifierLine: line - 1,
16022
- specifierColumn: column,
16023
- specifier: inlineContentUrl,
16024
- contentType,
16025
- content: inlineContent,
16026
- debug,
16027
- astInfo: { node },
16028
- });
16029
-
16030
- actions.push(async () => {
16031
- await inlineReference.urlInfo.cook();
16032
- mutations.push(() => {
16033
- if (hotAccept) {
16034
- removeHtmlNodeText(node);
16035
- setHtmlNodeAttributes(node, {
16036
- "jsenv-cooked-by": "jsenv:html_inline_content_analysis",
16037
- });
16038
- } else {
16039
- setHtmlNodeText(node, inlineReference.urlInfo.content, {
16040
- indentation: false, // indentation would decrease stack trace precision
16041
- });
16042
- setHtmlNodeAttributes(node, {
16043
- "jsenv-cooked-by": "jsenv:html_inline_content_analysis",
16044
- });
16045
- }
16073
+ const inlineContentUrl = getUrlForContentInsideHtml(node, {
16074
+ htmlUrl: urlInfo.url,
16046
16075
  });
16047
- });
16048
- return inlineReference;
16049
- };
16050
- const visitTextContent = (
16051
- node,
16052
- { type, subtype, expectedType, contentType },
16053
- ) => {
16054
- const inlineContent = getHtmlNodeText(node);
16055
- if (!inlineContent) {
16056
- return null;
16057
- }
16058
- return createInlineReference(node, inlineContent, {
16059
- type,
16060
- subtype,
16061
- expectedType,
16062
- contentType,
16063
- });
16064
- };
16065
-
16066
- visitHtmlNodes(htmlAst, {
16067
- link: (linkNode) => {
16068
- const rel = getHtmlNodeAttribute(linkNode, "rel");
16069
- const type = getHtmlNodeAttribute(linkNode, "type");
16070
- const ref = visitHref(linkNode, {
16071
- type: "link_href",
16072
- subtype: rel,
16073
- // https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload#including_a_mime_type
16074
- expectedContentType: type,
16076
+ const debug =
16077
+ getHtmlNodeAttribute(node, "jsenv-debug") !== undefined;
16078
+ const inlineReference = urlInfo.dependencies.foundInline({
16079
+ type,
16080
+ expectedType,
16081
+ isOriginalPosition: isOriginal,
16082
+ // we remove 1 to the line because imagine the following html:
16083
+ // <style>body { color: red; }</style>
16084
+ // -> content starts same line as <style> (same for <script>)
16085
+ specifierLine: line - 1,
16086
+ specifierColumn: column,
16087
+ specifier: inlineContentUrl,
16088
+ contentType,
16089
+ content: inlineContent,
16090
+ debug,
16091
+ astInfo: { node },
16075
16092
  });
16076
- if (ref) {
16077
- finalizeCallbacks.push(() => {
16078
- if (ref.expectedType) ; else {
16079
- ref.expectedType = decideLinkExpectedType(ref, urlInfo);
16093
+
16094
+ actions.push(async () => {
16095
+ await inlineReference.urlInfo.cook();
16096
+ mutations.push(() => {
16097
+ if (hotAccept) {
16098
+ removeHtmlNodeText(node);
16099
+ setHtmlNodeAttributes(node, {
16100
+ "jsenv-cooked-by": "jsenv:html_inline_content_analysis",
16101
+ });
16102
+ } else {
16103
+ setHtmlNodeText(node, inlineReference.urlInfo.content, {
16104
+ indentation: false, // indentation would decrease stack trace precision
16105
+ });
16106
+ setHtmlNodeAttributes(node, {
16107
+ "jsenv-cooked-by": "jsenv:html_inline_content_analysis",
16108
+ });
16080
16109
  }
16081
16110
  });
16111
+ });
16112
+ return inlineReference;
16113
+ };
16114
+ const visitTextContent = (
16115
+ node,
16116
+ { type, subtype, expectedType, contentType },
16117
+ ) => {
16118
+ const inlineContent = getHtmlNodeText(node);
16119
+ if (!inlineContent) {
16120
+ return null;
16082
16121
  }
16083
- },
16084
- style: inlineContent
16085
- ? (styleNode) => {
16086
- visitTextContent(styleNode, {
16087
- type: "style",
16088
- expectedType: "css",
16089
- contentType: "text/css",
16122
+ return createInlineReference(node, inlineContent, {
16123
+ type,
16124
+ subtype,
16125
+ expectedType,
16126
+ contentType,
16127
+ });
16128
+ };
16129
+
16130
+ visitNonIgnoredHtmlNode(htmlAst, {
16131
+ link: (linkNode) => {
16132
+ const rel = getHtmlNodeAttribute(linkNode, "rel");
16133
+ const type = getHtmlNodeAttribute(linkNode, "type");
16134
+ const ref = visitHref(linkNode, {
16135
+ type: "link_href",
16136
+ subtype: rel,
16137
+ // https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload#including_a_mime_type
16138
+ expectedContentType: type,
16139
+ });
16140
+ if (ref) {
16141
+ finalizeCallbacks.push(() => {
16142
+ if (ref.expectedType) {
16143
+ // might be set by other plugins, in that case respect it
16144
+ } else {
16145
+ ref.expectedType = decideLinkExpectedType(ref, urlInfo);
16146
+ }
16090
16147
  });
16091
16148
  }
16092
- : null,
16093
- script: (scriptNode) => {
16094
- const { type, subtype, contentType, extension } =
16095
- analyzeScriptNode(scriptNode);
16096
- if (type === "text") {
16097
- // ignore <script type="whatever">foobar</script>
16098
- // per HTML spec https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-type
16099
- return;
16100
- }
16101
- if (type === "importmap") {
16102
- importmapFound = true;
16103
-
16104
- const src = getHtmlNodeAttribute(scriptNode, "src");
16105
- if (src) {
16106
- // Browser would throw on remote importmap
16107
- // and won't sent a request to the server for it
16108
- // We must precook the importmap to know its content and inline it into the HTML
16109
- const importmapReference = createExternalReference(
16110
- scriptNode,
16111
- "src",
16112
- src,
16113
- {
16114
- type: "script",
16115
- subtype: "importmap",
16116
- expectedType: "importmap",
16117
- },
16118
- );
16119
- const { line, column, isOriginal } = getHtmlNodePosition(
16120
- scriptNode,
16121
- {
16122
- preferOriginal: true,
16123
- },
16124
- );
16125
- const importmapInlineUrl = getUrlForContentInsideHtml(
16126
- scriptNode,
16127
- {
16128
- htmlUrl: urlInfo.url,
16129
- },
16130
- );
16131
- const importmapReferenceInlined = importmapReference.inline({
16132
- line: line - 1,
16133
- column,
16134
- isOriginal,
16135
- specifier: importmapInlineUrl,
16136
- contentType: "application/importmap+json",
16137
- });
16138
- const importmapInlineUrlInfo =
16139
- importmapReferenceInlined.urlInfo;
16140
- actions.push(async () => {
16141
- await importmapInlineUrlInfo.cook();
16142
- importmapLoaded(importmapInlineUrlInfo);
16143
- mutations.push(() => {
16144
- setHtmlNodeText(
16145
- scriptNode,
16146
- importmapInlineUrlInfo.content,
16147
- {
16148
- indentation: "auto",
16149
- },
16150
- );
16151
- setHtmlNodeAttributes(scriptNode, {
16152
- "src": undefined,
16153
- "jsenv-inlined-by": "jsenv:html_reference_analysis",
16154
- "inlined-from-src": src,
16155
- });
16149
+ },
16150
+ style: inlineContent
16151
+ ? (styleNode) => {
16152
+ visitTextContent(styleNode, {
16153
+ type: "style",
16154
+ expectedType: "css",
16155
+ contentType: "text/css",
16156
16156
  });
16157
- });
16158
- } else {
16159
- const htmlNodeText = getHtmlNodeText(scriptNode);
16160
- if (htmlNodeText) {
16161
- const importmapReference = createInlineReference(
16157
+ }
16158
+ : null,
16159
+ script: (scriptNode) => {
16160
+ const { type, subtype, contentType, extension } =
16161
+ analyzeScriptNode(scriptNode);
16162
+ if (type === "text") {
16163
+ // ignore <script type="whatever">foobar</script>
16164
+ // per HTML spec https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-type
16165
+ return;
16166
+ }
16167
+ if (type === "importmap") {
16168
+ importmapFound = true;
16169
+
16170
+ const src = getHtmlNodeAttribute(scriptNode, "src");
16171
+ if (src) {
16172
+ // Browser would throw on remote importmap
16173
+ // and won't sent a request to the server for it
16174
+ // We must precook the importmap to know its content and inline it into the HTML
16175
+ const importmapReference = createExternalReference(
16162
16176
  scriptNode,
16163
- htmlNodeText,
16177
+ "src",
16178
+ src,
16164
16179
  {
16165
16180
  type: "script",
16181
+ subtype: "importmap",
16166
16182
  expectedType: "importmap",
16167
- contentType: "application/importmap+json",
16168
16183
  },
16169
16184
  );
16170
- const inlineImportmapUrlInfo = importmapReference.urlInfo;
16185
+ const { line, column, isOriginal } = getHtmlNodePosition(
16186
+ scriptNode,
16187
+ {
16188
+ preferOriginal: true,
16189
+ },
16190
+ );
16191
+ const importmapInlineUrl = getUrlForContentInsideHtml(
16192
+ scriptNode,
16193
+ {
16194
+ htmlUrl: urlInfo.url,
16195
+ },
16196
+ );
16197
+ const importmapReferenceInlined = importmapReference.inline({
16198
+ line: line - 1,
16199
+ column,
16200
+ isOriginal,
16201
+ specifier: importmapInlineUrl,
16202
+ contentType: "application/importmap+json",
16203
+ });
16204
+ const importmapInlineUrlInfo =
16205
+ importmapReferenceInlined.urlInfo;
16171
16206
  actions.push(async () => {
16172
- await inlineImportmapUrlInfo.cook();
16173
- importmapLoaded(inlineImportmapUrlInfo);
16207
+ try {
16208
+ await importmapInlineUrlInfo.cook();
16209
+ } finally {
16210
+ importmapLoaded(importmapInlineUrlInfo);
16211
+ }
16174
16212
  mutations.push(() => {
16175
16213
  setHtmlNodeText(
16176
16214
  scriptNode,
16177
- inlineImportmapUrlInfo.content,
16215
+ importmapInlineUrlInfo.content,
16178
16216
  {
16179
16217
  indentation: "auto",
16180
16218
  },
16181
16219
  );
16182
16220
  setHtmlNodeAttributes(scriptNode, {
16183
- "jsenv-cooked-by": "jsenv:html_reference_analysis",
16221
+ "src": undefined,
16222
+ "jsenv-inlined-by": "jsenv:html_reference_analysis",
16223
+ "inlined-from-src": src,
16224
+ });
16225
+ });
16226
+ });
16227
+ } else {
16228
+ const htmlNodeText = getHtmlNodeText(scriptNode);
16229
+ if (htmlNodeText) {
16230
+ const importmapReference = createInlineReference(
16231
+ scriptNode,
16232
+ htmlNodeText,
16233
+ {
16234
+ type: "script",
16235
+ expectedType: "importmap",
16236
+ contentType: "application/importmap+json",
16237
+ },
16238
+ );
16239
+ const inlineImportmapUrlInfo = importmapReference.urlInfo;
16240
+ actions.push(async () => {
16241
+ try {
16242
+ await inlineImportmapUrlInfo.cook();
16243
+ } finally {
16244
+ importmapLoaded(inlineImportmapUrlInfo);
16245
+ }
16246
+ mutations.push(() => {
16247
+ setHtmlNodeText(
16248
+ scriptNode,
16249
+ inlineImportmapUrlInfo.content,
16250
+ {
16251
+ indentation: "auto",
16252
+ },
16253
+ );
16254
+ setHtmlNodeAttributes(scriptNode, {
16255
+ "jsenv-cooked-by": "jsenv:html_reference_analysis",
16256
+ });
16184
16257
  });
16185
16258
  });
16259
+ }
16260
+ }
16261
+ // once this plugin knows the importmap, it will use it
16262
+ // to map imports. These import specifiers will be normalized
16263
+ // by "formatReference" making the importmap presence useless.
16264
+ // In dev/test we keep importmap into the HTML to see it even if useless
16265
+ // Duing build we get rid of it
16266
+ if (urlInfo.context.build) {
16267
+ mutations.push(() => {
16268
+ removeHtmlNode(scriptNode);
16186
16269
  });
16187
16270
  }
16271
+ return;
16188
16272
  }
16189
- // once this plugin knows the importmap, it will use it
16190
- // to map imports. These import specifiers will be normalized
16191
- // by "formatReference" making the importmap presence useless.
16192
- // In dev/test we keep importmap into the HTML to see it even if useless
16193
- // Duing build we get rid of it
16194
- if (urlInfo.context.build) {
16195
- mutations.push(() => {
16196
- removeHtmlNode(scriptNode);
16197
- });
16273
+ const externalRef = visitSrc(scriptNode, {
16274
+ type: "script",
16275
+ subtype: type,
16276
+ expectedType: type,
16277
+ });
16278
+ if (externalRef) {
16279
+ return;
16198
16280
  }
16199
- return;
16200
- }
16201
- const externalRef = visitSrc(scriptNode, {
16202
- type: "script",
16203
- subtype: type,
16204
- expectedType: type,
16205
- });
16206
- if (externalRef) {
16207
- return;
16208
- }
16209
16281
 
16210
- // now visit the content, if any
16211
- if (!inlineContent) {
16212
- return;
16213
- }
16214
- // If the inline script was already handled by an other plugin, ignore it
16215
- // - we want to preserve inline scripts generated by html supervisor during dev
16216
- // - we want to avoid cooking twice a script during build
16217
- if (
16218
- !inlineConvertedScript &&
16219
- getHtmlNodeAttribute(scriptNode, "jsenv-injected-by") ===
16220
- "jsenv:js_module_fallback"
16221
- ) {
16222
- return;
16223
- }
16282
+ // now visit the content, if any
16283
+ if (!inlineContent) {
16284
+ return;
16285
+ }
16286
+ // If the inline script was already handled by an other plugin, ignore it
16287
+ // - we want to preserve inline scripts generated by html supervisor during dev
16288
+ // - we want to avoid cooking twice a script during build
16289
+ if (
16290
+ !inlineConvertedScript &&
16291
+ getHtmlNodeAttribute(scriptNode, "jsenv-injected-by") ===
16292
+ "jsenv:js_module_fallback"
16293
+ ) {
16294
+ return;
16295
+ }
16224
16296
 
16225
- const inlineRef = visitTextContent(scriptNode, {
16226
- type: "script",
16227
- subtype,
16228
- expectedType: type,
16229
- contentType,
16230
- });
16231
- if (inlineRef) {
16232
- // 1. <script type="jsx"> becomes <script>
16233
- if (type === "js_classic" && extension !== ".js") {
16234
- mutations.push(() => {
16235
- setHtmlNodeAttributes(scriptNode, {
16236
- type: undefined,
16297
+ const inlineRef = visitTextContent(scriptNode, {
16298
+ type: "script",
16299
+ subtype,
16300
+ expectedType: type,
16301
+ contentType,
16302
+ });
16303
+ if (inlineRef) {
16304
+ // 1. <script type="jsx"> becomes <script>
16305
+ if (type === "js_classic" && extension !== ".js") {
16306
+ mutations.push(() => {
16307
+ setHtmlNodeAttributes(scriptNode, {
16308
+ type: undefined,
16309
+ });
16237
16310
  });
16238
- });
16239
- }
16240
- // 2. <script type="module/jsx"> becomes <script type="module">
16241
- if (type === "js_module" && extension !== ".js") {
16242
- mutations.push(() => {
16243
- setHtmlNodeAttributes(scriptNode, {
16244
- type: "module",
16311
+ }
16312
+ // 2. <script type="module/jsx"> becomes <script type="module">
16313
+ if (type === "js_module" && extension !== ".js") {
16314
+ mutations.push(() => {
16315
+ setHtmlNodeAttributes(scriptNode, {
16316
+ type: "module",
16317
+ });
16245
16318
  });
16246
- });
16319
+ }
16247
16320
  }
16248
- }
16249
- },
16250
- a: (aNode) => {
16251
- visitHref(aNode, {
16252
- type: "a_href",
16253
- });
16254
- },
16255
- iframe: (iframeNode) => {
16256
- visitSrc(iframeNode, {
16257
- type: "iframe_src",
16258
- });
16259
- },
16260
- img: (imgNode) => {
16261
- visitSrc(imgNode, {
16262
- type: "img_src",
16263
- });
16264
- visitSrcset(imgNode, {
16265
- type: "img_srcset",
16266
- });
16267
- },
16268
- source: (sourceNode) => {
16269
- visitSrc(sourceNode, {
16270
- type: "source_src",
16271
- });
16272
- visitSrcset(sourceNode, {
16273
- type: "source_srcset",
16274
- });
16275
- },
16276
- // svg <image> tag
16277
- image: (imageNode) => {
16278
- visitHref(imageNode, {
16279
- type: "image_href",
16280
- });
16281
- },
16282
- use: (useNode) => {
16283
- visitHref(useNode, {
16284
- type: "use_href",
16285
- });
16286
- },
16287
- });
16288
- if (!importmapFound) {
16289
- importmapLoaded();
16290
- }
16291
- finalizeCallbacks.forEach((finalizeCallback) => {
16292
- finalizeCallback();
16293
- });
16321
+ },
16322
+ a: (aNode) => {
16323
+ visitHref(aNode, {
16324
+ type: "a_href",
16325
+ });
16326
+ },
16327
+ iframe: (iframeNode) => {
16328
+ visitSrc(iframeNode, {
16329
+ type: "iframe_src",
16330
+ });
16331
+ },
16332
+ img: (imgNode) => {
16333
+ visitSrc(imgNode, {
16334
+ type: "img_src",
16335
+ });
16336
+ visitSrcset(imgNode, {
16337
+ type: "img_srcset",
16338
+ });
16339
+ },
16340
+ source: (sourceNode) => {
16341
+ visitSrc(sourceNode, {
16342
+ type: "source_src",
16343
+ });
16344
+ visitSrcset(sourceNode, {
16345
+ type: "source_srcset",
16346
+ });
16347
+ },
16348
+ // svg <image> tag
16349
+ image: (imageNode) => {
16350
+ visitHref(imageNode, {
16351
+ type: "image_href",
16352
+ });
16353
+ },
16354
+ use: (useNode) => {
16355
+ visitHref(useNode, {
16356
+ type: "use_href",
16357
+ });
16358
+ },
16359
+ });
16360
+ if (!importmapFound) {
16361
+ importmapLoaded();
16362
+ }
16363
+ finalizeCallbacks.forEach((finalizeCallback) => {
16364
+ finalizeCallback();
16365
+ });
16294
16366
 
16295
- if (actions.length > 0) {
16296
- await Promise.all(actions.map((action) => action()));
16297
- actions.length = 0;
16298
- }
16299
- if (mutations.length === 0) {
16300
- return null;
16367
+ if (actions.length > 0) {
16368
+ await Promise.all(actions.map((action) => action()));
16369
+ actions.length = 0;
16370
+ }
16371
+ if (mutations.length === 0) {
16372
+ return null;
16373
+ }
16374
+ mutations.forEach((mutation) => mutation());
16375
+ mutations.length = 0;
16376
+ return stringifyHtmlAst(htmlAst);
16377
+ } catch (e) {
16378
+ importmapLoaded();
16379
+ throw e;
16301
16380
  }
16302
- mutations.forEach((mutation) => mutation());
16303
- mutations.length = 0;
16304
- return stringifyHtmlAst(htmlAst);
16305
16381
  },
16306
16382
  },
16307
16383
  };
16308
16384
  };
16309
16385
 
16386
+ const visitNonIgnoredHtmlNode = (htmlAst, visitors) => {
16387
+ const visitorsInstrumented = {};
16388
+ for (const key of Object.keys(visitors)) {
16389
+ visitorsInstrumented[key] = (node) => {
16390
+ const jsenvIgnoreAttribute = getHtmlNodeAttribute(node, "jsenv-ignore");
16391
+ if (jsenvIgnoreAttribute !== undefined) {
16392
+ return;
16393
+ }
16394
+ visitors[key](node);
16395
+ };
16396
+ }
16397
+ visitHtmlNodes(htmlAst, visitorsInstrumented);
16398
+ };
16399
+
16400
+ const generateHtmlForSyntaxError = (
16401
+ htmlSyntaxError,
16402
+ { htmlUrl, rootDirectoryUrl, htmlErrorContentFrame },
16403
+ ) => {
16404
+ const htmlForSyntaxError = String(readFileSync(htmlSyntaxErrorFileUrl));
16405
+ const htmlRelativeUrl = urlToRelativeUrl(htmlUrl, rootDirectoryUrl);
16406
+ const { line, column } = htmlSyntaxError;
16407
+ const urlWithLineAndColumn = `${htmlUrl}:${line}:${column}`;
16408
+ const replacers = {
16409
+ fileRelativeUrl: htmlRelativeUrl,
16410
+ reasonCode: htmlSyntaxError.reasonCode,
16411
+ errorLinkHref: `javascript:window.fetch('/__open_in_editor__/${encodeURIComponent(
16412
+ urlWithLineAndColumn,
16413
+ )}')`,
16414
+ errorLinkText: `${htmlRelativeUrl}:${line}:${column}`,
16415
+ syntaxError: escapeHtml(htmlErrorContentFrame),
16416
+ };
16417
+ const html = replacePlaceholders$2(htmlForSyntaxError, replacers);
16418
+ return html;
16419
+ };
16420
+ const escapeHtml = (string) => {
16421
+ return string
16422
+ .replace(/&/g, "&amp;")
16423
+ .replace(/</g, "&lt;")
16424
+ .replace(/>/g, "&gt;")
16425
+ .replace(/"/g, "&quot;")
16426
+ .replace(/'/g, "&#039;");
16427
+ };
16428
+ const replacePlaceholders$2 = (html, replacers) => {
16429
+ return html.replace(/\${([\w]+)}/g, (match, name) => {
16430
+ const replacer = replacers[name];
16431
+ if (replacer === undefined) {
16432
+ return match;
16433
+ }
16434
+ if (typeof replacer === "function") {
16435
+ return replacer();
16436
+ }
16437
+ return replacer;
16438
+ });
16439
+ };
16440
+
16310
16441
  const crossOriginCompatibleTagNames = ["script", "link", "img", "source"];
16311
16442
  const integrityCompatibleTagNames = ["script", "link", "img", "source"];
16312
16443
  const readFetchMetas = (node) => {
@@ -18036,6 +18167,16 @@ const jsenvPluginVersionSearchParam = () => {
18036
18167
  };
18037
18168
  };
18038
18169
 
18170
+ const html404AndParentDirIsEmptyFileUrl = new URL(
18171
+ "./html/html_404_and_parent_dir_is_empty.html",
18172
+ import.meta.url,
18173
+ );
18174
+ const html404AndParentDirFileUrl = new URL(
18175
+ "./html/html_404_and_parent_dir.html",
18176
+ import.meta.url,
18177
+ );
18178
+ const htmlFileUrlForDirectory = new URL("./html/directory.html", import.meta.url);
18179
+
18039
18180
  const jsenvPluginProtocolFile = ({
18040
18181
  magicExtensions = ["inherit", ".js"],
18041
18182
  magicDirectoryIndex = true,
@@ -18122,7 +18263,9 @@ const jsenvPluginProtocolFile = ({
18122
18263
  reference.leadsToADirectory = stat && stat.isDirectory();
18123
18264
  if (reference.leadsToADirectory) {
18124
18265
  let actionForDirectory;
18125
- if (
18266
+ if (reference.type === "a_href") {
18267
+ actionForDirectory = "ignore";
18268
+ } else if (
18126
18269
  reference.type === "http_request" ||
18127
18270
  reference.type === "filesystem"
18128
18271
  ) {
@@ -18207,17 +18350,33 @@ const jsenvPluginProtocolFile = ({
18207
18350
  urlInfo.filenameHint = `${urlToFilename$1(urlInfo.url)}/`;
18208
18351
  }
18209
18352
  }
18210
- const { headers, body } = serveDirectory(urlObject.href, {
18211
- headers: urlInfo.context.request
18212
- ? urlInfo.context.request.headers
18213
- : {},
18214
- rootDirectoryUrl: urlInfo.context.rootDirectoryUrl,
18215
- });
18353
+ const directoryContentArray = readdirSync(urlObject);
18354
+ if (urlInfo.firstReference.type === "filesystem") {
18355
+ const content = JSON.stringify(directoryContentArray, null, " ");
18356
+ return {
18357
+ type: "directory",
18358
+ contentType: "application/json",
18359
+ content,
18360
+ };
18361
+ }
18362
+ const acceptsHtml = urlInfo.context.request
18363
+ ? pickContentType(urlInfo.context.request, ["text/html"])
18364
+ : false;
18365
+ if (acceptsHtml) {
18366
+ const html = generateHtmlForDirectory(
18367
+ urlObject.href,
18368
+ directoryContentArray,
18369
+ urlInfo.context.rootDirectoryUrl,
18370
+ );
18371
+ return {
18372
+ contentType: "text/html",
18373
+ content: html,
18374
+ };
18375
+ }
18216
18376
  return {
18217
18377
  type: "directory",
18218
- contentType: headers["content-type"],
18219
- contentLength: headers["content-length"],
18220
- content: body,
18378
+ contentType: "application/json",
18379
+ content: JSON.stringify(directoryContentArray, null, " "),
18221
18380
  };
18222
18381
  }
18223
18382
  if (
@@ -18227,20 +18386,144 @@ const jsenvPluginProtocolFile = ({
18227
18386
  urlInfo.dirnameHint =
18228
18387
  urlInfo.firstReference.ownerUrlInfo.filenameHint;
18229
18388
  }
18230
- const fileBuffer = readFileSync(urlObject);
18231
18389
  const contentType = CONTENT_TYPE.fromUrlExtension(urlInfo.url);
18390
+ if (contentType === "text/html") {
18391
+ try {
18392
+ const fileBuffer = readFileSync(urlObject);
18393
+ const content = String(fileBuffer);
18394
+ return {
18395
+ content,
18396
+ contentType,
18397
+ contentLength: fileBuffer.length,
18398
+ };
18399
+ } catch (e) {
18400
+ if (e.code !== "ENOENT") {
18401
+ throw e;
18402
+ }
18403
+ const parentDirectoryUrl = new URL("./", urlInfo.url);
18404
+ if (!existsSync(parentDirectoryUrl)) {
18405
+ throw e;
18406
+ }
18407
+ const parentDirectoryContentArray = readdirSync(
18408
+ new URL(parentDirectoryUrl),
18409
+ );
18410
+ const html = generateHtmlForENOENTOnHtmlFile(
18411
+ urlInfo.url,
18412
+ parentDirectoryContentArray,
18413
+ parentDirectoryUrl,
18414
+ urlInfo.context.rootDirectoryUrl,
18415
+ );
18416
+ return {
18417
+ contentType: "text/html",
18418
+ content: html,
18419
+ };
18420
+ }
18421
+ }
18422
+ const fileBuffer = readFileSync(urlObject);
18232
18423
  const content = CONTENT_TYPE.isTextual(contentType)
18233
18424
  ? String(fileBuffer)
18234
18425
  : fileBuffer;
18235
18426
  return {
18236
18427
  content,
18237
18428
  contentType,
18429
+ contentLength: fileBuffer.length,
18238
18430
  };
18239
18431
  },
18240
18432
  },
18241
18433
  ];
18242
18434
  };
18243
18435
 
18436
+ const generateHtmlForDirectory = (
18437
+ directoryUrl,
18438
+ directoryContentArray,
18439
+ rootDirectoryUrl,
18440
+ ) => {
18441
+ directoryUrl = assertAndNormalizeDirectoryUrl(directoryUrl);
18442
+ const htmlForDirectory = String(readFileSync(htmlFileUrlForDirectory));
18443
+ const replacers = {
18444
+ directoryRelativeUrl: urlToRelativeUrl(directoryUrl, rootDirectoryUrl),
18445
+ directoryUrl,
18446
+ directoryContent: () =>
18447
+ generateDirectoryContent(
18448
+ directoryContentArray,
18449
+ directoryUrl,
18450
+ rootDirectoryUrl,
18451
+ ),
18452
+ };
18453
+ const html = replacePlaceholders$1(htmlForDirectory, replacers);
18454
+ return html;
18455
+ };
18456
+ const generateHtmlForENOENTOnHtmlFile = (
18457
+ url,
18458
+ parentDirectoryContentArray,
18459
+ parentDirectoryUrl,
18460
+ rootDirectoryUrl,
18461
+ ) => {
18462
+ if (parentDirectoryContentArray.length === 0) {
18463
+ const htmlFor404AndParentDirIsEmpty = String(
18464
+ readFileSync(html404AndParentDirIsEmptyFileUrl),
18465
+ );
18466
+ return replacePlaceholders$1(htmlFor404AndParentDirIsEmpty, {
18467
+ fileRelativeUrl: urlToRelativeUrl(url, rootDirectoryUrl),
18468
+ parentDirectoryRelativeUrl: urlToRelativeUrl(
18469
+ parentDirectoryUrl,
18470
+ rootDirectoryUrl,
18471
+ ),
18472
+ });
18473
+ }
18474
+ const htmlFor404AndParentDir = String(
18475
+ readFileSync(html404AndParentDirFileUrl),
18476
+ );
18477
+
18478
+ const replacers = {
18479
+ fileUrl: url,
18480
+ fileRelativeUrl: urlToRelativeUrl(url, rootDirectoryUrl),
18481
+ parentDirectoryUrl,
18482
+ parentDirectoryRelativeUrl: urlToRelativeUrl(
18483
+ parentDirectoryUrl,
18484
+ rootDirectoryUrl,
18485
+ ),
18486
+ parentDirectoryContent: () =>
18487
+ generateDirectoryContent(
18488
+ parentDirectoryContentArray,
18489
+ parentDirectoryUrl,
18490
+ rootDirectoryUrl,
18491
+ ),
18492
+ };
18493
+ const html = replacePlaceholders$1(htmlFor404AndParentDir, replacers);
18494
+ return html;
18495
+ };
18496
+ const generateDirectoryContent = (
18497
+ directoryContentArray,
18498
+ directoryUrl,
18499
+ rootDirectoryUrl,
18500
+ ) => {
18501
+ return directoryContentArray.map((filename) => {
18502
+ const fileUrlObject = new URL(filename, directoryUrl);
18503
+ const fileUrl = String(fileUrlObject);
18504
+ let fileUrlRelative = urlToRelativeUrl(fileUrl, rootDirectoryUrl);
18505
+ if (lstatSync(fileUrlObject).isDirectory()) {
18506
+ fileUrlRelative += "/";
18507
+ }
18508
+ return `<li>
18509
+ <a href="/${fileUrlRelative}">/${fileUrlRelative}</a>
18510
+ </li>`;
18511
+ }).join(`
18512
+ `);
18513
+ };
18514
+ const replacePlaceholders$1 = (html, replacers) => {
18515
+ return html.replace(/\${([\w]+)}/g, (match, name) => {
18516
+ const replacer = replacers[name];
18517
+ if (replacer === undefined) {
18518
+ return match;
18519
+ }
18520
+ if (typeof replacer === "function") {
18521
+ return replacer();
18522
+ }
18523
+ return replacer;
18524
+ });
18525
+ };
18526
+
18244
18527
  const resolveSymlink = (fileUrl) => {
18245
18528
  const urlObject = new URL(fileUrl);
18246
18529
  const realpath = realpathSync(urlObject);
@@ -19409,12 +19692,21 @@ const jsenvPluginAutoreloadClient = () => {
19409
19692
  expectedType: "js_module",
19410
19693
  specifier: autoreloadClientFileUrl,
19411
19694
  });
19695
+ const paramsJson = JSON.stringify(
19696
+ {
19697
+ mainFilePath: `/${htmlUrlInfo.kitchen.context.mainFilePath}`,
19698
+ },
19699
+ null,
19700
+ " ",
19701
+ );
19412
19702
  injectHtmlNodeAsEarlyAsPossible(
19413
19703
  htmlAst,
19414
19704
  createHtmlNode({
19415
19705
  tagName: "script",
19416
19706
  type: "module",
19417
- src: autoreloadClientReference.generatedSpecifier,
19707
+ textContent: `import { initAutoreload } from "${autoreloadClientReference.generatedSpecifier}";
19708
+
19709
+ initAutoreload(${paramsJson});`,
19418
19710
  }),
19419
19711
  "jsenv:autoreload_client",
19420
19712
  );
@@ -19465,7 +19757,10 @@ const jsenvPluginAutoreloadServer = ({
19465
19757
  : `a dependent file accepts hot reload`,
19466
19758
  };
19467
19759
  }
19468
- if (urlInfo.data.hotDecline) {
19760
+ if (
19761
+ urlInfo.data.hotDecline ||
19762
+ urlInfo.firstReference?.type === "http_request"
19763
+ ) {
19469
19764
  return {
19470
19765
  declined: true,
19471
19766
  reason: `file declines hot reload`,
@@ -19859,7 +20154,7 @@ const jsenvPluginRibbon = ({
19859
20154
  createHtmlNode({
19860
20155
  tagName: "script",
19861
20156
  type: "module",
19862
- textContent: `import { injectRibbon } from "${ribbonClientFileReference.generatedSpecifier}"
20157
+ textContent: `import { injectRibbon } from "${ribbonClientFileReference.generatedSpecifier}";
19863
20158
 
19864
20159
  injectRibbon(${paramsJson});`,
19865
20160
  }),
@@ -22302,11 +22597,18 @@ const startDevServer = async ({
22302
22597
  sourceDirectoryUrl,
22303
22598
  "sourceDirectoryUrl",
22304
22599
  );
22600
+ if (!existsSync(new URL(sourceDirectoryUrl))) {
22601
+ throw new Error(`ENOENT on sourceDirectoryUrl at ${sourceDirectoryUrl}`);
22602
+ }
22305
22603
  if (typeof sourceMainFilePath !== "string") {
22306
22604
  throw new TypeError(
22307
22605
  `sourceMainFilePath must be a string, got ${sourceMainFilePath}`,
22308
22606
  );
22309
22607
  }
22608
+ sourceMainFilePath = urlToRelativeUrl(
22609
+ new URL(sourceMainFilePath, sourceDirectoryUrl),
22610
+ sourceDirectoryUrl,
22611
+ );
22310
22612
  if (outDirectoryUrl === undefined) {
22311
22613
  if (!process.env.CI) {
22312
22614
  const packageDirectoryUrl = lookupPackageDirectory(sourceDirectoryUrl);