@jsenv/core 38.2.11 → 38.3.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.
@@ -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, parseHtmlString, visitHtmlNodes, getHtmlNodeAttribute, analyzeScriptNode, getHtmlNodeText, stringifyHtmlAst, setHtmlNodeAttributes, injectHtmlNodeAsEarlyAsPossible, createHtmlNode, generateUrlForInlineContent, parseJsWithAcorn, parseSrcSet, getHtmlNodePosition, getHtmlNodeAttributePosition, getUrlForContentInsideHtml, removeHtmlNodeText, setHtmlNodeText, parseCssUrls, parseJsUrls, getUrlForContentInsideJs, findHtmlNode, removeHtmlNode, analyzeLinkNode, injectHtmlNode, insertHtmlNodeAfter } from "@jsenv/ast";
16
+ import { injectJsImport, visitJsAstUntil, applyBabelPlugins, parseHtmlString, visitHtmlNodes, getHtmlNodeAttribute, analyzeScriptNode, getHtmlNodeText, stringifyHtmlAst, setHtmlNodeAttributes, injectHtmlNodeAsEarlyAsPossible, createHtmlNode, generateUrlForInlineContent, parseJsWithAcorn, getHtmlNodePosition, getUrlForContentInsideHtml, getHtmlNodeAttributePosition, parseSrcSet, removeHtmlNodeText, setHtmlNodeText, removeHtmlNode, parseCssUrls, parseJsUrls, 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";
@@ -11814,12 +11814,12 @@ const createReference = ({
11814
11814
  ownerUrlInfo.context.finalizeReference(reference);
11815
11815
  };
11816
11816
 
11817
- // "formatReferencedUrl" can be async BUT this is an exception
11817
+ // "formatReference" can be async BUT this is an exception
11818
11818
  // for most cases it will be sync. We want to favor the sync signature to keep things simpler
11819
11819
  // The only case where it needs to be async is when
11820
11820
  // the specifier is a `data:*` url
11821
11821
  // in this case we'll wait for the promise returned by
11822
- // "formatReferencedUrl"
11822
+ // "formatReference"
11823
11823
  reference.readGeneratedSpecifier = () => {
11824
11824
  if (reference.generatedSpecifier.then) {
11825
11825
  return reference.generatedSpecifier.then((value) => {
@@ -12398,6 +12398,9 @@ const createUrlInfo = (url, context) => {
12398
12398
  continue;
12399
12399
  }
12400
12400
  if (ref.gotInlined()) {
12401
+ if (ref.ownerUrlInfo.isUsed()) {
12402
+ return true;
12403
+ }
12401
12404
  // the url info was inlined, an other reference is required
12402
12405
  // to consider the non-inlined urlInfo as used
12403
12406
  continue;
@@ -12497,7 +12500,7 @@ const createUrlInfo = (url, context) => {
12497
12500
  };
12498
12501
  urlInfo.onModified = ({ modifiedTimestamp = Date.now() } = {}) => {
12499
12502
  const visitedSet = new Set();
12500
- const iterate = (urlInfo) => {
12503
+ const considerModified = (urlInfo) => {
12501
12504
  if (visitedSet.has(urlInfo)) {
12502
12505
  return;
12503
12506
  }
@@ -12507,14 +12510,21 @@ const createUrlInfo = (url, context) => {
12507
12510
  for (const referenceToOther of urlInfo.referenceToOthersSet) {
12508
12511
  const referencedUrlInfo = referenceToOther.urlInfo;
12509
12512
  if (referencedUrlInfo.isInline) {
12510
- iterate(referencedUrlInfo);
12513
+ considerModified(referencedUrlInfo);
12514
+ }
12515
+ }
12516
+ for (const referenceFromOther of urlInfo.referenceFromOthersSet) {
12517
+ if (referenceFromOther.gotInlined()) {
12518
+ const urlInfoReferencingThisOne = referenceFromOther.ownerUrlInfo;
12519
+ considerModified(urlInfoReferencingThisOne);
12511
12520
  }
12512
12521
  }
12513
12522
  for (const searchParamVariant of urlInfo.searchParamVariantSet) {
12514
- iterate(searchParamVariant);
12523
+ considerModified(searchParamVariant);
12515
12524
  }
12516
12525
  };
12517
- iterate(urlInfo);
12526
+ considerModified(urlInfo);
12527
+ visitedSet.clear();
12518
12528
  };
12519
12529
  urlInfo.onDereferenced = (lastReferenceFromOther) => {
12520
12530
  urlInfo.dereferencedTimestamp = Date.now();
@@ -15011,922 +15021,228 @@ const base64ToString = (base64String) =>
15011
15021
  Buffer.from(base64String, "base64").toString("utf8");
15012
15022
  const dataToBase64 = (data) => Buffer.from(data).toString("base64");
15013
15023
 
15014
- const jsenvPluginHtmlReferenceAnalysis = ({
15015
- inlineContent,
15016
- inlineConvertedScript,
15017
- }) => {
15018
- return {
15019
- name: "jsenv:html_reference_analysis",
15020
- appliesDuring: "*",
15021
- transformUrlContent: {
15022
- html: (urlInfo) =>
15023
- parseAndTransformHtmlReferences(urlInfo, {
15024
- inlineContent,
15025
- inlineConvertedScript,
15026
- }),
15027
- },
15028
- };
15029
- };
15024
+ // duplicated from @jsenv/log to avoid the dependency
15025
+ const createDetailedMessage = (message, details = {}) => {
15026
+ let string = `${message}`;
15030
15027
 
15031
- const parseAndTransformHtmlReferences = async (
15032
- urlInfo,
15033
- { inlineContent, inlineConvertedScript },
15034
- ) => {
15035
- const content = urlInfo.content;
15036
- const htmlAst = parseHtmlString(content);
15028
+ Object.keys(details).forEach((key) => {
15029
+ const value = details[key];
15030
+ string += `
15031
+ --- ${key} ---
15032
+ ${
15033
+ Array.isArray(value)
15034
+ ? value.join(`
15035
+ `)
15036
+ : value
15037
+ }`;
15038
+ });
15037
15039
 
15038
- const mutations = [];
15039
- const actions = [];
15040
- const finalizeCallbacks = [];
15040
+ return string
15041
+ };
15041
15042
 
15042
- const createExternalReference = (
15043
- node,
15044
- attributeName,
15045
- attributeValue,
15046
- { type, subtype, expectedType, ...rest },
15047
- ) => {
15048
- let position;
15049
- if (getHtmlNodeAttribute(node, "jsenv-cooked-by")) {
15050
- // when generated from inline content,
15051
- // line, column is not "src" nor "inlined-from-src" but "original-position"
15052
- position = getHtmlNodePosition(node);
15053
- } else {
15054
- position = getHtmlNodeAttributePosition(node, attributeName);
15055
- }
15056
- const {
15057
- line,
15058
- column,
15059
- // originalLine, originalColumn
15060
- } = position;
15061
- const debug = getHtmlNodeAttribute(node, "jsenv-debug") !== undefined;
15062
-
15063
- const { crossorigin, integrity } = readFetchMetas(node);
15064
- const isResourceHint = [
15065
- "preconnect",
15066
- "dns-prefetch",
15067
- "prefetch",
15068
- "preload",
15069
- "modulepreload",
15070
- ].includes(subtype);
15071
- let attributeLocation = node.sourceCodeLocation.attrs[attributeName];
15072
- if (
15073
- !attributeLocation &&
15074
- attributeName === "href" &&
15075
- (node.tagName === "use" || node.tagName === "image")
15076
- ) {
15077
- attributeLocation = node.sourceCodeLocation.attrs["xlink:href"];
15078
- }
15079
- const attributeStart = attributeLocation.startOffset;
15080
- const attributeValueStart = urlInfo.content.indexOf(
15081
- attributeValue,
15082
- attributeStart + `${attributeName}=`.length,
15083
- );
15084
- const attributeValueEnd = attributeValueStart + attributeValue.length;
15085
- const reference = urlInfo.dependencies.found({
15086
- type,
15087
- subtype,
15088
- expectedType,
15089
- specifier: attributeValue,
15090
- specifierLine: line,
15091
- specifierColumn: column,
15092
- specifierStart: attributeValueStart,
15093
- specifierEnd: attributeValueEnd,
15094
- isResourceHint,
15095
- isWeak: isResourceHint,
15096
- crossorigin,
15097
- integrity,
15098
- debug,
15099
- astInfo: { node, attributeName },
15100
- ...rest,
15101
- });
15102
- actions.push(async () => {
15103
- await reference.readGeneratedSpecifier();
15104
- mutations.push(() => {
15105
- setHtmlNodeAttributes(node, {
15106
- [attributeName]: reference.generatedSpecifier,
15107
- });
15108
- });
15109
- });
15110
- return reference;
15111
- };
15112
- const visitHref = (node, referenceProps) => {
15113
- const href = getHtmlNodeAttribute(node, "href");
15114
- if (href) {
15115
- return createExternalReference(node, "href", href, referenceProps);
15116
- }
15117
- return null;
15118
- };
15119
- const visitSrc = (node, referenceProps) => {
15120
- const src = getHtmlNodeAttribute(node, "src");
15121
- if (src) {
15122
- return createExternalReference(node, "src", src, referenceProps);
15123
- }
15124
- return null;
15125
- };
15126
- const visitSrcset = (node, referenceProps) => {
15127
- const srcset = getHtmlNodeAttribute(node, "srcset");
15128
- if (srcset) {
15129
- const srcCandidates = parseSrcSet(srcset);
15130
- return srcCandidates.map((srcCandidate) => {
15131
- return createExternalReference(
15132
- node,
15133
- "srcset",
15134
- srcCandidate.specifier,
15135
- referenceProps,
15136
- );
15137
- });
15138
- }
15139
- return null;
15140
- };
15043
+ const assertImportMap = (value) => {
15044
+ if (value === null) {
15045
+ throw new TypeError(`an importMap must be an object, got null`)
15046
+ }
15141
15047
 
15142
- const createInlineReference = (
15143
- node,
15144
- inlineContent,
15145
- { type, expectedType, contentType },
15146
- ) => {
15147
- const hotAccept = getHtmlNodeAttribute(node, "hot-accept") !== undefined;
15148
- const { line, column, isOriginal } = getHtmlNodePosition(node, {
15149
- preferOriginal: true,
15150
- });
15151
- const inlineContentUrl = getUrlForContentInsideHtml(node, {
15152
- htmlUrl: urlInfo.url,
15153
- });
15154
- const debug = getHtmlNodeAttribute(node, "jsenv-debug") !== undefined;
15155
- const inlineReference = urlInfo.dependencies.foundInline({
15156
- type,
15157
- expectedType,
15158
- isOriginalPosition: isOriginal,
15159
- // we remove 1 to the line because imagine the following html:
15160
- // <style>body { color: red; }</style>
15161
- // -> content starts same line as <style> (same for <script>)
15162
- specifierLine: line - 1,
15163
- specifierColumn: column,
15164
- specifier: inlineContentUrl,
15165
- contentType,
15166
- content: inlineContent,
15167
- debug,
15168
- astInfo: { node },
15169
- });
15048
+ const type = typeof value;
15049
+ if (type !== "object") {
15050
+ throw new TypeError(`an importMap must be an object, received ${value}`)
15051
+ }
15170
15052
 
15171
- actions.push(async () => {
15172
- await inlineReference.urlInfo.cook();
15173
- mutations.push(() => {
15174
- if (hotAccept) {
15175
- removeHtmlNodeText(node);
15176
- setHtmlNodeAttributes(node, {
15177
- "jsenv-cooked-by": "jsenv:html_inline_content_analysis",
15178
- });
15179
- } else {
15180
- setHtmlNodeText(node, inlineReference.urlInfo.content, {
15181
- indentation: false, // indentation would decrease stack trace precision
15182
- });
15183
- setHtmlNodeAttributes(node, {
15184
- "jsenv-cooked-by": "jsenv:html_inline_content_analysis",
15185
- });
15186
- }
15187
- });
15188
- });
15189
- return inlineReference;
15190
- };
15191
- const visitTextContent = (
15192
- node,
15193
- { type, subtype, expectedType, contentType },
15194
- ) => {
15195
- const inlineContent = getHtmlNodeText(node);
15196
- if (!inlineContent) {
15197
- return null;
15198
- }
15199
- return createInlineReference(node, inlineContent, {
15200
- type,
15201
- subtype,
15202
- expectedType,
15203
- contentType,
15204
- });
15205
- };
15053
+ if (Array.isArray(value)) {
15054
+ throw new TypeError(
15055
+ `an importMap must be an object, received array ${value}`,
15056
+ )
15057
+ }
15058
+ };
15206
15059
 
15207
- visitHtmlNodes(htmlAst, {
15208
- link: (linkNode) => {
15209
- const rel = getHtmlNodeAttribute(linkNode, "rel");
15210
- const type = getHtmlNodeAttribute(linkNode, "type");
15211
- const ref = visitHref(linkNode, {
15212
- type: "link_href",
15213
- subtype: rel,
15214
- // https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload#including_a_mime_type
15215
- expectedContentType: type,
15216
- });
15217
- if (ref) {
15218
- finalizeCallbacks.push(() => {
15219
- if (ref.expectedType) ; else {
15220
- ref.expectedType = decideLinkExpectedType(ref, urlInfo);
15221
- }
15222
- });
15223
- }
15224
- },
15225
- style: inlineContent
15226
- ? (styleNode) => {
15227
- visitTextContent(styleNode, {
15228
- type: "style",
15229
- expectedType: "css",
15230
- contentType: "text/css",
15231
- });
15232
- }
15233
- : null,
15234
- script: (scriptNode) => {
15235
- // during build the importmap is inlined
15236
- // and shoud not be considered as a dependency anymore
15237
- if (
15238
- getHtmlNodeAttribute(scriptNode, "jsenv-inlined-by") ===
15239
- "jsenv:importmap"
15240
- ) {
15241
- return;
15242
- }
15060
+ const hasScheme = (string) => {
15061
+ return /^[a-zA-Z]{2,}:/.test(string)
15062
+ };
15243
15063
 
15244
- const { type, subtype, contentType, extension } =
15245
- analyzeScriptNode(scriptNode);
15246
- // ignore <script type="whatever">foobar</script>
15247
- // per HTML spec https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-type
15248
- if (type !== "text") {
15249
- const externalRef = visitSrc(scriptNode, {
15250
- type: "script",
15251
- subtype: type,
15252
- expectedType: type,
15253
- });
15254
- if (externalRef) {
15255
- return;
15256
- }
15257
- }
15064
+ const urlToScheme = (urlString) => {
15065
+ const colonIndex = urlString.indexOf(":");
15066
+ if (colonIndex === -1) return ""
15067
+ return urlString.slice(0, colonIndex)
15068
+ };
15258
15069
 
15259
- // now visit the content, if any
15260
- if (!inlineContent) {
15261
- return;
15262
- }
15263
- // If the inline script was already handled by an other plugin, ignore it
15264
- // - we want to preserve inline scripts generated by html supervisor during dev
15265
- // - we want to avoid cooking twice a script during build
15266
- if (
15267
- !inlineConvertedScript &&
15268
- getHtmlNodeAttribute(scriptNode, "jsenv-injected-by") ===
15269
- "jsenv:js_module_fallback"
15270
- ) {
15271
- return;
15272
- }
15070
+ const urlToPathname = (urlString) => {
15071
+ return ressourceToPathname(urlToRessource(urlString))
15072
+ };
15273
15073
 
15274
- const inlineRef = visitTextContent(scriptNode, {
15275
- type: "script",
15276
- subtype,
15277
- expectedType: type,
15278
- contentType,
15279
- });
15280
- if (inlineRef) {
15281
- // 1. <script type="jsx"> becomes <script>
15282
- if (type === "js_classic" && extension !== ".js") {
15283
- mutations.push(() => {
15284
- setHtmlNodeAttributes(scriptNode, {
15285
- type: undefined,
15286
- });
15287
- });
15288
- }
15289
- // 2. <script type="module/jsx"> becomes <script type="module">
15290
- if (type === "js_module" && extension !== ".js") {
15291
- mutations.push(() => {
15292
- setHtmlNodeAttributes(scriptNode, {
15293
- type: "module",
15294
- });
15295
- });
15296
- }
15297
- }
15298
- },
15299
- a: (aNode) => {
15300
- visitHref(aNode, {
15301
- type: "a_href",
15302
- });
15303
- },
15304
- iframe: (iframeNode) => {
15305
- visitSrc(iframeNode, {
15306
- type: "iframe_src",
15307
- });
15308
- },
15309
- img: (imgNode) => {
15310
- visitSrc(imgNode, {
15311
- type: "img_src",
15312
- });
15313
- visitSrcset(imgNode, {
15314
- type: "img_srcset",
15315
- });
15316
- },
15317
- source: (sourceNode) => {
15318
- visitSrc(sourceNode, {
15319
- type: "source_src",
15320
- });
15321
- visitSrcset(sourceNode, {
15322
- type: "source_srcset",
15323
- });
15324
- },
15325
- // svg <image> tag
15326
- image: (imageNode) => {
15327
- visitHref(imageNode, {
15328
- type: "image_href",
15329
- });
15330
- },
15331
- use: (useNode) => {
15332
- visitHref(useNode, {
15333
- type: "use_href",
15334
- });
15335
- },
15336
- });
15337
- finalizeCallbacks.forEach((finalizeCallback) => {
15338
- finalizeCallback();
15339
- });
15074
+ const urlToRessource = (urlString) => {
15075
+ const scheme = urlToScheme(urlString);
15340
15076
 
15341
- if (actions.length > 0) {
15342
- await Promise.all(actions.map((action) => action()));
15077
+ if (scheme === "file") {
15078
+ return urlString.slice("file://".length)
15343
15079
  }
15344
- if (mutations.length === 0) {
15345
- return null;
15080
+
15081
+ if (scheme === "https" || scheme === "http") {
15082
+ // remove origin
15083
+ const afterProtocol = urlString.slice(scheme.length + "://".length);
15084
+ const pathnameSlashIndex = afterProtocol.indexOf("/", "://".length);
15085
+ return afterProtocol.slice(pathnameSlashIndex)
15346
15086
  }
15347
- mutations.forEach((mutation) => mutation());
15348
- return stringifyHtmlAst(htmlAst);
15087
+
15088
+ return urlString.slice(scheme.length + 1)
15349
15089
  };
15350
15090
 
15351
- const crossOriginCompatibleTagNames = ["script", "link", "img", "source"];
15352
- const integrityCompatibleTagNames = ["script", "link", "img", "source"];
15353
- const readFetchMetas = (node) => {
15354
- const meta = {};
15355
- if (crossOriginCompatibleTagNames.includes(node.nodeName)) {
15356
- const crossorigin = getHtmlNodeAttribute(node, "crossorigin") !== undefined;
15357
- meta.crossorigin = crossorigin;
15358
- }
15359
- if (integrityCompatibleTagNames.includes(node.nodeName)) {
15360
- const integrity = getHtmlNodeAttribute(node, "integrity");
15361
- meta.integrity = integrity;
15362
- }
15363
- return meta;
15091
+ const ressourceToPathname = (ressource) => {
15092
+ const searchSeparatorIndex = ressource.indexOf("?");
15093
+ return searchSeparatorIndex === -1
15094
+ ? ressource
15095
+ : ressource.slice(0, searchSeparatorIndex)
15364
15096
  };
15365
15097
 
15366
- const decideLinkExpectedType = (linkReference, htmlUrlInfo) => {
15367
- const rel = getHtmlNodeAttribute(linkReference.astInfo.node, "rel");
15368
- if (rel === "webmanifest") {
15369
- return "webmanifest";
15098
+ const urlToOrigin = (urlString) => {
15099
+ const scheme = urlToScheme(urlString);
15100
+
15101
+ if (scheme === "file") {
15102
+ return "file://"
15370
15103
  }
15371
- if (rel === "modulepreload") {
15372
- return "js_module";
15104
+
15105
+ if (scheme === "http" || scheme === "https") {
15106
+ const secondProtocolSlashIndex = scheme.length + "://".length;
15107
+ const pathnameSlashIndex = urlString.indexOf("/", secondProtocolSlashIndex);
15108
+
15109
+ if (pathnameSlashIndex === -1) return urlString
15110
+ return urlString.slice(0, pathnameSlashIndex)
15373
15111
  }
15374
- if (rel === "stylesheet") {
15375
- return "css";
15112
+
15113
+ return urlString.slice(0, scheme.length + 1)
15114
+ };
15115
+
15116
+ const pathnameToParentPathname = (pathname) => {
15117
+ const slashLastIndex = pathname.lastIndexOf("/");
15118
+ if (slashLastIndex === -1) {
15119
+ return "/"
15376
15120
  }
15377
- if (rel === "preload") {
15378
- // https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload#what_types_of_content_can_be_preloaded
15379
- const as = getHtmlNodeAttribute(linkReference.astInfo.node, "as");
15380
- if (as === "document") {
15381
- return "html";
15121
+
15122
+ return pathname.slice(0, slashLastIndex + 1)
15123
+ };
15124
+
15125
+ // could be useful: https://url.spec.whatwg.org/#url-miscellaneous
15126
+
15127
+
15128
+ const resolveUrl = (specifier, baseUrl) => {
15129
+ if (baseUrl) {
15130
+ if (typeof baseUrl !== "string") {
15131
+ throw new TypeError(writeBaseUrlMustBeAString({ baseUrl, specifier }))
15382
15132
  }
15383
- if (as === "style") {
15384
- return "css";
15385
- }
15386
- if (as === "script") {
15387
- for (const referenceToOther of htmlUrlInfo.referenceToOthersSet) {
15388
- if (referenceToOther.url !== linkReference.url) {
15389
- continue;
15390
- }
15391
- if (referenceToOther.type !== "script") {
15392
- continue;
15393
- }
15394
- return referenceToOther.expectedType;
15395
- }
15396
- return undefined;
15133
+ if (!hasScheme(baseUrl)) {
15134
+ throw new Error(writeBaseUrlMustBeAbsolute({ baseUrl, specifier }))
15397
15135
  }
15398
15136
  }
15399
- return undefined;
15400
- };
15401
15137
 
15402
- // const applyWebUrlResolution = (url, baseUrl) => {
15403
- // if (url[0] === "/") {
15404
- // return new URL(url.slice(1), baseUrl).href;
15405
- // }
15406
- // return new URL(url, baseUrl).href;
15407
- // };
15138
+ if (hasScheme(specifier)) {
15139
+ return specifier
15140
+ }
15408
15141
 
15409
- // css: parseAndTransformCssUrls,
15142
+ if (!baseUrl) {
15143
+ throw new Error(writeBaseUrlRequired({ baseUrl, specifier }))
15144
+ }
15410
15145
 
15411
- const jsenvPluginWebmanifestReferenceAnalysis = () => {
15412
- return {
15413
- name: "jsenv:webmanifest_reference_analysis",
15414
- appliesDuring: "*",
15415
- transformUrlContent: {
15416
- webmanifest: parseAndTransformWebmanifestUrls,
15417
- },
15418
- };
15419
- };
15146
+ // scheme relative
15147
+ if (specifier.slice(0, 2) === "//") {
15148
+ return `${urlToScheme(baseUrl)}:${specifier}`
15149
+ }
15420
15150
 
15421
- const parseAndTransformWebmanifestUrls = async (urlInfo) => {
15422
- const content = urlInfo.content;
15423
- const manifest = JSON.parse(content);
15424
- const actions = [];
15425
- const { icons = [] } = manifest;
15426
- icons.forEach((icon) => {
15427
- const iconReference = urlInfo.dependencies.found({
15428
- type: "webmanifest_icon_src",
15429
- specifier: icon.src,
15430
- });
15431
- actions.push(async () => {
15432
- await iconReference.readGeneratedSpecifier();
15433
- icon.src = iconReference.generatedSpecifier;
15434
- });
15435
- });
15151
+ // origin relative
15152
+ if (specifier[0] === "/") {
15153
+ return `${urlToOrigin(baseUrl)}${specifier}`
15154
+ }
15436
15155
 
15437
- if (actions.length === 0) {
15438
- return null;
15156
+ const baseOrigin = urlToOrigin(baseUrl);
15157
+ const basePathname = urlToPathname(baseUrl);
15158
+
15159
+ if (specifier === ".") {
15160
+ const baseDirectoryPathname = pathnameToParentPathname(basePathname);
15161
+ return `${baseOrigin}${baseDirectoryPathname}`
15439
15162
  }
15440
- await Promise.all(actions.map((action) => action()));
15441
- return JSON.stringify(manifest, null, " ");
15442
- };
15443
15163
 
15444
- /*
15445
- * https://github.com/parcel-bundler/parcel/blob/v2/packages/transformers/css/src/CSSTransformer.js
15446
- */
15164
+ // pathname relative inside
15165
+ if (specifier.slice(0, 2) === "./") {
15166
+ const baseDirectoryPathname = pathnameToParentPathname(basePathname);
15167
+ return `${baseOrigin}${baseDirectoryPathname}${specifier.slice(2)}`
15168
+ }
15447
15169
 
15170
+ // pathname relative outside
15171
+ if (specifier.slice(0, 3) === "../") {
15172
+ let unresolvedPathname = specifier;
15173
+ const importerFolders = basePathname.split("/");
15174
+ importerFolders.pop();
15448
15175
 
15449
- const jsenvPluginCssReferenceAnalysis = () => {
15450
- return {
15451
- name: "jsenv:css_reference_analysis",
15452
- appliesDuring: "*",
15453
- transformUrlContent: {
15454
- css: parseAndTransformCssUrls,
15455
- },
15456
- };
15457
- };
15176
+ while (unresolvedPathname.slice(0, 3) === "../") {
15177
+ unresolvedPathname = unresolvedPathname.slice(3);
15178
+ // when there is no folder left to resolved
15179
+ // we just ignore '../'
15180
+ if (importerFolders.length) {
15181
+ importerFolders.pop();
15182
+ }
15183
+ }
15458
15184
 
15459
- const parseAndTransformCssUrls = async (urlInfo) => {
15460
- const cssUrls = await parseCssUrls({
15461
- css: urlInfo.content,
15462
- url: urlInfo.originalUrl,
15463
- });
15464
- const actions = [];
15465
- const magicSource = createMagicSource(urlInfo.content);
15466
- for (const cssUrl of cssUrls) {
15467
- const reference = urlInfo.dependencies.found({
15468
- type: cssUrl.type,
15469
- specifier: cssUrl.specifier,
15470
- specifierStart: cssUrl.start,
15471
- specifierEnd: cssUrl.end,
15472
- specifierLine: cssUrl.line,
15473
- specifierColumn: cssUrl.column,
15474
- });
15475
- actions.push(async () => {
15476
- await reference.readGeneratedSpecifier();
15477
- const replacement = reference.generatedSpecifier;
15478
- magicSource.replace({
15479
- start: cssUrl.start,
15480
- end: cssUrl.end,
15481
- replacement,
15482
- });
15483
- });
15185
+ const resolvedPathname = `${importerFolders.join(
15186
+ "/",
15187
+ )}/${unresolvedPathname}`;
15188
+ return `${baseOrigin}${resolvedPathname}`
15484
15189
  }
15485
- if (actions.length > 0) {
15486
- await Promise.all(actions.map((action) => action()));
15190
+
15191
+ // bare
15192
+ if (basePathname === "") {
15193
+ return `${baseOrigin}/${specifier}`
15487
15194
  }
15488
- return magicSource.toContentAndSourcemap();
15195
+ if (basePathname[basePathname.length] === "/") {
15196
+ return `${baseOrigin}${basePathname}${specifier}`
15197
+ }
15198
+ return `${baseOrigin}${pathnameToParentPathname(basePathname)}${specifier}`
15489
15199
  };
15490
15200
 
15491
- const jsenvPluginJsReferenceAnalysis = ({ inlineContent }) => {
15492
- return [
15493
- {
15494
- name: "jsenv:js_reference_analysis",
15495
- appliesDuring: "*",
15496
- transformUrlContent: {
15497
- js_classic: (urlInfo) =>
15498
- parseAndTransformJsReferences(urlInfo, {
15499
- inlineContent,
15500
- canUseTemplateLiterals:
15501
- urlInfo.context.isSupportedOnCurrentClients("template_literals"),
15502
- }),
15503
- js_module: (urlInfo) =>
15504
- parseAndTransformJsReferences(urlInfo, {
15505
- inlineContent,
15506
- canUseTemplateLiterals:
15507
- urlInfo.context.isSupportedOnCurrentClients("template_literals"),
15508
- }),
15509
- },
15510
- },
15511
- ];
15201
+ const writeBaseUrlMustBeAString = ({
15202
+ baseUrl,
15203
+ specifier,
15204
+ }) => `baseUrl must be a string.
15205
+ --- base url ---
15206
+ ${baseUrl}
15207
+ --- specifier ---
15208
+ ${specifier}`;
15209
+
15210
+ const writeBaseUrlMustBeAbsolute = ({
15211
+ baseUrl,
15212
+ specifier,
15213
+ }) => `baseUrl must be absolute.
15214
+ --- base url ---
15215
+ ${baseUrl}
15216
+ --- specifier ---
15217
+ ${specifier}`;
15218
+
15219
+ const writeBaseUrlRequired = ({
15220
+ baseUrl,
15221
+ specifier,
15222
+ }) => `baseUrl required to resolve relative specifier.
15223
+ --- base url ---
15224
+ ${baseUrl}
15225
+ --- specifier ---
15226
+ ${specifier}`;
15227
+
15228
+ const tryUrlResolution = (string, url) => {
15229
+ const result = resolveUrl(string, url);
15230
+ return hasScheme(result) ? result : null
15512
15231
  };
15513
15232
 
15514
- const parseAndTransformJsReferences = async (
15515
- urlInfo,
15516
- { inlineContent, canUseTemplateLiterals },
15517
- ) => {
15518
- const magicSource = createMagicSource(urlInfo.content);
15519
- const parallelActions = [];
15520
- const sequentialActions = [];
15521
- const isNodeJs =
15522
- Object.keys(urlInfo.context.runtimeCompat).toString() === "node";
15233
+ const resolveSpecifier = (specifier, importer) => {
15234
+ if (
15235
+ specifier === "." ||
15236
+ specifier[0] === "/" ||
15237
+ specifier.startsWith("./") ||
15238
+ specifier.startsWith("../")
15239
+ ) {
15240
+ return resolveUrl(specifier, importer)
15241
+ }
15523
15242
 
15524
- const onInlineReference = (inlineReferenceInfo) => {
15525
- const inlineUrl = getUrlForContentInsideJs(inlineReferenceInfo, {
15526
- url: urlInfo.url,
15527
- });
15528
- let { quote } = inlineReferenceInfo;
15529
- if (quote === "`" && !canUseTemplateLiterals) {
15530
- // if quote is "`" and template literals are not supported
15531
- // we'll use a regular string (single or double quote)
15532
- // when rendering the string
15533
- quote = JS_QUOTES.pickBest(inlineReferenceInfo.content);
15534
- }
15535
- const inlineReference = urlInfo.dependencies.foundInline({
15536
- type: "js_inline_content",
15537
- subtype: inlineReferenceInfo.type, // "new_blob_first_arg", "new_inline_content_first_arg", "json_parse_first_arg"
15538
- isOriginalPosition: urlInfo.content === urlInfo.originalContent,
15539
- specifierLine: inlineReferenceInfo.line,
15540
- specifierColumn: inlineReferenceInfo.column,
15541
- specifier: inlineUrl,
15542
- contentType: inlineReferenceInfo.contentType,
15543
- content: inlineReferenceInfo.content,
15544
- });
15545
- const inlineUrlInfo = inlineReference.urlInfo;
15546
- inlineUrlInfo.jsQuote = quote;
15547
- inlineReference.escape = (value) => {
15548
- return JS_QUOTES.escapeSpecialChars(value.slice(1, -1), { quote });
15549
- };
15550
-
15551
- sequentialActions.push(async () => {
15552
- await inlineUrlInfo.cook();
15553
- const replacement = JS_QUOTES.escapeSpecialChars(inlineUrlInfo.content, {
15554
- quote,
15555
- });
15556
- magicSource.replace({
15557
- start: inlineReferenceInfo.start,
15558
- end: inlineReferenceInfo.end,
15559
- replacement,
15560
- });
15561
- });
15562
- };
15563
- const onExternalReference = (externalReferenceInfo) => {
15564
- if (
15565
- externalReferenceInfo.subtype === "import_static" ||
15566
- externalReferenceInfo.subtype === "import_dynamic"
15567
- ) {
15568
- urlInfo.data.usesImport = true;
15569
- }
15570
- if (
15571
- isNodeJs &&
15572
- externalReferenceInfo.type === "js_url" &&
15573
- externalReferenceInfo.expectedSubtype === "worker" &&
15574
- externalReferenceInfo.expectedType === "js_classic" &&
15575
- // TODO: it's true also if closest package.json
15576
- // is type: module
15577
- urlToExtension$1(
15578
- new URL(externalReferenceInfo.specifier, urlInfo.url).href,
15579
- ) === ".mjs"
15580
- ) {
15581
- externalReferenceInfo.expectedType = "js_module";
15582
- }
15583
- const reference = urlInfo.dependencies.found({
15584
- type: externalReferenceInfo.type,
15585
- subtype: externalReferenceInfo.subtype,
15586
- expectedType: externalReferenceInfo.expectedType,
15587
- expectedSubtype: externalReferenceInfo.expectedSubtype || urlInfo.subtype,
15588
- specifier: externalReferenceInfo.specifier,
15589
- specifierStart: externalReferenceInfo.start,
15590
- specifierEnd: externalReferenceInfo.end,
15591
- specifierLine: externalReferenceInfo.line,
15592
- specifierColumn: externalReferenceInfo.column,
15593
- data: externalReferenceInfo.data,
15594
- baseUrl: {
15595
- "StringLiteral": externalReferenceInfo.baseUrl,
15596
- "window.location": urlInfo.url,
15597
- "window.origin": urlInfo.context.rootDirectoryUrl,
15598
- "import.meta.url": urlInfo.url,
15599
- "context.meta.url": urlInfo.url,
15600
- "document.currentScript.src": urlInfo.url,
15601
- }[externalReferenceInfo.baseUrlType],
15602
- importAttributes: externalReferenceInfo.importAttributes,
15603
- astInfo: externalReferenceInfo.astInfo,
15604
- });
15605
- parallelActions.push(async () => {
15606
- await reference.readGeneratedSpecifier();
15607
- const replacement = reference.generatedSpecifier;
15608
- magicSource.replace({
15609
- start: externalReferenceInfo.start,
15610
- end: externalReferenceInfo.end,
15611
- replacement,
15612
- });
15613
- if (reference.mutation) {
15614
- reference.mutation(magicSource, urlInfo);
15615
- }
15616
- });
15617
- };
15618
- const jsReferenceInfos = parseJsUrls({
15619
- js: urlInfo.content,
15620
- url: urlInfo.originalUrl,
15621
- ast: urlInfo.contentAst,
15622
- isJsModule: urlInfo.type === "js_module",
15623
- isWebWorker: isWebWorkerUrlInfo(urlInfo),
15624
- inlineContent,
15625
- isNodeJs,
15626
- });
15627
- for (const jsReferenceInfo of jsReferenceInfos) {
15628
- if (jsReferenceInfo.isInline) {
15629
- onInlineReference(jsReferenceInfo);
15630
- } else {
15631
- onExternalReference(jsReferenceInfo);
15632
- }
15633
- }
15634
- if (parallelActions.length > 0) {
15635
- await Promise.all(parallelActions.map((action) => action()));
15636
- }
15637
- if (sequentialActions.length > 0) {
15638
- await sequentialActions.reduce(async (previous, action) => {
15639
- await previous;
15640
- await action();
15641
- }, Promise.resolve());
15642
- }
15643
-
15644
- const { content, sourcemap } = magicSource.toContentAndSourcemap();
15645
- return { content, sourcemap };
15646
- };
15647
-
15648
- const jsenvPluginReferenceAnalysis = ({
15649
- inlineContent = true,
15650
- inlineConvertedScript = false,
15651
- fetchInlineUrls = true,
15652
- }) => {
15653
- return [
15654
- jsenvPluginDirectoryReferenceAnalysis(),
15655
- jsenvPluginHtmlReferenceAnalysis({
15656
- inlineContent,
15657
- inlineConvertedScript,
15658
- }),
15659
- jsenvPluginWebmanifestReferenceAnalysis(),
15660
- jsenvPluginCssReferenceAnalysis(),
15661
- jsenvPluginJsReferenceAnalysis({
15662
- inlineContent,
15663
- }),
15664
- ...(inlineContent ? [jsenvPluginDataUrlsAnalysis()] : []),
15665
- ...(inlineContent && fetchInlineUrls
15666
- ? [jsenvPluginInlineContentFetcher()]
15667
- : []),
15668
- jsenvPluginReferenceExpectedTypes(),
15669
- ];
15670
- };
15671
-
15672
- const jsenvPluginInlineContentFetcher = () => {
15673
- return {
15674
- name: "jsenv:inline_content_fetcher",
15675
- appliesDuring: "*",
15676
- fetchUrlContent: async (urlInfo) => {
15677
- if (!urlInfo.isInline) {
15678
- return null;
15679
- }
15680
- // - we must use last reference because
15681
- // when updating the file, first reference is the previous version
15682
- // - we cannot use urlInfo.lastReference because it can be the reference created by "http_request"
15683
- let lastInlineReference;
15684
- for (const reference of urlInfo.referenceFromOthersSet) {
15685
- if (reference.isInline) {
15686
- lastInlineReference = reference;
15687
- }
15688
- }
15689
- const { prev } = lastInlineReference;
15690
- if (prev && !prev.isInline) {
15691
- // got inlined, cook original url
15692
- if (lastInlineReference.content === undefined) {
15693
- const originalUrlInfo = prev.urlInfo;
15694
- await originalUrlInfo.cook();
15695
- lastInlineReference.content = originalUrlInfo.content;
15696
- lastInlineReference.contentType = originalUrlInfo.contentType;
15697
- }
15698
- }
15699
- return {
15700
- originalContent: urlInfo.originalContent,
15701
- content: lastInlineReference.content,
15702
- contentType: lastInlineReference.contentType,
15703
- };
15704
- },
15705
- };
15706
- };
15707
-
15708
- // duplicated from @jsenv/log to avoid the dependency
15709
- const createDetailedMessage = (message, details = {}) => {
15710
- let string = `${message}`;
15711
-
15712
- Object.keys(details).forEach((key) => {
15713
- const value = details[key];
15714
- string += `
15715
- --- ${key} ---
15716
- ${
15717
- Array.isArray(value)
15718
- ? value.join(`
15719
- `)
15720
- : value
15721
- }`;
15722
- });
15723
-
15724
- return string
15725
- };
15726
-
15727
- const assertImportMap = (value) => {
15728
- if (value === null) {
15729
- throw new TypeError(`an importMap must be an object, got null`)
15730
- }
15731
-
15732
- const type = typeof value;
15733
- if (type !== "object") {
15734
- throw new TypeError(`an importMap must be an object, received ${value}`)
15735
- }
15736
-
15737
- if (Array.isArray(value)) {
15738
- throw new TypeError(
15739
- `an importMap must be an object, received array ${value}`,
15740
- )
15741
- }
15742
- };
15743
-
15744
- const hasScheme = (string) => {
15745
- return /^[a-zA-Z]{2,}:/.test(string)
15746
- };
15747
-
15748
- const urlToScheme = (urlString) => {
15749
- const colonIndex = urlString.indexOf(":");
15750
- if (colonIndex === -1) return ""
15751
- return urlString.slice(0, colonIndex)
15752
- };
15753
-
15754
- const urlToPathname = (urlString) => {
15755
- return ressourceToPathname(urlToRessource(urlString))
15756
- };
15757
-
15758
- const urlToRessource = (urlString) => {
15759
- const scheme = urlToScheme(urlString);
15760
-
15761
- if (scheme === "file") {
15762
- return urlString.slice("file://".length)
15763
- }
15764
-
15765
- if (scheme === "https" || scheme === "http") {
15766
- // remove origin
15767
- const afterProtocol = urlString.slice(scheme.length + "://".length);
15768
- const pathnameSlashIndex = afterProtocol.indexOf("/", "://".length);
15769
- return afterProtocol.slice(pathnameSlashIndex)
15770
- }
15771
-
15772
- return urlString.slice(scheme.length + 1)
15773
- };
15774
-
15775
- const ressourceToPathname = (ressource) => {
15776
- const searchSeparatorIndex = ressource.indexOf("?");
15777
- return searchSeparatorIndex === -1
15778
- ? ressource
15779
- : ressource.slice(0, searchSeparatorIndex)
15780
- };
15781
-
15782
- const urlToOrigin = (urlString) => {
15783
- const scheme = urlToScheme(urlString);
15784
-
15785
- if (scheme === "file") {
15786
- return "file://"
15787
- }
15788
-
15789
- if (scheme === "http" || scheme === "https") {
15790
- const secondProtocolSlashIndex = scheme.length + "://".length;
15791
- const pathnameSlashIndex = urlString.indexOf("/", secondProtocolSlashIndex);
15792
-
15793
- if (pathnameSlashIndex === -1) return urlString
15794
- return urlString.slice(0, pathnameSlashIndex)
15795
- }
15796
-
15797
- return urlString.slice(0, scheme.length + 1)
15798
- };
15799
-
15800
- const pathnameToParentPathname = (pathname) => {
15801
- const slashLastIndex = pathname.lastIndexOf("/");
15802
- if (slashLastIndex === -1) {
15803
- return "/"
15804
- }
15805
-
15806
- return pathname.slice(0, slashLastIndex + 1)
15807
- };
15808
-
15809
- // could be useful: https://url.spec.whatwg.org/#url-miscellaneous
15810
-
15811
-
15812
- const resolveUrl = (specifier, baseUrl) => {
15813
- if (baseUrl) {
15814
- if (typeof baseUrl !== "string") {
15815
- throw new TypeError(writeBaseUrlMustBeAString({ baseUrl, specifier }))
15816
- }
15817
- if (!hasScheme(baseUrl)) {
15818
- throw new Error(writeBaseUrlMustBeAbsolute({ baseUrl, specifier }))
15819
- }
15820
- }
15821
-
15822
- if (hasScheme(specifier)) {
15823
- return specifier
15824
- }
15825
-
15826
- if (!baseUrl) {
15827
- throw new Error(writeBaseUrlRequired({ baseUrl, specifier }))
15828
- }
15829
-
15830
- // scheme relative
15831
- if (specifier.slice(0, 2) === "//") {
15832
- return `${urlToScheme(baseUrl)}:${specifier}`
15833
- }
15834
-
15835
- // origin relative
15836
- if (specifier[0] === "/") {
15837
- return `${urlToOrigin(baseUrl)}${specifier}`
15838
- }
15839
-
15840
- const baseOrigin = urlToOrigin(baseUrl);
15841
- const basePathname = urlToPathname(baseUrl);
15842
-
15843
- if (specifier === ".") {
15844
- const baseDirectoryPathname = pathnameToParentPathname(basePathname);
15845
- return `${baseOrigin}${baseDirectoryPathname}`
15846
- }
15847
-
15848
- // pathname relative inside
15849
- if (specifier.slice(0, 2) === "./") {
15850
- const baseDirectoryPathname = pathnameToParentPathname(basePathname);
15851
- return `${baseOrigin}${baseDirectoryPathname}${specifier.slice(2)}`
15852
- }
15853
-
15854
- // pathname relative outside
15855
- if (specifier.slice(0, 3) === "../") {
15856
- let unresolvedPathname = specifier;
15857
- const importerFolders = basePathname.split("/");
15858
- importerFolders.pop();
15859
-
15860
- while (unresolvedPathname.slice(0, 3) === "../") {
15861
- unresolvedPathname = unresolvedPathname.slice(3);
15862
- // when there is no folder left to resolved
15863
- // we just ignore '../'
15864
- if (importerFolders.length) {
15865
- importerFolders.pop();
15866
- }
15867
- }
15868
-
15869
- const resolvedPathname = `${importerFolders.join(
15870
- "/",
15871
- )}/${unresolvedPathname}`;
15872
- return `${baseOrigin}${resolvedPathname}`
15873
- }
15874
-
15875
- // bare
15876
- if (basePathname === "") {
15877
- return `${baseOrigin}/${specifier}`
15878
- }
15879
- if (basePathname[basePathname.length] === "/") {
15880
- return `${baseOrigin}${basePathname}${specifier}`
15881
- }
15882
- return `${baseOrigin}${pathnameToParentPathname(basePathname)}${specifier}`
15883
- };
15884
-
15885
- const writeBaseUrlMustBeAString = ({
15886
- baseUrl,
15887
- specifier,
15888
- }) => `baseUrl must be a string.
15889
- --- base url ---
15890
- ${baseUrl}
15891
- --- specifier ---
15892
- ${specifier}`;
15893
-
15894
- const writeBaseUrlMustBeAbsolute = ({
15895
- baseUrl,
15896
- specifier,
15897
- }) => `baseUrl must be absolute.
15898
- --- base url ---
15899
- ${baseUrl}
15900
- --- specifier ---
15901
- ${specifier}`;
15902
-
15903
- const writeBaseUrlRequired = ({
15904
- baseUrl,
15905
- specifier,
15906
- }) => `baseUrl required to resolve relative specifier.
15907
- --- base url ---
15908
- ${baseUrl}
15909
- --- specifier ---
15910
- ${specifier}`;
15911
-
15912
- const tryUrlResolution = (string, url) => {
15913
- const result = resolveUrl(string, url);
15914
- return hasScheme(result) ? result : null
15915
- };
15916
-
15917
- const resolveSpecifier = (specifier, importer) => {
15918
- if (
15919
- specifier === "." ||
15920
- specifier[0] === "/" ||
15921
- specifier.startsWith("./") ||
15922
- specifier.startsWith("../")
15923
- ) {
15924
- return resolveUrl(specifier, importer)
15925
- }
15926
-
15927
- if (hasScheme(specifier)) {
15928
- return specifier
15929
- }
15243
+ if (hasScheme(specifier)) {
15244
+ return specifier
15245
+ }
15930
15246
 
15931
15247
  return null
15932
15248
  };
@@ -16048,561 +15364,1243 @@ const applyMappings = (
16048
15364
  const afterSpecifier = specifierNormalized.slice(
16049
15365
  specifierCandidate.length,
16050
15366
  );
16051
- const addressFinal = tryUrlResolution(afterSpecifier, address);
16052
- onImportMapping({
16053
- scope,
16054
- from: specifierCandidate,
16055
- to: address,
16056
- before: specifierNormalized,
16057
- after: addressFinal,
16058
- });
16059
- return addressFinal
15367
+ const addressFinal = tryUrlResolution(afterSpecifier, address);
15368
+ onImportMapping({
15369
+ scope,
15370
+ from: specifierCandidate,
15371
+ to: address,
15372
+ before: specifierNormalized,
15373
+ after: addressFinal,
15374
+ });
15375
+ return addressFinal
15376
+ }
15377
+ }
15378
+
15379
+ return null
15380
+ };
15381
+
15382
+ const specifierIsPrefixOf = (specifierHref, href) => {
15383
+ return (
15384
+ specifierHref[specifierHref.length - 1] === "/" &&
15385
+ href.startsWith(specifierHref)
15386
+ )
15387
+ };
15388
+
15389
+ // https://github.com/systemjs/systemjs/blob/89391f92dfeac33919b0223bbf834a1f4eea5750/src/common.js#L136
15390
+
15391
+ const composeTwoImportMaps = (leftImportMap, rightImportMap) => {
15392
+ assertImportMap(leftImportMap);
15393
+ assertImportMap(rightImportMap);
15394
+
15395
+ const importMap = {};
15396
+
15397
+ const leftImports = leftImportMap.imports;
15398
+ const rightImports = rightImportMap.imports;
15399
+ const leftHasImports = Boolean(leftImports);
15400
+ const rightHasImports = Boolean(rightImports);
15401
+ if (leftHasImports && rightHasImports) {
15402
+ importMap.imports = composeTwoMappings(leftImports, rightImports);
15403
+ } else if (leftHasImports) {
15404
+ importMap.imports = { ...leftImports };
15405
+ } else if (rightHasImports) {
15406
+ importMap.imports = { ...rightImports };
15407
+ }
15408
+
15409
+ const leftScopes = leftImportMap.scopes;
15410
+ const rightScopes = rightImportMap.scopes;
15411
+ const leftHasScopes = Boolean(leftScopes);
15412
+ const rightHasScopes = Boolean(rightScopes);
15413
+ if (leftHasScopes && rightHasScopes) {
15414
+ importMap.scopes = composeTwoScopes(
15415
+ leftScopes,
15416
+ rightScopes,
15417
+ importMap.imports || {},
15418
+ );
15419
+ } else if (leftHasScopes) {
15420
+ importMap.scopes = { ...leftScopes };
15421
+ } else if (rightHasScopes) {
15422
+ importMap.scopes = { ...rightScopes };
15423
+ }
15424
+
15425
+ return importMap
15426
+ };
15427
+
15428
+ const composeTwoMappings = (leftMappings, rightMappings) => {
15429
+ const mappings = {};
15430
+
15431
+ Object.keys(leftMappings).forEach((leftSpecifier) => {
15432
+ if (objectHasKey(rightMappings, leftSpecifier)) {
15433
+ // will be overidden
15434
+ return
15435
+ }
15436
+ const leftAddress = leftMappings[leftSpecifier];
15437
+ const rightSpecifier = Object.keys(rightMappings).find((rightSpecifier) => {
15438
+ return compareAddressAndSpecifier(leftAddress, rightSpecifier)
15439
+ });
15440
+ mappings[leftSpecifier] = rightSpecifier
15441
+ ? rightMappings[rightSpecifier]
15442
+ : leftAddress;
15443
+ });
15444
+
15445
+ Object.keys(rightMappings).forEach((rightSpecifier) => {
15446
+ mappings[rightSpecifier] = rightMappings[rightSpecifier];
15447
+ });
15448
+
15449
+ return mappings
15450
+ };
15451
+
15452
+ const objectHasKey = (object, key) =>
15453
+ Object.prototype.hasOwnProperty.call(object, key);
15454
+
15455
+ const compareAddressAndSpecifier = (address, specifier) => {
15456
+ const addressUrl = resolveUrl(address, "file:///");
15457
+ const specifierUrl = resolveUrl(specifier, "file:///");
15458
+ return addressUrl === specifierUrl
15459
+ };
15460
+
15461
+ const composeTwoScopes = (leftScopes, rightScopes, imports) => {
15462
+ const scopes = {};
15463
+
15464
+ Object.keys(leftScopes).forEach((leftScopeKey) => {
15465
+ if (objectHasKey(rightScopes, leftScopeKey)) {
15466
+ // will be merged
15467
+ scopes[leftScopeKey] = leftScopes[leftScopeKey];
15468
+ return
15469
+ }
15470
+ const topLevelSpecifier = Object.keys(imports).find(
15471
+ (topLevelSpecifierCandidate) => {
15472
+ return compareAddressAndSpecifier(
15473
+ leftScopeKey,
15474
+ topLevelSpecifierCandidate,
15475
+ )
15476
+ },
15477
+ );
15478
+ if (topLevelSpecifier) {
15479
+ scopes[imports[topLevelSpecifier]] = leftScopes[leftScopeKey];
15480
+ } else {
15481
+ scopes[leftScopeKey] = leftScopes[leftScopeKey];
15482
+ }
15483
+ });
15484
+
15485
+ Object.keys(rightScopes).forEach((rightScopeKey) => {
15486
+ if (objectHasKey(scopes, rightScopeKey)) {
15487
+ scopes[rightScopeKey] = composeTwoMappings(
15488
+ scopes[rightScopeKey],
15489
+ rightScopes[rightScopeKey],
15490
+ );
15491
+ } else {
15492
+ scopes[rightScopeKey] = {
15493
+ ...rightScopes[rightScopeKey],
15494
+ };
15495
+ }
15496
+ });
15497
+
15498
+ return scopes
15499
+ };
15500
+
15501
+ const sortImports = (imports) => {
15502
+ const mappingsSorted = {};
15503
+
15504
+ Object.keys(imports)
15505
+ .sort(compareLengthOrLocaleCompare)
15506
+ .forEach((name) => {
15507
+ mappingsSorted[name] = imports[name];
15508
+ });
15509
+
15510
+ return mappingsSorted
15511
+ };
15512
+
15513
+ const sortScopes = (scopes) => {
15514
+ const scopesSorted = {};
15515
+
15516
+ Object.keys(scopes)
15517
+ .sort(compareLengthOrLocaleCompare)
15518
+ .forEach((scopeSpecifier) => {
15519
+ scopesSorted[scopeSpecifier] = sortImports(scopes[scopeSpecifier]);
15520
+ });
15521
+
15522
+ return scopesSorted
15523
+ };
15524
+
15525
+ const compareLengthOrLocaleCompare = (a, b) => {
15526
+ return b.length - a.length || a.localeCompare(b)
15527
+ };
15528
+
15529
+ const normalizeImportMap = (importMap, baseUrl) => {
15530
+ assertImportMap(importMap);
15531
+
15532
+ if (!isStringOrUrl(baseUrl)) {
15533
+ throw new TypeError(formulateBaseUrlMustBeStringOrUrl({ baseUrl }))
15534
+ }
15535
+
15536
+ const { imports, scopes } = importMap;
15537
+
15538
+ return {
15539
+ imports: imports ? normalizeMappings(imports, baseUrl) : undefined,
15540
+ scopes: scopes ? normalizeScopes(scopes, baseUrl) : undefined,
15541
+ }
15542
+ };
15543
+
15544
+ const isStringOrUrl = (value) => {
15545
+ if (typeof value === "string") {
15546
+ return true
15547
+ }
15548
+
15549
+ if (typeof URL === "function" && value instanceof URL) {
15550
+ return true
15551
+ }
15552
+
15553
+ return false
15554
+ };
15555
+
15556
+ const normalizeMappings = (mappings, baseUrl) => {
15557
+ const mappingsNormalized = {};
15558
+
15559
+ Object.keys(mappings).forEach((specifier) => {
15560
+ const address = mappings[specifier];
15561
+
15562
+ if (typeof address !== "string") {
15563
+ console.warn(
15564
+ formulateAddressMustBeAString({
15565
+ address,
15566
+ specifier,
15567
+ }),
15568
+ );
15569
+ return
15570
+ }
15571
+
15572
+ const specifierResolved = resolveSpecifier(specifier, baseUrl) || specifier;
15573
+
15574
+ const addressUrl = tryUrlResolution(address, baseUrl);
15575
+ if (addressUrl === null) {
15576
+ console.warn(
15577
+ formulateAdressResolutionFailed({
15578
+ address,
15579
+ baseUrl,
15580
+ specifier,
15581
+ }),
15582
+ );
15583
+ return
15584
+ }
15585
+
15586
+ if (specifier.endsWith("/") && !addressUrl.endsWith("/")) {
15587
+ console.warn(
15588
+ formulateAddressUrlRequiresTrailingSlash({
15589
+ addressUrl,
15590
+ address,
15591
+ specifier,
15592
+ }),
15593
+ );
15594
+ return
16060
15595
  }
16061
- }
15596
+ mappingsNormalized[specifierResolved] = addressUrl;
15597
+ });
16062
15598
 
16063
- return null
15599
+ return sortImports(mappingsNormalized)
16064
15600
  };
16065
15601
 
16066
- const specifierIsPrefixOf = (specifierHref, href) => {
16067
- return (
16068
- specifierHref[specifierHref.length - 1] === "/" &&
16069
- href.startsWith(specifierHref)
16070
- )
15602
+ const normalizeScopes = (scopes, baseUrl) => {
15603
+ const scopesNormalized = {};
15604
+
15605
+ Object.keys(scopes).forEach((scopeSpecifier) => {
15606
+ const scopeMappings = scopes[scopeSpecifier];
15607
+ const scopeUrl = tryUrlResolution(scopeSpecifier, baseUrl);
15608
+ if (scopeUrl === null) {
15609
+ console.warn(
15610
+ formulateScopeResolutionFailed({
15611
+ scope: scopeSpecifier,
15612
+ baseUrl,
15613
+ }),
15614
+ );
15615
+ return
15616
+ }
15617
+ const scopeValueNormalized = normalizeMappings(scopeMappings, baseUrl);
15618
+ scopesNormalized[scopeUrl] = scopeValueNormalized;
15619
+ });
15620
+
15621
+ return sortScopes(scopesNormalized)
16071
15622
  };
16072
15623
 
16073
- // https://github.com/systemjs/systemjs/blob/89391f92dfeac33919b0223bbf834a1f4eea5750/src/common.js#L136
15624
+ const formulateBaseUrlMustBeStringOrUrl = ({
15625
+ baseUrl,
15626
+ }) => `baseUrl must be a string or an url.
15627
+ --- base url ---
15628
+ ${baseUrl}`;
16074
15629
 
16075
- const composeTwoImportMaps = (leftImportMap, rightImportMap) => {
16076
- assertImportMap(leftImportMap);
16077
- assertImportMap(rightImportMap);
15630
+ const formulateAddressMustBeAString = ({
15631
+ specifier,
15632
+ address,
15633
+ }) => `Address must be a string.
15634
+ --- address ---
15635
+ ${address}
15636
+ --- specifier ---
15637
+ ${specifier}`;
16078
15638
 
16079
- const importMap = {};
15639
+ const formulateAdressResolutionFailed = ({
15640
+ address,
15641
+ baseUrl,
15642
+ specifier,
15643
+ }) => `Address url resolution failed.
15644
+ --- address ---
15645
+ ${address}
15646
+ --- base url ---
15647
+ ${baseUrl}
15648
+ --- specifier ---
15649
+ ${specifier}`;
16080
15650
 
16081
- const leftImports = leftImportMap.imports;
16082
- const rightImports = rightImportMap.imports;
16083
- const leftHasImports = Boolean(leftImports);
16084
- const rightHasImports = Boolean(rightImports);
16085
- if (leftHasImports && rightHasImports) {
16086
- importMap.imports = composeTwoMappings(leftImports, rightImports);
16087
- } else if (leftHasImports) {
16088
- importMap.imports = { ...leftImports };
16089
- } else if (rightHasImports) {
16090
- importMap.imports = { ...rightImports };
16091
- }
15651
+ const formulateAddressUrlRequiresTrailingSlash = ({
15652
+ addressURL,
15653
+ address,
15654
+ specifier,
15655
+ }) => `Address must end with /.
15656
+ --- address url ---
15657
+ ${addressURL}
15658
+ --- address ---
15659
+ ${address}
15660
+ --- specifier ---
15661
+ ${specifier}`;
16092
15662
 
16093
- const leftScopes = leftImportMap.scopes;
16094
- const rightScopes = rightImportMap.scopes;
16095
- const leftHasScopes = Boolean(leftScopes);
16096
- const rightHasScopes = Boolean(rightScopes);
16097
- if (leftHasScopes && rightHasScopes) {
16098
- importMap.scopes = composeTwoScopes(
16099
- leftScopes,
16100
- rightScopes,
16101
- importMap.imports || {},
16102
- );
16103
- } else if (leftHasScopes) {
16104
- importMap.scopes = { ...leftScopes };
16105
- } else if (rightHasScopes) {
16106
- importMap.scopes = { ...rightScopes };
15663
+ const formulateScopeResolutionFailed = ({
15664
+ scope,
15665
+ baseUrl,
15666
+ }) => `Scope url resolution failed.
15667
+ --- scope ---
15668
+ ${scope}
15669
+ --- base url ---
15670
+ ${baseUrl}`;
15671
+
15672
+ const pathnameToExtension = (pathname) => {
15673
+ const slashLastIndex = pathname.lastIndexOf("/");
15674
+ if (slashLastIndex !== -1) {
15675
+ pathname = pathname.slice(slashLastIndex + 1);
16107
15676
  }
16108
15677
 
16109
- return importMap
15678
+ const dotLastIndex = pathname.lastIndexOf(".");
15679
+ if (dotLastIndex === -1) return ""
15680
+ // if (dotLastIndex === pathname.length - 1) return ""
15681
+ return pathname.slice(dotLastIndex)
16110
15682
  };
16111
15683
 
16112
- const composeTwoMappings = (leftMappings, rightMappings) => {
16113
- const mappings = {};
16114
-
16115
- Object.keys(leftMappings).forEach((leftSpecifier) => {
16116
- if (objectHasKey(rightMappings, leftSpecifier)) {
16117
- // will be overidden
16118
- return
16119
- }
16120
- const leftAddress = leftMappings[leftSpecifier];
16121
- const rightSpecifier = Object.keys(rightMappings).find((rightSpecifier) => {
16122
- return compareAddressAndSpecifier(leftAddress, rightSpecifier)
15684
+ const resolveImport = ({
15685
+ specifier,
15686
+ importer,
15687
+ importMap,
15688
+ defaultExtension = false,
15689
+ createBareSpecifierError,
15690
+ onImportMapping = () => {},
15691
+ }) => {
15692
+ let url;
15693
+ if (importMap) {
15694
+ url = applyImportMap({
15695
+ importMap,
15696
+ specifier,
15697
+ importer,
15698
+ createBareSpecifierError,
15699
+ onImportMapping,
16123
15700
  });
16124
- mappings[leftSpecifier] = rightSpecifier
16125
- ? rightMappings[rightSpecifier]
16126
- : leftAddress;
16127
- });
15701
+ } else {
15702
+ url = resolveUrl(specifier, importer);
15703
+ }
16128
15704
 
16129
- Object.keys(rightMappings).forEach((rightSpecifier) => {
16130
- mappings[rightSpecifier] = rightMappings[rightSpecifier];
16131
- });
15705
+ if (defaultExtension) {
15706
+ url = applyDefaultExtension({ url, importer, defaultExtension });
15707
+ }
16132
15708
 
16133
- return mappings
15709
+ return url
16134
15710
  };
16135
15711
 
16136
- const objectHasKey = (object, key) =>
16137
- Object.prototype.hasOwnProperty.call(object, key);
16138
-
16139
- const compareAddressAndSpecifier = (address, specifier) => {
16140
- const addressUrl = resolveUrl(address, "file:///");
16141
- const specifierUrl = resolveUrl(specifier, "file:///");
16142
- return addressUrl === specifierUrl
16143
- };
15712
+ const applyDefaultExtension = ({ url, importer, defaultExtension }) => {
15713
+ if (urlToPathname(url).endsWith("/")) {
15714
+ return url
15715
+ }
16144
15716
 
16145
- const composeTwoScopes = (leftScopes, rightScopes, imports) => {
16146
- const scopes = {};
15717
+ if (typeof defaultExtension === "string") {
15718
+ const extension = pathnameToExtension(url);
15719
+ if (extension === "") {
15720
+ return `${url}${defaultExtension}`
15721
+ }
15722
+ return url
15723
+ }
16147
15724
 
16148
- Object.keys(leftScopes).forEach((leftScopeKey) => {
16149
- if (objectHasKey(rightScopes, leftScopeKey)) {
16150
- // will be merged
16151
- scopes[leftScopeKey] = leftScopes[leftScopeKey];
16152
- return
15725
+ if (defaultExtension === true) {
15726
+ const extension = pathnameToExtension(url);
15727
+ if (extension === "" && importer) {
15728
+ const importerPathname = urlToPathname(importer);
15729
+ const importerExtension = pathnameToExtension(importerPathname);
15730
+ return `${url}${importerExtension}`
16153
15731
  }
16154
- const topLevelSpecifier = Object.keys(imports).find(
16155
- (topLevelSpecifierCandidate) => {
16156
- return compareAddressAndSpecifier(
16157
- leftScopeKey,
16158
- topLevelSpecifierCandidate,
16159
- )
16160
- },
16161
- );
16162
- if (topLevelSpecifier) {
16163
- scopes[imports[topLevelSpecifier]] = leftScopes[leftScopeKey];
15732
+ }
15733
+
15734
+ return url
15735
+ };
15736
+
15737
+ const jsenvPluginHtmlReferenceAnalysis = ({
15738
+ inlineContent,
15739
+ inlineConvertedScript,
15740
+ }) => {
15741
+ /*
15742
+ * About importmap found in HTML files:
15743
+ * - feeds importmap files to jsenv kitchen
15744
+ * - use importmap to resolve import (when there is one + fallback to other resolution mecanism)
15745
+ * - inline importmap with [src=""]
15746
+ *
15747
+ * A correct importmap resolution should scope importmap resolution per html file.
15748
+ * It would be doable by adding ?html_id to each js file in order to track
15749
+ * the html file importing it.
15750
+ * Considering it happens only when all the following conditions are met:
15751
+ * - 2+ html files are using an importmap
15752
+ * - the importmap used is not the same
15753
+ * - the importmap contain conflicting mappings
15754
+ * - these html files are both executed during the same scenario (dev, test, build)
15755
+ * And that it would be ugly to see ?html_id all over the place
15756
+ * -> The importmap resolution implemented here takes a shortcut and does the following:
15757
+ * - All importmap found are merged into a single one that is applied to every import specifiers
15758
+ */
15759
+
15760
+ let globalImportmap = null;
15761
+ const importmaps = {};
15762
+ const onImportmapParsed = (htmlUrl, importmap) => {
15763
+ if (importmap) {
15764
+ importmaps[htmlUrl] = normalizeImportMap(importmap, htmlUrl);
16164
15765
  } else {
16165
- scopes[leftScopeKey] = leftScopes[leftScopeKey];
15766
+ importmaps[htmlUrl] = null;
16166
15767
  }
16167
- });
15768
+ globalImportmap = Object.keys(importmaps).reduce((previous, url) => {
15769
+ const importmap = importmaps[url];
15770
+ if (!previous) {
15771
+ return importmap;
15772
+ }
15773
+ if (!importmap) {
15774
+ return previous;
15775
+ }
15776
+ return composeTwoImportMaps(previous, importmap);
15777
+ }, null);
15778
+ };
15779
+
15780
+ return {
15781
+ name: "jsenv:html_reference_analysis",
15782
+ appliesDuring: "*",
15783
+ resolveReference: {
15784
+ js_import: (reference) => {
15785
+ if (!globalImportmap) {
15786
+ return null;
15787
+ }
15788
+ try {
15789
+ let fromMapping = false;
15790
+ const result = resolveImport({
15791
+ specifier: reference.specifier,
15792
+ importer: reference.ownerUrlInfo.url,
15793
+ importMap: globalImportmap,
15794
+ onImportMapping: () => {
15795
+ fromMapping = true;
15796
+ },
15797
+ });
15798
+ if (fromMapping) {
15799
+ return result;
15800
+ }
15801
+ return null;
15802
+ } catch (e) {
15803
+ if (e.message.includes("bare specifier")) {
15804
+ // in theory we should throw to be compliant with web behaviour
15805
+ // but for now it's simpler to return null
15806
+ // and let a chance to other plugins to handle the bare specifier
15807
+ // (node esm resolution)
15808
+ // and we want importmap to be prio over node esm so we cannot put this plugin after
15809
+ return null;
15810
+ }
15811
+ throw e;
15812
+ }
15813
+ },
15814
+ },
15815
+ transformUrlContent: {
15816
+ html: async (urlInfo) => {
15817
+ let importmapFound = false;
15818
+ let importmapParsedCallbackSet = new Set();
15819
+ const onImportmapReady = (importmap) => {
15820
+ onImportmapParsed(urlInfo.url, importmap);
15821
+ importmapParsedCallbackSet.forEach((callback) => {
15822
+ callback();
15823
+ });
15824
+ importmapParsedCallbackSet.clear();
15825
+ };
16168
15826
 
16169
- Object.keys(rightScopes).forEach((rightScopeKey) => {
16170
- if (objectHasKey(scopes, rightScopeKey)) {
16171
- scopes[rightScopeKey] = composeTwoMappings(
16172
- scopes[rightScopeKey],
16173
- rightScopes[rightScopeKey],
16174
- );
16175
- } else {
16176
- scopes[rightScopeKey] = {
16177
- ...rightScopes[rightScopeKey],
16178
- };
16179
- }
16180
- });
15827
+ const content = urlInfo.content;
15828
+ const htmlAst = parseHtmlString(content);
16181
15829
 
16182
- return scopes
16183
- };
15830
+ const mutations = [];
15831
+ const actions = [];
15832
+ const finalizeCallbacks = [];
16184
15833
 
16185
- const sortImports = (imports) => {
16186
- const mappingsSorted = {};
15834
+ const createExternalReference = (
15835
+ node,
15836
+ attributeName,
15837
+ attributeValue,
15838
+ { type, subtype, expectedType, ...rest },
15839
+ ) => {
15840
+ let position;
15841
+ if (getHtmlNodeAttribute(node, "jsenv-cooked-by")) {
15842
+ // when generated from inline content,
15843
+ // line, column is not "src" nor "inlined-from-src" but "original-position"
15844
+ position = getHtmlNodePosition(node);
15845
+ } else {
15846
+ position = getHtmlNodeAttributePosition(node, attributeName);
15847
+ }
15848
+ const {
15849
+ line,
15850
+ column,
15851
+ // originalLine, originalColumn
15852
+ } = position;
15853
+ const debug = getHtmlNodeAttribute(node, "jsenv-debug") !== undefined;
15854
+
15855
+ const { crossorigin, integrity } = readFetchMetas(node);
15856
+ const isResourceHint = [
15857
+ "preconnect",
15858
+ "dns-prefetch",
15859
+ "prefetch",
15860
+ "preload",
15861
+ "modulepreload",
15862
+ ].includes(subtype);
15863
+ let attributeLocation = node.sourceCodeLocation.attrs[attributeName];
15864
+ if (
15865
+ !attributeLocation &&
15866
+ attributeName === "href" &&
15867
+ (node.tagName === "use" || node.tagName === "image")
15868
+ ) {
15869
+ attributeLocation = node.sourceCodeLocation.attrs["xlink:href"];
15870
+ }
15871
+ const attributeStart = attributeLocation.startOffset;
15872
+ const attributeValueStart = urlInfo.content.indexOf(
15873
+ attributeValue,
15874
+ attributeStart + `${attributeName}=`.length,
15875
+ );
15876
+ const attributeValueEnd = attributeValueStart + attributeValue.length;
15877
+ const reference = urlInfo.dependencies.found({
15878
+ type,
15879
+ subtype,
15880
+ expectedType,
15881
+ specifier: attributeValue,
15882
+ specifierLine: line,
15883
+ specifierColumn: column,
15884
+ specifierStart: attributeValueStart,
15885
+ specifierEnd: attributeValueEnd,
15886
+ isResourceHint,
15887
+ isWeak: isResourceHint,
15888
+ crossorigin,
15889
+ integrity,
15890
+ debug,
15891
+ astInfo: { node, attributeName },
15892
+ ...rest,
15893
+ });
15894
+ actions.push(async () => {
15895
+ await reference.readGeneratedSpecifier();
15896
+ mutations.push(() => {
15897
+ setHtmlNodeAttributes(node, {
15898
+ [attributeName]: reference.generatedSpecifier,
15899
+ });
15900
+ });
15901
+ });
15902
+ return reference;
15903
+ };
15904
+ const visitHref = (node, referenceProps) => {
15905
+ const href = getHtmlNodeAttribute(node, "href");
15906
+ if (href) {
15907
+ return createExternalReference(node, "href", href, referenceProps);
15908
+ }
15909
+ return null;
15910
+ };
15911
+ const visitSrc = (node, referenceProps) => {
15912
+ const src = getHtmlNodeAttribute(node, "src");
15913
+ if (src) {
15914
+ return createExternalReference(node, "src", src, referenceProps);
15915
+ }
15916
+ return null;
15917
+ };
15918
+ const visitSrcset = (node, referenceProps) => {
15919
+ const srcset = getHtmlNodeAttribute(node, "srcset");
15920
+ if (srcset) {
15921
+ const srcCandidates = parseSrcSet(srcset);
15922
+ return srcCandidates.map((srcCandidate) => {
15923
+ return createExternalReference(
15924
+ node,
15925
+ "srcset",
15926
+ srcCandidate.specifier,
15927
+ referenceProps,
15928
+ );
15929
+ });
15930
+ }
15931
+ return null;
15932
+ };
16187
15933
 
16188
- Object.keys(imports)
16189
- .sort(compareLengthOrLocaleCompare)
16190
- .forEach((name) => {
16191
- mappingsSorted[name] = imports[name];
16192
- });
15934
+ const createInlineReference = (
15935
+ node,
15936
+ inlineContent,
15937
+ { type, expectedType, contentType },
15938
+ ) => {
15939
+ const hotAccept =
15940
+ getHtmlNodeAttribute(node, "hot-accept") !== undefined;
15941
+ const { line, column, isOriginal } = getHtmlNodePosition(node, {
15942
+ preferOriginal: true,
15943
+ });
15944
+ const inlineContentUrl = getUrlForContentInsideHtml(node, {
15945
+ htmlUrl: urlInfo.url,
15946
+ });
15947
+ const debug = getHtmlNodeAttribute(node, "jsenv-debug") !== undefined;
15948
+ const inlineReference = urlInfo.dependencies.foundInline({
15949
+ type,
15950
+ expectedType,
15951
+ isOriginalPosition: isOriginal,
15952
+ // we remove 1 to the line because imagine the following html:
15953
+ // <style>body { color: red; }</style>
15954
+ // -> content starts same line as <style> (same for <script>)
15955
+ specifierLine: line - 1,
15956
+ specifierColumn: column,
15957
+ specifier: inlineContentUrl,
15958
+ contentType,
15959
+ content: inlineContent,
15960
+ debug,
15961
+ astInfo: { node },
15962
+ });
16193
15963
 
16194
- return mappingsSorted
16195
- };
15964
+ actions.push(async () => {
15965
+ if (expectedType === "js_module" && importmapFound) {
15966
+ await new Promise((resolve) => {
15967
+ importmapParsedCallbackSet.add(resolve);
15968
+ });
15969
+ }
15970
+ await inlineReference.urlInfo.cook();
15971
+ mutations.push(() => {
15972
+ if (hotAccept) {
15973
+ removeHtmlNodeText(node);
15974
+ setHtmlNodeAttributes(node, {
15975
+ "jsenv-cooked-by": "jsenv:html_inline_content_analysis",
15976
+ });
15977
+ } else {
15978
+ setHtmlNodeText(node, inlineReference.urlInfo.content, {
15979
+ indentation: false, // indentation would decrease stack trace precision
15980
+ });
15981
+ setHtmlNodeAttributes(node, {
15982
+ "jsenv-cooked-by": "jsenv:html_inline_content_analysis",
15983
+ });
15984
+ }
15985
+ });
15986
+ });
15987
+ return inlineReference;
15988
+ };
15989
+ const visitTextContent = (
15990
+ node,
15991
+ { type, subtype, expectedType, contentType },
15992
+ ) => {
15993
+ const inlineContent = getHtmlNodeText(node);
15994
+ if (!inlineContent) {
15995
+ return null;
15996
+ }
15997
+ return createInlineReference(node, inlineContent, {
15998
+ type,
15999
+ subtype,
16000
+ expectedType,
16001
+ contentType,
16002
+ });
16003
+ };
16196
16004
 
16197
- const sortScopes = (scopes) => {
16198
- const scopesSorted = {};
16005
+ visitHtmlNodes(htmlAst, {
16006
+ link: (linkNode) => {
16007
+ const rel = getHtmlNodeAttribute(linkNode, "rel");
16008
+ const type = getHtmlNodeAttribute(linkNode, "type");
16009
+ const ref = visitHref(linkNode, {
16010
+ type: "link_href",
16011
+ subtype: rel,
16012
+ // https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload#including_a_mime_type
16013
+ expectedContentType: type,
16014
+ });
16015
+ if (ref) {
16016
+ finalizeCallbacks.push(() => {
16017
+ if (ref.expectedType) ; else {
16018
+ ref.expectedType = decideLinkExpectedType(ref, urlInfo);
16019
+ }
16020
+ });
16021
+ }
16022
+ },
16023
+ style: inlineContent
16024
+ ? (styleNode) => {
16025
+ visitTextContent(styleNode, {
16026
+ type: "style",
16027
+ expectedType: "css",
16028
+ contentType: "text/css",
16029
+ });
16030
+ }
16031
+ : null,
16032
+ script: (scriptNode) => {
16033
+ const { type, subtype, contentType, extension } =
16034
+ analyzeScriptNode(scriptNode);
16035
+ if (type === "text") {
16036
+ // ignore <script type="whatever">foobar</script>
16037
+ // per HTML spec https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-type
16038
+ return;
16039
+ }
16040
+ if (type === "importmap") {
16041
+ importmapFound = true;
16042
+
16043
+ const src = getHtmlNodeAttribute(scriptNode, "src");
16044
+ if (src) {
16045
+ // Browser would throw on remote importmap
16046
+ // and won't sent a request to the server for it
16047
+ // We must precook the importmap to know its content and inline it into the HTML
16048
+ const importmapReference = createExternalReference(
16049
+ scriptNode,
16050
+ "src",
16051
+ src,
16052
+ {
16053
+ type: "script",
16054
+ subtype: "importmap",
16055
+ expectedType: "importmap",
16056
+ },
16057
+ );
16058
+ const { line, column, isOriginal } = getHtmlNodePosition(
16059
+ scriptNode,
16060
+ {
16061
+ preferOriginal: true,
16062
+ },
16063
+ );
16064
+ const importmapInlineUrl = getUrlForContentInsideHtml(
16065
+ scriptNode,
16066
+ {
16067
+ htmlUrl: urlInfo.url,
16068
+ },
16069
+ );
16070
+ const importmapReferenceInlined = importmapReference.inline({
16071
+ line: line - 1,
16072
+ column,
16073
+ isOriginal,
16074
+ specifier: importmapInlineUrl,
16075
+ contentType: "application/importmap+json",
16076
+ });
16077
+ const importmapInlineUrlInfo =
16078
+ importmapReferenceInlined.urlInfo;
16079
+ actions.push(async () => {
16080
+ await importmapInlineUrlInfo.cook();
16081
+ onImportmapReady(JSON.parse(importmapInlineUrlInfo.content));
16082
+ mutations.push(() => {
16083
+ setHtmlNodeText(
16084
+ scriptNode,
16085
+ importmapInlineUrlInfo.content,
16086
+ {
16087
+ indentation: "auto",
16088
+ },
16089
+ );
16090
+ setHtmlNodeAttributes(scriptNode, {
16091
+ "src": undefined,
16092
+ "jsenv-inlined-by": "jsenv:html_reference_analysis",
16093
+ "inlined-from-src": src,
16094
+ });
16095
+ });
16096
+ });
16097
+ } else {
16098
+ const htmlNodeText = getHtmlNodeText(scriptNode);
16099
+ if (htmlNodeText) {
16100
+ const importmapReference = createInlineReference(
16101
+ scriptNode,
16102
+ htmlNodeText,
16103
+ {
16104
+ type: "script",
16105
+ expectedType: "importmap",
16106
+ contentType: "application/importmap+json",
16107
+ },
16108
+ );
16109
+ const inlineImportmapUrlInfo = importmapReference.urlInfo;
16110
+ actions.push(async () => {
16111
+ await inlineImportmapUrlInfo.cook();
16112
+ onImportmapReady(
16113
+ JSON.parse(inlineImportmapUrlInfo.content),
16114
+ );
16115
+ mutations.push(() => {
16116
+ setHtmlNodeText(
16117
+ scriptNode,
16118
+ inlineImportmapUrlInfo.content,
16119
+ {
16120
+ indentation: "auto",
16121
+ },
16122
+ );
16123
+ setHtmlNodeAttributes(scriptNode, {
16124
+ "jsenv-cooked-by": "jsenv:html_reference_analysis",
16125
+ });
16126
+ });
16127
+ });
16128
+ }
16129
+ }
16130
+ // once this plugin knows the importmap, it will use it
16131
+ // to map imports. These import specifiers will be normalized
16132
+ // by "formatReference" making the importmap presence useless.
16133
+ // In dev/test we keep importmap into the HTML to see it even if useless
16134
+ // Duing build we get rid of it
16135
+ if (urlInfo.context.build) {
16136
+ mutations.push(() => {
16137
+ removeHtmlNode(scriptNode);
16138
+ });
16139
+ }
16140
+ return;
16141
+ }
16142
+ const externalRef = visitSrc(scriptNode, {
16143
+ type: "script",
16144
+ subtype: type,
16145
+ expectedType: type,
16146
+ });
16147
+ if (externalRef) {
16148
+ return;
16149
+ }
16199
16150
 
16200
- Object.keys(scopes)
16201
- .sort(compareLengthOrLocaleCompare)
16202
- .forEach((scopeSpecifier) => {
16203
- scopesSorted[scopeSpecifier] = sortImports(scopes[scopeSpecifier]);
16204
- });
16151
+ // now visit the content, if any
16152
+ if (!inlineContent) {
16153
+ return;
16154
+ }
16155
+ // If the inline script was already handled by an other plugin, ignore it
16156
+ // - we want to preserve inline scripts generated by html supervisor during dev
16157
+ // - we want to avoid cooking twice a script during build
16158
+ if (
16159
+ !inlineConvertedScript &&
16160
+ getHtmlNodeAttribute(scriptNode, "jsenv-injected-by") ===
16161
+ "jsenv:js_module_fallback"
16162
+ ) {
16163
+ return;
16164
+ }
16205
16165
 
16206
- return scopesSorted
16207
- };
16166
+ const inlineRef = visitTextContent(scriptNode, {
16167
+ type: "script",
16168
+ subtype,
16169
+ expectedType: type,
16170
+ contentType,
16171
+ });
16172
+ if (inlineRef) {
16173
+ // 1. <script type="jsx"> becomes <script>
16174
+ if (type === "js_classic" && extension !== ".js") {
16175
+ mutations.push(() => {
16176
+ setHtmlNodeAttributes(scriptNode, {
16177
+ type: undefined,
16178
+ });
16179
+ });
16180
+ }
16181
+ // 2. <script type="module/jsx"> becomes <script type="module">
16182
+ if (type === "js_module" && extension !== ".js") {
16183
+ mutations.push(() => {
16184
+ setHtmlNodeAttributes(scriptNode, {
16185
+ type: "module",
16186
+ });
16187
+ });
16188
+ }
16189
+ }
16190
+ },
16191
+ a: (aNode) => {
16192
+ visitHref(aNode, {
16193
+ type: "a_href",
16194
+ });
16195
+ },
16196
+ iframe: (iframeNode) => {
16197
+ visitSrc(iframeNode, {
16198
+ type: "iframe_src",
16199
+ });
16200
+ },
16201
+ img: (imgNode) => {
16202
+ visitSrc(imgNode, {
16203
+ type: "img_src",
16204
+ });
16205
+ visitSrcset(imgNode, {
16206
+ type: "img_srcset",
16207
+ });
16208
+ },
16209
+ source: (sourceNode) => {
16210
+ visitSrc(sourceNode, {
16211
+ type: "source_src",
16212
+ });
16213
+ visitSrcset(sourceNode, {
16214
+ type: "source_srcset",
16215
+ });
16216
+ },
16217
+ // svg <image> tag
16218
+ image: (imageNode) => {
16219
+ visitHref(imageNode, {
16220
+ type: "image_href",
16221
+ });
16222
+ },
16223
+ use: (useNode) => {
16224
+ visitHref(useNode, {
16225
+ type: "use_href",
16226
+ });
16227
+ },
16228
+ });
16229
+ if (!importmapFound) {
16230
+ onImportmapReady();
16231
+ }
16232
+ finalizeCallbacks.forEach((finalizeCallback) => {
16233
+ finalizeCallback();
16234
+ });
16208
16235
 
16209
- const compareLengthOrLocaleCompare = (a, b) => {
16210
- return b.length - a.length || a.localeCompare(b)
16236
+ if (actions.length > 0) {
16237
+ await Promise.all(actions.map((action) => action()));
16238
+ actions.length = 0;
16239
+ }
16240
+ if (mutations.length === 0) {
16241
+ return null;
16242
+ }
16243
+ mutations.forEach((mutation) => mutation());
16244
+ mutations.length = 0;
16245
+ return stringifyHtmlAst(htmlAst);
16246
+ },
16247
+ },
16248
+ };
16211
16249
  };
16212
16250
 
16213
- const normalizeImportMap = (importMap, baseUrl) => {
16214
- assertImportMap(importMap);
16215
-
16216
- if (!isStringOrUrl(baseUrl)) {
16217
- throw new TypeError(formulateBaseUrlMustBeStringOrUrl({ baseUrl }))
16251
+ const crossOriginCompatibleTagNames = ["script", "link", "img", "source"];
16252
+ const integrityCompatibleTagNames = ["script", "link", "img", "source"];
16253
+ const readFetchMetas = (node) => {
16254
+ const meta = {};
16255
+ if (crossOriginCompatibleTagNames.includes(node.nodeName)) {
16256
+ const crossorigin = getHtmlNodeAttribute(node, "crossorigin") !== undefined;
16257
+ meta.crossorigin = crossorigin;
16218
16258
  }
16219
-
16220
- const { imports, scopes } = importMap;
16221
-
16222
- return {
16223
- imports: imports ? normalizeMappings(imports, baseUrl) : undefined,
16224
- scopes: scopes ? normalizeScopes(scopes, baseUrl) : undefined,
16259
+ if (integrityCompatibleTagNames.includes(node.nodeName)) {
16260
+ const integrity = getHtmlNodeAttribute(node, "integrity");
16261
+ meta.integrity = integrity;
16225
16262
  }
16263
+ return meta;
16226
16264
  };
16227
16265
 
16228
- const isStringOrUrl = (value) => {
16229
- if (typeof value === "string") {
16230
- return true
16266
+ const decideLinkExpectedType = (linkReference, htmlUrlInfo) => {
16267
+ const rel = getHtmlNodeAttribute(linkReference.astInfo.node, "rel");
16268
+ if (rel === "webmanifest") {
16269
+ return "webmanifest";
16231
16270
  }
16232
-
16233
- if (typeof URL === "function" && value instanceof URL) {
16234
- return true
16271
+ if (rel === "modulepreload") {
16272
+ return "js_module";
16235
16273
  }
16236
-
16237
- return false
16238
- };
16239
-
16240
- const normalizeMappings = (mappings, baseUrl) => {
16241
- const mappingsNormalized = {};
16242
-
16243
- Object.keys(mappings).forEach((specifier) => {
16244
- const address = mappings[specifier];
16245
-
16246
- if (typeof address !== "string") {
16247
- console.warn(
16248
- formulateAddressMustBeAString({
16249
- address,
16250
- specifier,
16251
- }),
16252
- );
16253
- return
16254
- }
16255
-
16256
- const specifierResolved = resolveSpecifier(specifier, baseUrl) || specifier;
16257
-
16258
- const addressUrl = tryUrlResolution(address, baseUrl);
16259
- if (addressUrl === null) {
16260
- console.warn(
16261
- formulateAdressResolutionFailed({
16262
- address,
16263
- baseUrl,
16264
- specifier,
16265
- }),
16266
- );
16267
- return
16274
+ if (rel === "stylesheet") {
16275
+ return "css";
16276
+ }
16277
+ if (rel === "preload") {
16278
+ // https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload#what_types_of_content_can_be_preloaded
16279
+ const as = getHtmlNodeAttribute(linkReference.astInfo.node, "as");
16280
+ if (as === "document") {
16281
+ return "html";
16268
16282
  }
16269
-
16270
- if (specifier.endsWith("/") && !addressUrl.endsWith("/")) {
16271
- console.warn(
16272
- formulateAddressUrlRequiresTrailingSlash({
16273
- addressUrl,
16274
- address,
16275
- specifier,
16276
- }),
16277
- );
16278
- return
16283
+ if (as === "style") {
16284
+ return "css";
16279
16285
  }
16280
- mappingsNormalized[specifierResolved] = addressUrl;
16281
- });
16282
-
16283
- return sortImports(mappingsNormalized)
16286
+ if (as === "script") {
16287
+ for (const referenceToOther of htmlUrlInfo.referenceToOthersSet) {
16288
+ if (referenceToOther.url !== linkReference.url) {
16289
+ continue;
16290
+ }
16291
+ if (referenceToOther.type !== "script") {
16292
+ continue;
16293
+ }
16294
+ return referenceToOther.expectedType;
16295
+ }
16296
+ return undefined;
16297
+ }
16298
+ }
16299
+ return undefined;
16284
16300
  };
16285
16301
 
16286
- const normalizeScopes = (scopes, baseUrl) => {
16287
- const scopesNormalized = {};
16302
+ // const applyWebUrlResolution = (url, baseUrl) => {
16303
+ // if (url[0] === "/") {
16304
+ // return new URL(url.slice(1), baseUrl).href;
16305
+ // }
16306
+ // return new URL(url, baseUrl).href;
16307
+ // };
16288
16308
 
16289
- Object.keys(scopes).forEach((scopeSpecifier) => {
16290
- const scopeMappings = scopes[scopeSpecifier];
16291
- const scopeUrl = tryUrlResolution(scopeSpecifier, baseUrl);
16292
- if (scopeUrl === null) {
16293
- console.warn(
16294
- formulateScopeResolutionFailed({
16295
- scope: scopeSpecifier,
16296
- baseUrl,
16297
- }),
16298
- );
16299
- return
16300
- }
16301
- const scopeValueNormalized = normalizeMappings(scopeMappings, baseUrl);
16302
- scopesNormalized[scopeUrl] = scopeValueNormalized;
16303
- });
16309
+ // css: parseAndTransformCssUrls,
16304
16310
 
16305
- return sortScopes(scopesNormalized)
16311
+ const jsenvPluginWebmanifestReferenceAnalysis = () => {
16312
+ return {
16313
+ name: "jsenv:webmanifest_reference_analysis",
16314
+ appliesDuring: "*",
16315
+ transformUrlContent: {
16316
+ webmanifest: parseAndTransformWebmanifestUrls,
16317
+ },
16318
+ };
16306
16319
  };
16307
16320
 
16308
- const formulateBaseUrlMustBeStringOrUrl = ({
16309
- baseUrl,
16310
- }) => `baseUrl must be a string or an url.
16311
- --- base url ---
16312
- ${baseUrl}`;
16313
-
16314
- const formulateAddressMustBeAString = ({
16315
- specifier,
16316
- address,
16317
- }) => `Address must be a string.
16318
- --- address ---
16319
- ${address}
16320
- --- specifier ---
16321
- ${specifier}`;
16322
-
16323
- const formulateAdressResolutionFailed = ({
16324
- address,
16325
- baseUrl,
16326
- specifier,
16327
- }) => `Address url resolution failed.
16328
- --- address ---
16329
- ${address}
16330
- --- base url ---
16331
- ${baseUrl}
16332
- --- specifier ---
16333
- ${specifier}`;
16321
+ const parseAndTransformWebmanifestUrls = async (urlInfo) => {
16322
+ const content = urlInfo.content;
16323
+ const manifest = JSON.parse(content);
16324
+ const actions = [];
16325
+ const { icons = [] } = manifest;
16326
+ icons.forEach((icon) => {
16327
+ const iconReference = urlInfo.dependencies.found({
16328
+ type: "webmanifest_icon_src",
16329
+ specifier: icon.src,
16330
+ });
16331
+ actions.push(async () => {
16332
+ await iconReference.readGeneratedSpecifier();
16333
+ icon.src = iconReference.generatedSpecifier;
16334
+ });
16335
+ });
16334
16336
 
16335
- const formulateAddressUrlRequiresTrailingSlash = ({
16336
- addressURL,
16337
- address,
16338
- specifier,
16339
- }) => `Address must end with /.
16340
- --- address url ---
16341
- ${addressURL}
16342
- --- address ---
16343
- ${address}
16344
- --- specifier ---
16345
- ${specifier}`;
16337
+ if (actions.length === 0) {
16338
+ return null;
16339
+ }
16340
+ await Promise.all(actions.map((action) => action()));
16341
+ return JSON.stringify(manifest, null, " ");
16342
+ };
16346
16343
 
16347
- const formulateScopeResolutionFailed = ({
16348
- scope,
16349
- baseUrl,
16350
- }) => `Scope url resolution failed.
16351
- --- scope ---
16352
- ${scope}
16353
- --- base url ---
16354
- ${baseUrl}`;
16344
+ /*
16345
+ * https://github.com/parcel-bundler/parcel/blob/v2/packages/transformers/css/src/CSSTransformer.js
16346
+ */
16355
16347
 
16356
- const pathnameToExtension = (pathname) => {
16357
- const slashLastIndex = pathname.lastIndexOf("/");
16358
- if (slashLastIndex !== -1) {
16359
- pathname = pathname.slice(slashLastIndex + 1);
16360
- }
16361
16348
 
16362
- const dotLastIndex = pathname.lastIndexOf(".");
16363
- if (dotLastIndex === -1) return ""
16364
- // if (dotLastIndex === pathname.length - 1) return ""
16365
- return pathname.slice(dotLastIndex)
16349
+ const jsenvPluginCssReferenceAnalysis = () => {
16350
+ return {
16351
+ name: "jsenv:css_reference_analysis",
16352
+ appliesDuring: "*",
16353
+ transformUrlContent: {
16354
+ css: parseAndTransformCssUrls,
16355
+ },
16356
+ };
16366
16357
  };
16367
16358
 
16368
- const resolveImport = ({
16369
- specifier,
16370
- importer,
16371
- importMap,
16372
- defaultExtension = false,
16373
- createBareSpecifierError,
16374
- onImportMapping = () => {},
16375
- }) => {
16376
- let url;
16377
- if (importMap) {
16378
- url = applyImportMap({
16379
- importMap,
16380
- specifier,
16381
- importer,
16382
- createBareSpecifierError,
16383
- onImportMapping,
16359
+ const parseAndTransformCssUrls = async (urlInfo) => {
16360
+ const cssUrls = await parseCssUrls({
16361
+ css: urlInfo.content,
16362
+ url: urlInfo.originalUrl,
16363
+ });
16364
+ const actions = [];
16365
+ const magicSource = createMagicSource(urlInfo.content);
16366
+ for (const cssUrl of cssUrls) {
16367
+ const reference = urlInfo.dependencies.found({
16368
+ type: cssUrl.type,
16369
+ specifier: cssUrl.specifier,
16370
+ specifierStart: cssUrl.start,
16371
+ specifierEnd: cssUrl.end,
16372
+ specifierLine: cssUrl.line,
16373
+ specifierColumn: cssUrl.column,
16374
+ });
16375
+ actions.push(async () => {
16376
+ await reference.readGeneratedSpecifier();
16377
+ const replacement = reference.generatedSpecifier;
16378
+ magicSource.replace({
16379
+ start: cssUrl.start,
16380
+ end: cssUrl.end,
16381
+ replacement,
16382
+ });
16384
16383
  });
16385
- } else {
16386
- url = resolveUrl(specifier, importer);
16387
16384
  }
16388
-
16389
- if (defaultExtension) {
16390
- url = applyDefaultExtension({ url, importer, defaultExtension });
16385
+ if (actions.length > 0) {
16386
+ await Promise.all(actions.map((action) => action()));
16391
16387
  }
16388
+ return magicSource.toContentAndSourcemap();
16389
+ };
16392
16390
 
16393
- return url
16391
+ const jsenvPluginJsReferenceAnalysis = ({ inlineContent }) => {
16392
+ return [
16393
+ {
16394
+ name: "jsenv:js_reference_analysis",
16395
+ appliesDuring: "*",
16396
+ transformUrlContent: {
16397
+ js_classic: (urlInfo) =>
16398
+ parseAndTransformJsReferences(urlInfo, {
16399
+ inlineContent,
16400
+ canUseTemplateLiterals:
16401
+ urlInfo.context.isSupportedOnCurrentClients("template_literals"),
16402
+ }),
16403
+ js_module: (urlInfo) =>
16404
+ parseAndTransformJsReferences(urlInfo, {
16405
+ inlineContent,
16406
+ canUseTemplateLiterals:
16407
+ urlInfo.context.isSupportedOnCurrentClients("template_literals"),
16408
+ }),
16409
+ },
16410
+ },
16411
+ ];
16394
16412
  };
16395
16413
 
16396
- const applyDefaultExtension = ({ url, importer, defaultExtension }) => {
16397
- if (urlToPathname(url).endsWith("/")) {
16398
- return url
16399
- }
16414
+ const parseAndTransformJsReferences = async (
16415
+ urlInfo,
16416
+ { inlineContent, canUseTemplateLiterals },
16417
+ ) => {
16418
+ const magicSource = createMagicSource(urlInfo.content);
16419
+ const parallelActions = [];
16420
+ const sequentialActions = [];
16421
+ const isNodeJs =
16422
+ Object.keys(urlInfo.context.runtimeCompat).toString() === "node";
16423
+
16424
+ const onInlineReference = (inlineReferenceInfo) => {
16425
+ const inlineUrl = getUrlForContentInsideJs(inlineReferenceInfo, {
16426
+ url: urlInfo.url,
16427
+ });
16428
+ let { quote } = inlineReferenceInfo;
16429
+ if (quote === "`" && !canUseTemplateLiterals) {
16430
+ // if quote is "`" and template literals are not supported
16431
+ // we'll use a regular string (single or double quote)
16432
+ // when rendering the string
16433
+ quote = JS_QUOTES.pickBest(inlineReferenceInfo.content);
16434
+ }
16435
+ const inlineReference = urlInfo.dependencies.foundInline({
16436
+ type: "js_inline_content",
16437
+ subtype: inlineReferenceInfo.type, // "new_blob_first_arg", "new_inline_content_first_arg", "json_parse_first_arg"
16438
+ isOriginalPosition: urlInfo.content === urlInfo.originalContent,
16439
+ specifierLine: inlineReferenceInfo.line,
16440
+ specifierColumn: inlineReferenceInfo.column,
16441
+ specifier: inlineUrl,
16442
+ contentType: inlineReferenceInfo.contentType,
16443
+ content: inlineReferenceInfo.content,
16444
+ });
16445
+ const inlineUrlInfo = inlineReference.urlInfo;
16446
+ inlineUrlInfo.jsQuote = quote;
16447
+ inlineReference.escape = (value) => {
16448
+ return JS_QUOTES.escapeSpecialChars(value.slice(1, -1), { quote });
16449
+ };
16400
16450
 
16401
- if (typeof defaultExtension === "string") {
16402
- const extension = pathnameToExtension(url);
16403
- if (extension === "") {
16404
- return `${url}${defaultExtension}`
16451
+ sequentialActions.push(async () => {
16452
+ await inlineUrlInfo.cook();
16453
+ const replacement = JS_QUOTES.escapeSpecialChars(inlineUrlInfo.content, {
16454
+ quote,
16455
+ });
16456
+ magicSource.replace({
16457
+ start: inlineReferenceInfo.start,
16458
+ end: inlineReferenceInfo.end,
16459
+ replacement,
16460
+ });
16461
+ });
16462
+ };
16463
+ const onExternalReference = (externalReferenceInfo) => {
16464
+ if (
16465
+ externalReferenceInfo.subtype === "import_static" ||
16466
+ externalReferenceInfo.subtype === "import_dynamic"
16467
+ ) {
16468
+ urlInfo.data.usesImport = true;
16405
16469
  }
16406
- return url
16407
- }
16408
-
16409
- if (defaultExtension === true) {
16410
- const extension = pathnameToExtension(url);
16411
- if (extension === "" && importer) {
16412
- const importerPathname = urlToPathname(importer);
16413
- const importerExtension = pathnameToExtension(importerPathname);
16414
- return `${url}${importerExtension}`
16470
+ if (
16471
+ isNodeJs &&
16472
+ externalReferenceInfo.type === "js_url" &&
16473
+ externalReferenceInfo.expectedSubtype === "worker" &&
16474
+ externalReferenceInfo.expectedType === "js_classic" &&
16475
+ // TODO: it's true also if closest package.json
16476
+ // is type: module
16477
+ urlToExtension$1(
16478
+ new URL(externalReferenceInfo.specifier, urlInfo.url).href,
16479
+ ) === ".mjs"
16480
+ ) {
16481
+ externalReferenceInfo.expectedType = "js_module";
16482
+ }
16483
+ const reference = urlInfo.dependencies.found({
16484
+ type: externalReferenceInfo.type,
16485
+ subtype: externalReferenceInfo.subtype,
16486
+ expectedType: externalReferenceInfo.expectedType,
16487
+ expectedSubtype: externalReferenceInfo.expectedSubtype || urlInfo.subtype,
16488
+ specifier: externalReferenceInfo.specifier,
16489
+ specifierStart: externalReferenceInfo.start,
16490
+ specifierEnd: externalReferenceInfo.end,
16491
+ specifierLine: externalReferenceInfo.line,
16492
+ specifierColumn: externalReferenceInfo.column,
16493
+ data: externalReferenceInfo.data,
16494
+ baseUrl: {
16495
+ "StringLiteral": externalReferenceInfo.baseUrl,
16496
+ "window.location": urlInfo.url,
16497
+ "window.origin": urlInfo.context.rootDirectoryUrl,
16498
+ "import.meta.url": urlInfo.url,
16499
+ "context.meta.url": urlInfo.url,
16500
+ "document.currentScript.src": urlInfo.url,
16501
+ }[externalReferenceInfo.baseUrlType],
16502
+ importAttributes: externalReferenceInfo.importAttributes,
16503
+ astInfo: externalReferenceInfo.astInfo,
16504
+ });
16505
+ parallelActions.push(async () => {
16506
+ await reference.readGeneratedSpecifier();
16507
+ const replacement = reference.generatedSpecifier;
16508
+ magicSource.replace({
16509
+ start: externalReferenceInfo.start,
16510
+ end: externalReferenceInfo.end,
16511
+ replacement,
16512
+ });
16513
+ if (reference.mutation) {
16514
+ reference.mutation(magicSource, urlInfo);
16515
+ }
16516
+ });
16517
+ };
16518
+ const jsReferenceInfos = parseJsUrls({
16519
+ js: urlInfo.content,
16520
+ url: urlInfo.originalUrl,
16521
+ ast: urlInfo.contentAst,
16522
+ isJsModule: urlInfo.type === "js_module",
16523
+ isWebWorker: isWebWorkerUrlInfo(urlInfo),
16524
+ inlineContent,
16525
+ isNodeJs,
16526
+ });
16527
+ for (const jsReferenceInfo of jsReferenceInfos) {
16528
+ if (jsReferenceInfo.isInline) {
16529
+ onInlineReference(jsReferenceInfo);
16530
+ } else {
16531
+ onExternalReference(jsReferenceInfo);
16415
16532
  }
16416
16533
  }
16534
+ if (parallelActions.length > 0) {
16535
+ await Promise.all(parallelActions.map((action) => action()));
16536
+ }
16537
+ if (sequentialActions.length > 0) {
16538
+ await sequentialActions.reduce(async (previous, action) => {
16539
+ await previous;
16540
+ await action();
16541
+ }, Promise.resolve());
16542
+ }
16417
16543
 
16418
- return url
16544
+ const { content, sourcemap } = magicSource.toContentAndSourcemap();
16545
+ return { content, sourcemap };
16419
16546
  };
16420
16547
 
16421
- /*
16422
- * Plugin to read and apply importmap files found in html files.
16423
- * - feeds importmap files to jsenv kitchen
16424
- * - use importmap to resolve import (when there is one + fallback to other resolution mecanism)
16425
- * - inline importmap with [src=""]
16426
- *
16427
- * A correct importmap resolution should scope importmap resolution per html file.
16428
- * It would be doable by adding ?html_id to each js file in order to track
16429
- * the html file importing it.
16430
- * Considering it happens only when all the following conditions are met:
16431
- * - 2+ html files are using an importmap
16432
- * - the importmap used is not the same
16433
- * - the importmap contain conflicting mappings
16434
- * - these html files are both executed during the same scenario (dev, test, build)
16435
- * And that it would be ugly to see ?html_id all over the place
16436
- * -> The importmap resolution implemented here takes a shortcut and does the following:
16437
- * - All importmap found are merged into a single one that is applied to every import specifiers
16438
- */
16439
-
16440
-
16441
- const jsenvPluginImportmap = () => {
16442
- let finalImportmap = null;
16443
- const importmaps = {};
16444
- const onHtmlImportmapParsed = (importmap, htmlUrl) => {
16445
- importmaps[htmlUrl] = importmap
16446
- ? normalizeImportMap(importmap, htmlUrl)
16447
- : null;
16448
- finalImportmap = Object.keys(importmaps).reduce((previous, url) => {
16449
- const importmap = importmaps[url];
16450
- if (!previous) {
16451
- return importmap;
16452
- }
16453
- if (!importmap) {
16454
- return previous;
16455
- }
16456
- return composeTwoImportMaps(previous, importmap);
16457
- }, null);
16458
- };
16548
+ const jsenvPluginReferenceAnalysis = ({
16549
+ inlineContent = true,
16550
+ inlineConvertedScript = false,
16551
+ fetchInlineUrls = true,
16552
+ }) => {
16553
+ return [
16554
+ jsenvPluginDirectoryReferenceAnalysis(),
16555
+ jsenvPluginHtmlReferenceAnalysis({
16556
+ inlineContent,
16557
+ inlineConvertedScript,
16558
+ }),
16559
+ jsenvPluginWebmanifestReferenceAnalysis(),
16560
+ jsenvPluginCssReferenceAnalysis(),
16561
+ jsenvPluginJsReferenceAnalysis({
16562
+ inlineContent,
16563
+ }),
16564
+ ...(inlineContent ? [jsenvPluginDataUrlsAnalysis()] : []),
16565
+ ...(inlineContent && fetchInlineUrls
16566
+ ? [jsenvPluginInlineContentFetcher()]
16567
+ : []),
16568
+ jsenvPluginReferenceExpectedTypes(),
16569
+ ];
16570
+ };
16459
16571
 
16572
+ const jsenvPluginInlineContentFetcher = () => {
16460
16573
  return {
16461
- name: "jsenv:importmap",
16574
+ name: "jsenv:inline_content_fetcher",
16462
16575
  appliesDuring: "*",
16463
- resolveReference: {
16464
- js_import: (reference) => {
16465
- if (!finalImportmap) {
16466
- return null;
16467
- }
16468
- try {
16469
- let fromMapping = false;
16470
- const result = resolveImport({
16471
- specifier: reference.specifier,
16472
- importer: reference.ownerUrlInfo.url,
16473
- importMap: finalImportmap,
16474
- onImportMapping: () => {
16475
- fromMapping = true;
16476
- },
16477
- });
16478
- if (fromMapping) {
16479
- return result;
16480
- }
16481
- return null;
16482
- } catch (e) {
16483
- if (e.message.includes("bare specifier")) {
16484
- // in theory we should throw to be compliant with web behaviour
16485
- // but for now it's simpler to return null
16486
- // and let a chance to other plugins to handle the bare specifier
16487
- // (node esm resolution)
16488
- // and we want importmap to be prio over node esm so we cannot put this plugin after
16489
- return null;
16490
- }
16491
- throw e;
16492
- }
16493
- },
16494
- },
16495
- transformUrlContent: {
16496
- html: async (htmlUrlInfo) => {
16497
- const htmlAst = parseHtmlString(htmlUrlInfo.content);
16498
- const importmap = findHtmlNode(htmlAst, (node) => {
16499
- if (node.nodeName !== "script") {
16500
- return false;
16501
- }
16502
- const type = getHtmlNodeAttribute(node, "type");
16503
- if (type === undefined || type !== "importmap") {
16504
- return false;
16505
- }
16506
- return true;
16507
- });
16508
- if (!importmap) {
16509
- onHtmlImportmapParsed(null, htmlUrlInfo.url);
16510
- return null;
16511
- }
16512
- const handleInlineImportmap = async (importmap, htmlNodeText) => {
16513
- const { line, column, isOriginal } = getHtmlNodePosition(importmap, {
16514
- preferOriginal: true,
16515
- });
16516
- const inlineImportmapUrl = getUrlForContentInsideHtml(importmap, {
16517
- htmlUrl: htmlUrlInfo.url,
16518
- });
16519
- const inlineImportmapReference = htmlUrlInfo.dependencies.foundInline(
16520
- {
16521
- type: "script",
16522
- isOriginalPosition: isOriginal,
16523
- specifierLine: line - 1,
16524
- specifierColumn: column,
16525
- specifier: inlineImportmapUrl,
16526
- contentType: "application/importmap+json",
16527
- content: htmlNodeText,
16528
- },
16529
- );
16530
- const inlineImportmapUrlInfo = inlineImportmapReference.urlInfo;
16531
- await inlineImportmapUrlInfo.cook();
16532
- setHtmlNodeText(importmap, inlineImportmapUrlInfo.content, {
16533
- indentation: "auto",
16534
- });
16535
- setHtmlNodeAttributes(importmap, {
16536
- "jsenv-cooked-by": "jsenv:importmap",
16537
- });
16538
- onHtmlImportmapParsed(
16539
- JSON.parse(inlineImportmapUrlInfo.content),
16540
- htmlUrlInfo.url,
16541
- );
16542
- };
16543
- const handleImportmapWithSrc = async (importmap, src) => {
16544
- // Browser would throw on remote importmap
16545
- // and won't sent a request to the server for it
16546
- // We must precook the importmap to know its content and inline it into the HTML
16547
- // In this situation the ref to the importmap was already discovered
16548
- // when parsing the HTML
16549
- let importmapReference = null;
16550
- for (const referenceToOther of htmlUrlInfo.referenceToOthersSet) {
16551
- if (referenceToOther.generatedSpecifier === src) {
16552
- importmapReference = referenceToOther;
16553
- break;
16554
- }
16555
- }
16556
- const { line, column, isOriginal } = getHtmlNodePosition(importmap, {
16557
- preferOriginal: true,
16558
- });
16559
- const importmapInlineUrl = getUrlForContentInsideHtml(importmap, {
16560
- htmlUrl: htmlUrlInfo.url,
16561
- });
16562
- const importmapReferenceInlined = importmapReference.inline({
16563
- line: line - 1,
16564
- column,
16565
- isOriginal,
16566
- specifier: importmapInlineUrl,
16567
- contentType: "application/importmap+json",
16568
- });
16569
- const importmapInlineUrlInfo = importmapReferenceInlined.urlInfo;
16570
- await importmapInlineUrlInfo.cook();
16571
- onHtmlImportmapParsed(
16572
- JSON.parse(importmapInlineUrlInfo.content),
16573
- htmlUrlInfo.url,
16574
- );
16575
- setHtmlNodeText(importmap, importmapInlineUrlInfo.content, {
16576
- indentation: "auto",
16577
- });
16578
- setHtmlNodeAttributes(importmap, {
16579
- "src": undefined,
16580
- "jsenv-inlined-by": "jsenv:importmap",
16581
- "inlined-from-src": src,
16582
- });
16583
- };
16584
-
16585
- const src = getHtmlNodeAttribute(importmap, "src");
16586
- if (src) {
16587
- await handleImportmapWithSrc(importmap, src);
16588
- } else {
16589
- const htmlNodeText = getHtmlNodeText(importmap);
16590
- if (htmlNodeText) {
16591
- await handleInlineImportmap(importmap, htmlNodeText);
16592
- }
16576
+ fetchUrlContent: async (urlInfo) => {
16577
+ if (!urlInfo.isInline) {
16578
+ return null;
16579
+ }
16580
+ // - we must use last reference because
16581
+ // when updating the file, first reference is the previous version
16582
+ // - we cannot use urlInfo.lastReference because it can be the reference created by "http_request"
16583
+ let lastInlineReference;
16584
+ for (const reference of urlInfo.referenceFromOthersSet) {
16585
+ if (reference.isInline) {
16586
+ lastInlineReference = reference;
16593
16587
  }
16594
- // once this plugin knows the importmap, it will use it
16595
- // to map imports. These import specifiers will be normalized
16596
- // by "formatReferencedUrl" making the importmap presence useless.
16597
- // In dev/test we keep importmap into the HTML to see it even if useless
16598
- // Duing build we get rid of it
16599
- if (htmlUrlInfo.context.build) {
16600
- removeHtmlNode(importmap);
16588
+ }
16589
+ const { prev } = lastInlineReference;
16590
+ if (prev && !prev.isInline) {
16591
+ // got inlined, cook original url
16592
+ if (lastInlineReference.content === undefined) {
16593
+ const originalUrlInfo = prev.urlInfo;
16594
+ await originalUrlInfo.cook();
16595
+ lastInlineReference.content = originalUrlInfo.content;
16596
+ lastInlineReference.contentType = originalUrlInfo.contentType;
16601
16597
  }
16602
- return {
16603
- content: stringifyHtmlAst(htmlAst),
16604
- };
16605
- },
16598
+ }
16599
+ return {
16600
+ originalContent: urlInfo.originalContent,
16601
+ content: lastInlineReference.content,
16602
+ contentType: lastInlineReference.contentType,
16603
+ };
16606
16604
  },
16607
16605
  };
16608
16606
  };
@@ -19478,14 +19476,22 @@ const jsenvPluginAutoreloadServer = ({
19478
19476
  return iterateMemoized(firstUrlInfo, []);
19479
19477
  };
19480
19478
 
19481
- const propagationResult = propagateUpdate(firstUrlInfo);
19479
+ let propagationResult = propagateUpdate(firstUrlInfo);
19482
19480
  const seen = new Set();
19483
19481
  const invalidateImporters = (urlInfo) => {
19484
19482
  // to indicate this urlInfo should be modified
19485
19483
  for (const referenceFromOther of urlInfo.referenceFromOthersSet) {
19486
19484
  const urlInfoReferencingThisOne = referenceFromOther.ownerUrlInfo;
19487
- const { hotAcceptDependencies = [] } =
19485
+ const { hotDecline, hotAcceptDependencies = [] } =
19488
19486
  urlInfoReferencingThisOne.data;
19487
+ if (hotDecline) {
19488
+ propagationResult = {
19489
+ declined: true,
19490
+ reason: `file declines hot reload`,
19491
+ declinedBy: formatUrlForClient(urlInfoReferencingThisOne.url),
19492
+ };
19493
+ return;
19494
+ }
19489
19495
  if (hotAcceptDependencies.includes(urlInfo.url)) {
19490
19496
  continue;
19491
19497
  }
@@ -19764,7 +19770,7 @@ const jsenvPluginRibbon = ({
19764
19770
  null,
19765
19771
  " ",
19766
19772
  );
19767
- injectHtmlNode(
19773
+ injectHtmlNodeAsEarlyAsPossible(
19768
19774
  htmlAst,
19769
19775
  createHtmlNode({
19770
19776
  tagName: "script",
@@ -19829,7 +19835,6 @@ const getCorePlugins = ({
19829
19835
  jsenvPluginReferenceAnalysis(referenceAnalysis),
19830
19836
  ...(injections ? [jsenvPluginInjections(injections)] : []),
19831
19837
  jsenvPluginTranspilation(transpilation),
19832
- jsenvPluginImportmap(),
19833
19838
  ...(inlining ? [jsenvPluginInlining()] : []),
19834
19839
  ...(supervisor ? [jsenvPluginSupervisor(supervisor)] : []), // after inline as it needs inline script to be cooked
19835
19840
 
@@ -22279,7 +22284,7 @@ const createFileService = ({
22279
22284
  associations: watchAssociations,
22280
22285
  });
22281
22286
  urlInfoCreated.isWatched = watch;
22282
- // wehn an url depends on many others, we check all these (like package.json)
22287
+ // when an url depends on many others, we check all these (like package.json)
22283
22288
  urlInfoCreated.isValid = () => {
22284
22289
  if (!urlInfoCreated.url.startsWith("file:")) {
22285
22290
  return false;