@jsenv/core 35.0.5 → 36.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +1 -1
  2. package/dist/js/inline_content.js +5 -4
  3. package/dist/jsenv_core.js +1127 -1497
  4. package/package.json +8 -8
  5. package/src/build/build.js +49 -41
  6. package/src/dev/file_service.js +7 -17
  7. package/src/dev/start_dev_server.js +12 -7
  8. package/src/kitchen/kitchen.js +38 -19
  9. package/src/kitchen/url_graph.js +1 -1
  10. package/src/plugins/autoreload/jsenv_plugin_hmr.js +2 -2
  11. package/src/plugins/file_urls/jsenv_plugin_file_urls.js +4 -4
  12. package/src/plugins/http_urls/jsenv_plugin_http_urls.js +1 -1
  13. package/src/plugins/importmap/jsenv_plugin_importmap.js +1 -1
  14. package/src/plugins/inlining/jsenv_plugin_inlining.js +1 -1
  15. package/src/plugins/inlining/jsenv_plugin_inlining_as_data_url.js +13 -2
  16. package/src/plugins/plugin_controller.js +19 -10
  17. package/src/plugins/plugins.js +21 -25
  18. package/src/plugins/{url_analysis/css/css_urls.js → reference_analysis/css/jsenv_plugin_css_reference_analysis.js} +11 -1
  19. package/src/plugins/{inline_content_analysis/jsenv_plugin_data_urls.js → reference_analysis/data_urls/jsenv_plugin_data_urls_analysis.js} +19 -19
  20. package/src/plugins/reference_analysis/directory/jsenv_plugin_directory_reference_analysis.js +51 -0
  21. package/src/plugins/reference_analysis/html/jsenv_plugin_html_reference_analysis.js +429 -0
  22. package/src/plugins/reference_analysis/inline_content.js +7 -0
  23. package/src/plugins/reference_analysis/js/jsenv_plugin_js_reference_analysis.js +161 -0
  24. package/src/plugins/reference_analysis/jsenv_plugin_reference_analysis.js +120 -0
  25. package/src/plugins/{url_analysis → reference_analysis}/jsenv_plugin_reference_expected_types.js +19 -12
  26. package/src/plugins/{url_analysis/webmanifest/webmanifest_urls.js → reference_analysis/webmanifest/jsenv_plugin_webmanifest_reference_analysis.js} +13 -1
  27. package/src/plugins/resolution_node_esm/jsenv_plugin_node_esm_resolution.js +74 -0
  28. package/src/plugins/{url_resolution → resolution_node_esm}/node_esm_resolver.js +8 -0
  29. package/src/plugins/resolution_web/jsenv_plugin_web_resolution.js +45 -0
  30. package/src/plugins/transpilation/as_js_module/jsenv_plugin_as_js_module.js +1 -1
  31. package/src/plugins/transpilation/babel/jsenv_plugin_babel.js +1 -1
  32. package/src/plugins/transpilation/import_assertions/jsenv_plugin_import_assertions.js +4 -6
  33. package/src/plugins/transpilation/js_module_fallback/jsenv_plugin_js_module_conversion.js +1 -1
  34. package/src/plugins/transpilation/js_module_fallback/jsenv_plugin_js_module_fallback_inside_html.js +4 -6
  35. package/src/plugins/transpilation/js_module_fallback/jsenv_plugin_js_module_fallback_on_workers.js +1 -1
  36. package/src/plugins/url_type_from_reference.js +13 -0
  37. package/src/plugins/{url_version/jsenv_plugin_url_version.js → version_search_param/jsenv_plugin_version_search_param.js} +4 -4
  38. package/dist/html/explorer.html +0 -559
  39. package/dist/other/jsenv.png +0 -0
  40. package/src/plugins/explorer/client/explorer.html +0 -608
  41. package/src/plugins/explorer/client/jsenv.png +0 -0
  42. package/src/plugins/explorer/jsenv_plugin_explorer.js +0 -86
  43. package/src/plugins/inline_content_analysis/client/inline_content.js +0 -6
  44. package/src/plugins/inline_content_analysis/jsenv_plugin_html_inline_content_analysis.js +0 -206
  45. package/src/plugins/inline_content_analysis/jsenv_plugin_inline_content_analysis.js +0 -34
  46. package/src/plugins/inline_content_analysis/jsenv_plugin_js_inline_content_analysis.js +0 -314
  47. package/src/plugins/url_analysis/html/html_urls.js +0 -313
  48. package/src/plugins/url_analysis/js/js_urls.js +0 -65
  49. package/src/plugins/url_analysis/jsenv_plugin_url_analysis.js +0 -116
  50. package/src/plugins/url_resolution/jsenv_plugin_url_resolution.js +0 -140
@@ -14,7 +14,7 @@ import { Readable, Stream, Writable } from "node:stream";
14
14
  import { Http2ServerResponse } from "node:http2";
15
15
  import { lookup } from "node:dns";
16
16
  import { SOURCEMAP, generateSourcemapFileUrl, composeTwoSourcemaps, generateSourcemapDataUrl, createMagicSource, getOriginalPosition } from "@jsenv/sourcemap";
17
- import { parseHtmlString, stringifyHtmlAst, getHtmlNodeAttribute, visitHtmlNodes, analyzeScriptNode, setHtmlNodeAttributes, parseSrcSet, getHtmlNodePosition, getHtmlNodeAttributePosition, parseCssUrls, parseJsUrls, getHtmlNodeText, setHtmlNodeText, removeHtmlNodeText, applyBabelPlugins, injectHtmlNodeAsEarlyAsPossible, createHtmlNode, findHtmlNode, removeHtmlNode, injectJsImport, analyzeLinkNode, injectHtmlNode, insertHtmlNodeAfter } from "@jsenv/ast";
17
+ import { parseHtmlString, visitHtmlNodes, getHtmlNodeAttribute, analyzeScriptNode, stringifyHtmlAst, parseSrcSet, getHtmlNodeText, setHtmlNodeAttributes, getHtmlNodePosition, getHtmlNodeAttributePosition, removeHtmlNodeText, setHtmlNodeText, parseCssUrls, parseJsUrls, applyBabelPlugins, injectHtmlNodeAsEarlyAsPossible, createHtmlNode, findHtmlNode, removeHtmlNode, injectJsImport, analyzeLinkNode, injectHtmlNode, insertHtmlNodeAfter } from "@jsenv/ast";
18
18
  import { createRequire } from "node:module";
19
19
  import babelParser from "@babel/parser";
20
20
 
@@ -139,6 +139,10 @@ const generateInlineContentUrl = ({
139
139
  };
140
140
 
141
141
  // consider switching to https://babeljs.io/docs/en/babel-code-frame
142
+ // https://github.com/postcss/postcss/blob/fd30d3df5abc0954a0ec642a3cdc644ab2aacf9c/lib/css-syntax-error.js#L43
143
+ // https://github.com/postcss/postcss/blob/fd30d3df5abc0954a0ec642a3cdc644ab2aacf9c/lib/terminal-highlight.js#L50
144
+ // https://github.com/babel/babel/blob/eea156b2cb8deecfcf82d52aa1b71ba4995c7d68/packages/babel-code-frame/src/index.js#L1
145
+
142
146
  const stringifyUrlSite = ({
143
147
  url,
144
148
  line,
@@ -791,6 +795,7 @@ const getPermissionOrComputeDefault = (action, subject, permissions) => {
791
795
  * - stats object documentation on Node.js
792
796
  * https://nodejs.org/docs/latest-v13.x/api/fs.html#fs_class_fs_stats
793
797
  */
798
+
794
799
  const isWindows$3 = process.platform === "win32";
795
800
  const readEntryStat = async (source, {
796
801
  nullIfNotFound = false,
@@ -861,6 +866,7 @@ const readStat = (sourcePath, {
861
866
  * - eTag documentation on MDN
862
867
  * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
863
868
  */
869
+
864
870
  const ETAG_FOR_EMPTY_CONTENT$1 = '"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"';
865
871
  const bufferToEtag$1 = buffer => {
866
872
  if (!Buffer.isBuffer(buffer)) {
@@ -1023,6 +1029,7 @@ const removeNoop = () => {};
1023
1029
  /*
1024
1030
  * https://github.com/whatwg/dom/issues/920
1025
1031
  */
1032
+
1026
1033
  const Abort = {
1027
1034
  isAbortError: error => {
1028
1035
  return error && error.name === "AbortError";
@@ -1414,6 +1421,7 @@ const asFlatAssociations = associations => {
1414
1421
  * https://github.com/kaelzhang/node-ignore
1415
1422
  */
1416
1423
 
1424
+
1417
1425
  /** @module jsenv_url_meta **/
1418
1426
  /**
1419
1427
  * An object representing the result of applying a pattern to an url
@@ -1881,80 +1889,6 @@ const comparePathnames = (leftPathame, rightPathname) => {
1881
1889
  return 0;
1882
1890
  };
1883
1891
 
1884
- const collectFiles = async ({
1885
- signal = new AbortController().signal,
1886
- directoryUrl,
1887
- associations,
1888
- predicate
1889
- }) => {
1890
- const rootDirectoryUrl = assertAndNormalizeDirectoryUrl(directoryUrl);
1891
- if (typeof predicate !== "function") {
1892
- throw new TypeError(`predicate must be a function, got ${predicate}`);
1893
- }
1894
- associations = URL_META.resolveAssociations(associations, rootDirectoryUrl);
1895
- const collectOperation = Abort.startOperation();
1896
- collectOperation.addAbortSignal(signal);
1897
- const matchingFileResultArray = [];
1898
- const visitDirectory = async directoryUrl => {
1899
- collectOperation.throwIfAborted();
1900
- const directoryItems = await readDirectory(directoryUrl);
1901
- await Promise.all(directoryItems.map(async directoryItem => {
1902
- const directoryChildNodeUrl = `${directoryUrl}${directoryItem}`;
1903
- collectOperation.throwIfAborted();
1904
- const directoryChildNodeStats = await readEntryStat(directoryChildNodeUrl, {
1905
- // we ignore symlink because recursively traversed
1906
- // so symlinked file will be discovered.
1907
- // Moreover if they lead outside of directoryPath it can become a problem
1908
- // like infinite recursion of whatever.
1909
- // that we could handle using an object of pathname already seen but it will be useless
1910
- // because directoryPath is recursively traversed
1911
- followLink: false
1912
- });
1913
- if (directoryChildNodeStats.isDirectory()) {
1914
- const subDirectoryUrl = `${directoryChildNodeUrl}/`;
1915
- if (!URL_META.urlChildMayMatch({
1916
- url: subDirectoryUrl,
1917
- associations,
1918
- predicate
1919
- })) {
1920
- return;
1921
- }
1922
- await visitDirectory(subDirectoryUrl);
1923
- return;
1924
- }
1925
- if (directoryChildNodeStats.isFile()) {
1926
- const meta = URL_META.applyAssociations({
1927
- url: directoryChildNodeUrl,
1928
- associations
1929
- });
1930
- if (!predicate(meta)) return;
1931
- const relativeUrl = urlToRelativeUrl(directoryChildNodeUrl, rootDirectoryUrl);
1932
- matchingFileResultArray.push({
1933
- url: new URL(relativeUrl, rootDirectoryUrl).href,
1934
- relativeUrl: decodeURIComponent(relativeUrl),
1935
- meta,
1936
- fileStats: directoryChildNodeStats
1937
- });
1938
- return;
1939
- }
1940
- }));
1941
- };
1942
- try {
1943
- await visitDirectory(rootDirectoryUrl);
1944
-
1945
- // When we operate on thoose files later it feels more natural
1946
- // to perform operation in the same order they appear in the filesystem.
1947
- // It also allow to get a predictable return value.
1948
- // For that reason we sort matchingFileResultArray
1949
- matchingFileResultArray.sort((leftFile, rightFile) => {
1950
- return comparePathnames(leftFile.relativeUrl, rightFile.relativeUrl);
1951
- });
1952
- return matchingFileResultArray;
1953
- } finally {
1954
- await collectOperation.end();
1955
- }
1956
- };
1957
-
1958
1892
  // https://nodejs.org/dist/latest-v13.x/docs/api/fs.html#fs_fspromises_mkdir_path_options
1959
1893
  const {
1960
1894
  mkdir
@@ -3011,6 +2945,7 @@ function isUnicodeSupported() {
3011
2945
  }
3012
2946
 
3013
2947
  // see also https://github.com/sindresorhus/figures
2948
+
3014
2949
  const canUseUnicode = isUnicodeSupported();
3015
2950
  const COMMAND_RAW = canUseUnicode ? `❯` : `>`;
3016
2951
  const OK_RAW = canUseUnicode ? `✔` : `√`;
@@ -3428,6 +3363,7 @@ const spyStreamOutput = stream => {
3428
3363
  /*
3429
3364
  * see also https://github.com/vadimdemedes/ink
3430
3365
  */
3366
+
3431
3367
  const createLog = ({
3432
3368
  stream = process.stdout,
3433
3369
  newLine = "after"
@@ -4022,6 +3958,7 @@ const listenEvent = (objectWithEventEmitter, eventName, callback, {
4022
3958
  https://stackoverflow.com/a/42019773/2634179
4023
3959
 
4024
3960
  */
3961
+
4025
3962
  const createPolyglotServer = async ({
4026
3963
  http2 = false,
4027
3964
  http1Allowed = true,
@@ -4481,6 +4418,7 @@ const normalizeHeaderValue = headerValue => {
4481
4418
  https://developer.mozilla.org/en-US/docs/Web/API/Headers
4482
4419
  https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
4483
4420
  */
4421
+
4484
4422
  const headersFromObject = headersObject => {
4485
4423
  const headers = {};
4486
4424
  Object.keys(headersObject).forEach(headerName => {
@@ -6769,6 +6707,7 @@ const serveDirectory = (url, {
6769
6707
  * { status: 200, body: "Hello world" }.
6770
6708
  * It is meant to be used inside "requestToResponse"
6771
6709
  */
6710
+
6772
6711
  const fetchFileSystem = async (filesystemUrl, {
6773
6712
  // signal,
6774
6713
  method = "GET",
@@ -7856,7 +7795,7 @@ const createUrlInfo = url => {
7856
7795
  dependents: new Set(),
7857
7796
  implicitUrls: new Set(),
7858
7797
  type: undefined,
7859
- // "html", "css", "js_classic", "js_module", "importmap", "json", "webmanifest", ...
7798
+ // "html", "css", "js_classic", "js_module", "importmap", "sourcemap", "json", "webmanifest", ...
7860
7799
  subtype: undefined,
7861
7800
  // "worker", "service_worker", "shared_worker" for js, otherwise undefined
7862
7801
  typeHint: undefined,
@@ -7891,7 +7830,7 @@ const createUrlInfo = url => {
7891
7830
 
7892
7831
  const HOOK_NAMES = ["init", "serve",
7893
7832
  // is called only during dev/tests
7894
- "resolveUrl", "redirectUrl", "fetchUrlContent", "transformUrlContent", "transformUrlSearchParams", "formatUrl", "finalizeUrlContent", "bundle",
7833
+ "resolveReference", "redirectReference", "transformReferenceSearchParams", "formatReference", "fetchUrlContent", "transformUrlContent", "finalizeUrlContent", "bundle",
7895
7834
  // is called only during build
7896
7835
  "optimizeUrlContent",
7897
7836
  // is called only during build
@@ -7905,8 +7844,19 @@ const createPluginController = kitchenContext => {
7905
7844
  // also it should increase perf as there is less work to do
7906
7845
  const hookGroups = {};
7907
7846
  const addPlugin = (plugin, {
7908
- position = "start"
7847
+ position = "end"
7909
7848
  }) => {
7849
+ if (Array.isArray(plugin)) {
7850
+ if (position === "start") {
7851
+ plugin = plugin.slice().reverse();
7852
+ }
7853
+ plugin.forEach(plugin => {
7854
+ addPlugin(plugin, {
7855
+ position
7856
+ });
7857
+ });
7858
+ return;
7859
+ }
7910
7860
  if (plugin === null || typeof plugin !== "object") {
7911
7861
  throw new TypeError(`plugin must be objects, got ${plugin}`);
7912
7862
  }
@@ -7938,9 +7888,9 @@ const createPluginController = kitchenContext => {
7938
7888
  value: hookValue
7939
7889
  };
7940
7890
  if (position === "start") {
7941
- group.push(hook);
7942
- } else {
7943
7891
  group.unshift(hook);
7892
+ } else {
7893
+ group.push(hook);
7944
7894
  }
7945
7895
  }
7946
7896
  });
@@ -7993,12 +7943,12 @@ const createPluginController = kitchenContext => {
7993
7943
  };
7994
7944
  const pushPlugin = plugin => {
7995
7945
  addPlugin(plugin, {
7996
- position: "start"
7946
+ position: "end"
7997
7947
  });
7998
7948
  };
7999
7949
  const unshiftPlugin = plugin => {
8000
7950
  addPlugin(plugin, {
8001
- position: "end"
7951
+ position: "start"
8002
7952
  });
8003
7953
  };
8004
7954
  let lastPluginUsed = null;
@@ -8155,7 +8105,7 @@ const assertAndNormalizeReturnValue = (hookName, returnValue) => {
8155
8105
  };
8156
8106
  const returnValueAssertions = [{
8157
8107
  name: "url_assertion",
8158
- appliesTo: ["resolveUrl", "redirectUrl"],
8108
+ appliesTo: ["resolveReference", "redirectReference"],
8159
8109
  assertion: valueReturned => {
8160
8110
  if (valueReturned instanceof URL) {
8161
8111
  return valueReturned.href;
@@ -9151,6 +9101,7 @@ const createKitchen = ({
9151
9101
  signal,
9152
9102
  logLevel,
9153
9103
  rootDirectoryUrl,
9104
+ mainFilePath,
9154
9105
  urlGraph,
9155
9106
  dev = false,
9156
9107
  build = false,
@@ -9175,6 +9126,7 @@ const createKitchen = ({
9175
9126
  signal,
9176
9127
  logger,
9177
9128
  rootDirectoryUrl,
9129
+ mainFilePath,
9178
9130
  urlGraph,
9179
9131
  dev,
9180
9132
  build,
@@ -9192,16 +9144,34 @@ const createKitchen = ({
9192
9144
  outDirectoryUrl
9193
9145
  };
9194
9146
  const pluginController = createPluginController(kitchenContext);
9195
- const pushPlugins = plugins => {
9196
- plugins.forEach(pluginEntry => {
9197
- if (Array.isArray(pluginEntry)) {
9198
- pushPlugins(pluginEntry);
9199
- } else {
9200
- pluginController.pushPlugin(pluginEntry);
9201
- }
9202
- });
9203
- };
9204
- pushPlugins(plugins);
9147
+ plugins.forEach(pluginEntry => {
9148
+ pluginController.pushPlugin(pluginEntry);
9149
+ });
9150
+
9151
+ /*
9152
+ * - "http_request"
9153
+ * - "entry_point"
9154
+ * - "link_href"
9155
+ * - "style"
9156
+ * - "script"
9157
+ * - "a_href"
9158
+ * - "iframe_src
9159
+ * - "img_src"
9160
+ * - "img_srcset"
9161
+ * - "source_src"
9162
+ * - "source_srcset"
9163
+ * - "image_href"
9164
+ * - "use_href"
9165
+ * - "css_@import"
9166
+ * - "css_url"
9167
+ * - "js_import"
9168
+ * - "js_import_script"
9169
+ * - "js_url"
9170
+ * - "js_inline_content"
9171
+ * - "sourcemap_comment"
9172
+ * - "webmanifest_icon_src"
9173
+ * - "package_json"
9174
+ * */
9205
9175
  const createReference = ({
9206
9176
  data = {},
9207
9177
  node,
@@ -9306,14 +9276,14 @@ const createKitchen = ({
9306
9276
  resolveReference: (reference, context = referenceContext) => resolveReference(reference, context)
9307
9277
  };
9308
9278
  try {
9309
- let resolvedUrl = pluginController.callHooksUntil("resolveUrl", reference, referenceContext);
9310
- if (!resolvedUrl) {
9279
+ let url = pluginController.callHooksUntil("resolveReference", reference, referenceContext);
9280
+ if (!url) {
9311
9281
  throw new Error(`NO_RESOLVE`);
9312
9282
  }
9313
- if (resolvedUrl.includes("?debug")) {
9283
+ if (url.includes("?debug")) {
9314
9284
  reference.debug = true;
9315
9285
  }
9316
- resolvedUrl = normalizeUrl(resolvedUrl);
9286
+ url = normalizeUrl(url);
9317
9287
  let referencedUrlObject;
9318
9288
  let searchParams;
9319
9289
  const onReferenceUrlChange = referenceUrl => {
@@ -9322,14 +9292,14 @@ const createKitchen = ({
9322
9292
  reference.url = referenceUrl;
9323
9293
  reference.searchParams = searchParams;
9324
9294
  };
9325
- onReferenceUrlChange(resolvedUrl);
9295
+ onReferenceUrlChange(url);
9326
9296
  if (reference.debug) {
9327
9297
  logger.debug(`url resolved by "${pluginController.getLastPluginUsed().name}"
9328
9298
  ${ANSI.color(reference.specifier, ANSI.GREY)} ->
9329
9299
  ${ANSI.color(reference.url, ANSI.YELLOW)}
9330
9300
  `);
9331
9301
  }
9332
- pluginController.callHooks("redirectUrl", reference, referenceContext, (returnValue, plugin) => {
9302
+ pluginController.callHooks("redirectReference", reference, referenceContext, (returnValue, plugin) => {
9333
9303
  const normalizedReturnValue = normalizeUrl(returnValue);
9334
9304
  if (normalizedReturnValue === reference.url) {
9335
9305
  return;
@@ -9356,13 +9326,13 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
9356
9326
  // - convey information (?hmr)
9357
9327
  // But do not represent an other resource, it is considered as
9358
9328
  // the same resource under the hood
9359
- pluginController.callHooks("transformUrlSearchParams", reference, referenceContext, returnValue => {
9329
+ pluginController.callHooks("transformReferenceSearchParams", reference, referenceContext, returnValue => {
9360
9330
  Object.keys(returnValue).forEach(key => {
9361
9331
  searchParams.set(key, returnValue[key]);
9362
9332
  });
9363
9333
  reference.generatedUrl = normalizeUrl(referencedUrlObject.href);
9364
9334
  });
9365
- const returnValue = pluginController.callHooksUntil("formatUrl", reference, referenceContext);
9335
+ const returnValue = pluginController.callHooksUntil("formatReference", reference, referenceContext);
9366
9336
  reference.generatedSpecifier = returnValue || reference.generatedUrl;
9367
9337
  reference.generatedSpecifier = urlSpecifierEncoding.encode(reference);
9368
9338
  return [reference, urlInfo];
@@ -10259,7 +10229,7 @@ const createRepartitionMessage = ({
10259
10229
  };
10260
10230
 
10261
10231
  const jsenvPluginReferenceExpectedTypes = () => {
10262
- const redirectJsUrls = reference => {
10232
+ const redirectJsReference = reference => {
10263
10233
  const urlObject = new URL(reference.url);
10264
10234
  const {
10265
10235
  searchParams
@@ -10269,16 +10239,23 @@ const jsenvPluginReferenceExpectedTypes = () => {
10269
10239
  }
10270
10240
  if (searchParams.has("js_classic")) {
10271
10241
  reference.expectedType = "js_classic";
10272
- } else if (searchParams.has("js_module_fallback") || searchParams.has("as_js_classic")) {
10242
+ } else if (searchParams.has("js_module")) {
10243
+ reference.expectedType = "js_module";
10244
+ }
10245
+ // we need to keep these checks here because during versioning:
10246
+ // - only reference anlysis plugin is executed
10247
+ // -> plugin about js transpilation don't apply and can't set expectedType: 'js_classic'
10248
+ // - query params like ?js_module_fallback are still there
10249
+ // - without this check build would throw as reference could expect js module and find js classic
10250
+ else if (searchParams.has("js_module_fallback") || searchParams.has("as_js_classic")) {
10273
10251
  reference.expectedType = "js_classic";
10274
10252
  } else if (searchParams.has("as_js_module")) {
10275
10253
  reference.expectedType = "js_module";
10276
- } else if (searchParams.has("js_module")) {
10277
- reference.expectedType = "js_module";
10278
- } else if (reference.type === "js_url" && reference.expectedType === undefined && CONTENT_TYPE.fromUrlExtension(reference.url) === "text/javascript") {
10279
- // by default, js referenced by new URL is considered as "js_module"
10280
- // in case this is not desired code must use "?js_classic" like
10281
- // new URL('./file.js?js_classic', import.meta.url)
10254
+ }
10255
+ // by default, js referenced by new URL is considered as "js_module"
10256
+ // in case this is not desired code must use "?js_classic" like
10257
+ // new URL('./file.js?js_classic', import.meta.url)
10258
+ else if (reference.type === "js_url" && reference.expectedType === undefined && CONTENT_TYPE.fromUrlExtension(reference.url) === "text/javascript") {
10282
10259
  reference.expectedType = "js_module";
10283
10260
  }
10284
10261
  if (searchParams.has("worker")) {
@@ -10293,40 +10270,175 @@ const jsenvPluginReferenceExpectedTypes = () => {
10293
10270
  return {
10294
10271
  name: "jsenv:reference_expected_types",
10295
10272
  appliesDuring: "*",
10296
- redirectUrl: {
10297
- script: redirectJsUrls,
10298
- js_url: redirectJsUrls,
10299
- js_import: redirectJsUrls
10273
+ redirectReference: {
10274
+ script: redirectJsReference,
10275
+ js_url: redirectJsReference,
10276
+ js_import: redirectJsReference
10277
+ }
10278
+ };
10279
+ };
10280
+
10281
+ const jsenvPluginDirectoryReferenceAnalysis = () => {
10282
+ return {
10283
+ name: "jsenv:directory_reference_analysis",
10284
+ transformUrlContent: {
10285
+ directory: (urlInfo, context) => {
10286
+ const originalDirectoryReference = findOriginalDirectoryReference(urlInfo, context);
10287
+ const directoryRelativeUrl = urlToRelativeUrl(urlInfo.url, context.rootDirectoryUrl);
10288
+ JSON.parse(urlInfo.content).forEach(directoryEntryName => {
10289
+ context.referenceUtils.found({
10290
+ type: "filesystem",
10291
+ subtype: "directory_entry",
10292
+ specifier: directoryEntryName,
10293
+ trace: {
10294
+ message: `"${directoryRelativeUrl}${directoryEntryName}" entry in directory referenced by ${originalDirectoryReference.trace.message}`
10295
+ }
10296
+ });
10297
+ });
10298
+ }
10299
+ }
10300
+ };
10301
+ };
10302
+ const findOriginalDirectoryReference = (urlInfo, context) => {
10303
+ const findNonFileSystemAncestor = urlInfo => {
10304
+ for (const dependentUrl of urlInfo.dependents) {
10305
+ const dependentUrlInfo = context.urlGraph.getUrlInfo(dependentUrl);
10306
+ if (dependentUrlInfo.type !== "directory") {
10307
+ return [dependentUrlInfo, urlInfo];
10308
+ }
10309
+ const found = findNonFileSystemAncestor(dependentUrlInfo);
10310
+ if (found) {
10311
+ return found;
10312
+ }
10313
+ }
10314
+ return [];
10315
+ };
10316
+ const [ancestor, child] = findNonFileSystemAncestor(urlInfo);
10317
+ if (!ancestor) {
10318
+ return null;
10319
+ }
10320
+ const ref = ancestor.references.find(ref => ref.url === child.url);
10321
+ return ref;
10322
+ };
10323
+
10324
+ const jsenvPluginDataUrlsAnalysis = () => {
10325
+ return {
10326
+ name: "jsenv:data_urls_analysis",
10327
+ appliesDuring: "*",
10328
+ resolveReference: reference => {
10329
+ if (!reference.specifier.startsWith("data:")) {
10330
+ return null;
10331
+ }
10332
+ return reference.specifier;
10333
+ },
10334
+ formatReference: (reference, context) => {
10335
+ if (!reference.generatedUrl.startsWith("data:")) {
10336
+ return null;
10337
+ }
10338
+ if (reference.type === "sourcemap_comment") {
10339
+ return null;
10340
+ }
10341
+ return (async () => {
10342
+ const urlInfo = context.urlGraph.getUrlInfo(reference.url);
10343
+ await context.cook(urlInfo, {
10344
+ reference
10345
+ });
10346
+ if (urlInfo.originalContent === urlInfo.content) {
10347
+ return reference.generatedUrl;
10348
+ }
10349
+ const specifier = DATA_URL.stringify({
10350
+ contentType: urlInfo.contentType,
10351
+ base64Flag: urlInfo.data.base64Flag,
10352
+ data: urlInfo.data.base64Flag ? dataToBase64(urlInfo.content) : String(urlInfo.content)
10353
+ });
10354
+ return specifier;
10355
+ })();
10356
+ },
10357
+ fetchUrlContent: urlInfo => {
10358
+ if (!urlInfo.url.startsWith("data:")) {
10359
+ return null;
10360
+ }
10361
+ const {
10362
+ contentType,
10363
+ base64Flag,
10364
+ data: urlData
10365
+ } = DATA_URL.parse(urlInfo.url);
10366
+ urlInfo.data.base64Flag = base64Flag;
10367
+ return {
10368
+ content: contentFromUrlData({
10369
+ contentType,
10370
+ base64Flag,
10371
+ urlData
10372
+ }),
10373
+ contentType
10374
+ };
10300
10375
  }
10301
10376
  };
10302
10377
  };
10378
+ const contentFromUrlData = ({
10379
+ contentType,
10380
+ base64Flag,
10381
+ urlData
10382
+ }) => {
10383
+ if (CONTENT_TYPE.isTextual(contentType)) {
10384
+ if (base64Flag) {
10385
+ return base64ToString(urlData);
10386
+ }
10387
+ return urlData;
10388
+ }
10389
+ if (base64Flag) {
10390
+ return base64ToBuffer(urlData);
10391
+ }
10392
+ return Buffer.from(urlData);
10393
+ };
10394
+ const base64ToBuffer = base64String => Buffer.from(base64String, "base64");
10395
+ const base64ToString = base64String => Buffer.from(base64String, "base64").toString("utf8");
10396
+ const dataToBase64 = data => Buffer.from(data).toString("base64");
10303
10397
 
10304
- const parseAndTransformHtmlUrls = async (urlInfo, context) => {
10398
+ const jsenvPluginHtmlReferenceAnalysis = ({
10399
+ inlineContent,
10400
+ inlineConvertedScript
10401
+ }) => {
10402
+ return {
10403
+ name: "jsenv:html_reference_analysis",
10404
+ appliesDuring: "*",
10405
+ transformUrlContent: {
10406
+ html: (urlInfo, context) => parseAndTransformHtmlReferences(urlInfo, context, {
10407
+ inlineContent,
10408
+ inlineConvertedScript
10409
+ })
10410
+ }
10411
+ };
10412
+ };
10413
+ const parseAndTransformHtmlReferences = async (urlInfo, context, {
10414
+ inlineContent,
10415
+ inlineConvertedScript
10416
+ }) => {
10305
10417
  const url = urlInfo.originalUrl;
10306
10418
  const content = urlInfo.content;
10307
- const htmlAst = parseHtmlString(content, {
10308
- storeOriginalPositions: context.dev
10309
- });
10310
- const mentions = visitHtmlUrls({
10311
- url,
10312
- htmlAst
10313
- });
10419
+ const htmlAst = parseHtmlString(content);
10314
10420
  const mutations = [];
10315
10421
  const actions = [];
10316
- for (const mention of mentions) {
10422
+ const finalizeCallbacks = [];
10423
+ const createExternalReference = (node, attributeName, attributeValue, {
10424
+ type,
10425
+ subtype,
10426
+ expectedType
10427
+ }) => {
10428
+ let position;
10429
+ if (getHtmlNodeAttribute(node, "jsenv-cooked-by")) {
10430
+ // when generated from inline content,
10431
+ // line, column is not "src" nor "inlined-from-src" but "original-position"
10432
+ position = getHtmlNodePosition(node);
10433
+ } else {
10434
+ position = getHtmlNodeAttributePosition(node, attributeName);
10435
+ }
10317
10436
  const {
10318
- type,
10319
- subtype,
10320
- expectedType,
10321
10437
  line,
10322
- column,
10323
- originalLine,
10324
- originalColumn,
10325
- node,
10326
- attributeName,
10327
- debug,
10328
- specifier
10329
- } = mention;
10438
+ column
10439
+ // originalLine, originalColumn
10440
+ } = position;
10441
+ const debug = getHtmlNodeAttribute(node, "jsenv-debug") !== undefined;
10330
10442
  const {
10331
10443
  crossorigin,
10332
10444
  integrity
@@ -10336,9 +10448,7 @@ const parseAndTransformHtmlUrls = async (urlInfo, context) => {
10336
10448
  type,
10337
10449
  subtype,
10338
10450
  expectedType,
10339
- originalLine,
10340
- originalColumn,
10341
- specifier,
10451
+ specifier: attributeValue,
10342
10452
  specifierLine: line,
10343
10453
  specifierColumn: column,
10344
10454
  isResourceHint,
@@ -10354,221 +10464,283 @@ const parseAndTransformHtmlUrls = async (urlInfo, context) => {
10354
10464
  });
10355
10465
  });
10356
10466
  });
10357
- }
10358
- if (actions.length > 0) {
10359
- await Promise.all(actions.map(action => action()));
10360
- }
10361
- if (mutations.length === 0) {
10467
+ };
10468
+ const visitHref = (node, referenceProps) => {
10469
+ const href = getHtmlNodeAttribute(node, "href");
10470
+ if (href) {
10471
+ return createExternalReference(node, "href", href, referenceProps);
10472
+ }
10473
+ const inlinedFromHref = getHtmlNodeAttribute(node, "inlined-from-href");
10474
+ if (inlinedFromHref) {
10475
+ return createExternalReference(node, "inlined-from-href", new URL(inlinedFromHref, url).href, referenceProps);
10476
+ }
10362
10477
  return null;
10363
- }
10364
- mutations.forEach(mutation => mutation());
10365
- return stringifyHtmlAst(htmlAst);
10366
- };
10367
- const crossOriginCompatibleTagNames = ["script", "link", "img", "source"];
10368
- const integrityCompatibleTagNames = ["script", "link", "img", "source"];
10369
- const readFetchMetas = node => {
10370
- const meta = {};
10371
- if (crossOriginCompatibleTagNames.includes(node.nodeName)) {
10372
- const crossorigin = getHtmlNodeAttribute(node, "crossorigin") !== undefined;
10373
- meta.crossorigin = crossorigin;
10374
- }
10375
- if (integrityCompatibleTagNames.includes(node.nodeName)) {
10376
- const integrity = getHtmlNodeAttribute(node, "integrity");
10377
- meta.integrity = integrity;
10378
- }
10379
- return meta;
10380
- };
10381
- const visitHtmlUrls = ({
10382
- url,
10383
- htmlAst
10384
- }) => {
10385
- const mentions = [];
10386
- const finalizeCallbacks = [];
10387
- const addMention = ({
10478
+ };
10479
+ const visitSrc = (node, referenceProps) => {
10480
+ const src = getHtmlNodeAttribute(node, "src");
10481
+ if (src) {
10482
+ return createExternalReference(node, "src", src, referenceProps);
10483
+ }
10484
+ const inlinedFromSrc = getHtmlNodeAttribute(node, "inlined-from-src");
10485
+ if (inlinedFromSrc) {
10486
+ return createExternalReference(node, "inlined-from-src", new URL(inlinedFromSrc, url).href, referenceProps);
10487
+ }
10488
+ return null;
10489
+ };
10490
+ const visitSrcset = (node, referenceProps) => {
10491
+ const srcset = getHtmlNodeAttribute(node, "srcset");
10492
+ if (srcset) {
10493
+ const srcCandidates = parseSrcSet(srcset);
10494
+ return srcCandidates.map(srcCandidate => {
10495
+ return createExternalReference(node, "srcset", srcCandidate.specifier, referenceProps);
10496
+ });
10497
+ }
10498
+ return null;
10499
+ };
10500
+ const createInlineReference = (node, inlineContent, {
10501
+ extension,
10388
10502
  type,
10389
- subtype,
10390
10503
  expectedType,
10391
- node,
10392
- attributeName,
10393
- specifier
10504
+ contentType
10394
10505
  }) => {
10395
- let position;
10396
- if (getHtmlNodeAttribute(node, "jsenv-cooked-by")) {
10397
- // when generated from inline content,
10398
- // line, column is not "src" nor "inlined-from-src" but "original-position"
10399
- position = getHtmlNodePosition(node);
10400
- } else {
10401
- position = getHtmlNodeAttributePosition(node, attributeName);
10402
- }
10506
+ const hotAccept = getHtmlNodeAttribute(node, "hot-accept") !== undefined;
10403
10507
  const {
10404
10508
  line,
10405
- column
10406
- // originalLine, originalColumn
10407
- } = position;
10408
- const debug = getHtmlNodeAttribute(node, "jsenv-debug") !== undefined;
10409
- const mention = {
10410
- type,
10411
- subtype,
10412
- expectedType,
10509
+ column,
10510
+ lineEnd,
10511
+ columnEnd,
10512
+ isOriginal
10513
+ } = getHtmlNodePosition(node, {
10514
+ preferOriginal: true
10515
+ });
10516
+ const inlineContentUrl = generateInlineContentUrl({
10517
+ url: urlInfo.url,
10518
+ extension,
10413
10519
  line,
10414
10520
  column,
10415
- // originalLine, originalColumn
10416
- specifier,
10521
+ lineEnd,
10522
+ columnEnd
10523
+ });
10524
+ const debug = getHtmlNodeAttribute(node, "jsenv-debug") !== undefined;
10525
+ const [inlineReference, inlineUrlInfo] = context.referenceUtils.foundInline({
10417
10526
  node,
10418
- attributeName,
10527
+ type,
10528
+ expectedType,
10529
+ isOriginalPosition: isOriginal,
10530
+ // we remove 1 to the line because imagine the following html:
10531
+ // <style>body { color: red; }</style>
10532
+ // -> content starts same line as <style> (same for <script>)
10533
+ specifierLine: line - 1,
10534
+ specifierColumn: column,
10535
+ specifier: inlineContentUrl,
10536
+ contentType,
10537
+ content: inlineContent,
10419
10538
  debug
10420
- };
10421
- mentions.push(mention);
10422
- return mention;
10423
- };
10424
- const visitAttributeAsUrlSpecifier = ({
10425
- node,
10426
- attributeName,
10427
- ...rest
10428
- }) => {
10429
- const value = getHtmlNodeAttribute(node, attributeName);
10430
- if (value) {
10431
- if (getHtmlNodeAttribute(node, "jsenv-inlined-by") === "jsenv:importmap") {
10432
- // during build the importmap is inlined
10433
- // and shoud not be considered as a dependency anymore
10434
- return null;
10435
- }
10436
- return addMention({
10437
- ...rest,
10438
- node,
10439
- attributeName,
10440
- specifier: attributeName === "inlined-from-src" || attributeName === "inlined-from-href" ? new URL(value, url).href : value
10441
- });
10442
- }
10443
- if (attributeName === "src") {
10444
- return visitAttributeAsUrlSpecifier({
10445
- ...rest,
10446
- node,
10447
- attributeName: "inlined-from-src"
10539
+ });
10540
+ actions.push(async () => {
10541
+ await cookInlineContent({
10542
+ context,
10543
+ inlineContentUrlInfo: inlineUrlInfo,
10544
+ inlineContentReference: inlineReference
10448
10545
  });
10449
- }
10450
- if (attributeName === "href") {
10451
- return visitAttributeAsUrlSpecifier({
10452
- ...rest,
10453
- node,
10454
- attributeName: "inlined-from-href"
10546
+ mutations.push(() => {
10547
+ if (hotAccept) {
10548
+ removeHtmlNodeText(node);
10549
+ setHtmlNodeAttributes(node, {
10550
+ "jsenv-cooked-by": "jsenv:html_inline_content_analysis"
10551
+ });
10552
+ } else {
10553
+ setHtmlNodeText(node, inlineUrlInfo.content, {
10554
+ indentation: false // indentation would decrease stack trace precision
10555
+ });
10556
+
10557
+ setHtmlNodeAttributes(node, {
10558
+ "jsenv-cooked-by": "jsenv:html_inline_content_analysis"
10559
+ });
10560
+ }
10455
10561
  });
10456
- }
10457
- return null;
10562
+ });
10563
+ return inlineReference;
10458
10564
  };
10459
- const visitSrcset = ({
10565
+ const visitTextContent = (node, {
10566
+ extension,
10460
10567
  type,
10461
- node
10568
+ expectedType,
10569
+ contentType
10462
10570
  }) => {
10463
- const srcset = getHtmlNodeAttribute(node, "srcset");
10464
- if (srcset) {
10465
- const srcCandidates = parseSrcSet(srcset);
10466
- srcCandidates.forEach(srcCandidate => {
10467
- addMention({
10468
- type,
10469
- node,
10470
- attributeName: "srcset",
10471
- specifier: srcCandidate.specifier
10472
- });
10473
- });
10571
+ const inlineContent = getHtmlNodeText(node);
10572
+ if (!inlineContent) {
10573
+ return null;
10474
10574
  }
10575
+ return createInlineReference(node, inlineContent, {
10576
+ extension,
10577
+ type,
10578
+ expectedType,
10579
+ contentType
10580
+ });
10475
10581
  };
10476
10582
  visitHtmlNodes(htmlAst, {
10477
- link: node => {
10478
- const rel = getHtmlNodeAttribute(node, "rel");
10479
- const type = getHtmlNodeAttribute(node, "type");
10480
- const mention = visitAttributeAsUrlSpecifier({
10583
+ link: linkNode => {
10584
+ const rel = getHtmlNodeAttribute(linkNode, "rel");
10585
+ const type = getHtmlNodeAttribute(linkNode, "type");
10586
+ const ref = visitHref(linkNode, {
10481
10587
  type: "link_href",
10482
10588
  subtype: rel,
10483
- node,
10484
- attributeName: "href",
10485
10589
  // https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload#including_a_mime_type
10486
10590
  expectedContentType: type
10487
10591
  });
10488
- if (mention) {
10592
+ if (ref) {
10489
10593
  finalizeCallbacks.push(() => {
10490
- mention.expectedType = decideLinkExpectedType(mention, mentions);
10594
+ ref.expectedType = decideLinkExpectedType(ref, context);
10491
10595
  });
10492
10596
  }
10493
10597
  },
10494
- // style: () => {},
10495
- script: node => {
10598
+ style: inlineContent ? styleNode => {
10599
+ visitTextContent(styleNode, {
10600
+ extension: ".css",
10601
+ type: "style",
10602
+ expectedType: "css",
10603
+ contentType: "text/css"
10604
+ });
10605
+ } : null,
10606
+ script: scriptNode => {
10607
+ // during build the importmap is inlined
10608
+ // and shoud not be considered as a dependency anymore
10609
+ if (getHtmlNodeAttribute(scriptNode, "jsenv-inlined-by") === "jsenv:importmap") {
10610
+ return;
10611
+ }
10496
10612
  const {
10497
- type
10498
- } = analyzeScriptNode(node);
10499
- if (type === "text") {
10500
- // ignore <script type="whatever" src="./file.js">
10501
- // per HTML spec https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-type
10502
- // this will be handled by jsenv_plugin_html_inline_content_analysis
10613
+ type,
10614
+ contentType,
10615
+ extension
10616
+ } = analyzeScriptNode(scriptNode);
10617
+ // ignore <script type="whatever">foobar</script>
10618
+ // per HTML spec https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-type
10619
+ if (type !== "text") {
10620
+ const externalRef = visitSrc(scriptNode, {
10621
+ type: "script",
10622
+ subtype: type,
10623
+ expectedType: type
10624
+ });
10625
+ if (externalRef) {
10626
+ return;
10627
+ }
10628
+ }
10629
+
10630
+ // now visit the content, if any
10631
+ if (!inlineContent) {
10503
10632
  return;
10504
10633
  }
10505
- visitAttributeAsUrlSpecifier({
10634
+ // If the inline script was already handled by an other plugin, ignore it
10635
+ // - we want to preserve inline scripts generated by html supervisor during dev
10636
+ // - we want to avoid cooking twice a script during build
10637
+ if (!inlineConvertedScript && getHtmlNodeAttribute(scriptNode, "jsenv-injected-by") === "jsenv:js_module_fallback") {
10638
+ return;
10639
+ }
10640
+ const inlineRef = visitTextContent(scriptNode, {
10641
+ extension: extension || CONTENT_TYPE.asFileExtension(contentType),
10506
10642
  type: "script",
10507
- subtype: type,
10508
10643
  expectedType: type,
10509
- node,
10510
- attributeName: "src"
10644
+ contentType
10511
10645
  });
10646
+ if (inlineRef && extension) {
10647
+ // 1. <script type="jsx"> becomes <script>
10648
+ // 2. <script type="module/jsx"> becomes <script type="module">
10649
+ mutations.push(() => {
10650
+ setHtmlNodeAttributes(scriptNode, {
10651
+ type: type === "js_module" ? "module" : undefined
10652
+ });
10653
+ });
10654
+ }
10512
10655
  },
10513
- a: node => {
10514
- visitAttributeAsUrlSpecifier({
10515
- type: "a_href",
10516
- node,
10517
- attributeName: "href"
10656
+ a: aNode => {
10657
+ visitHref(aNode, {
10658
+ type: "a_href"
10518
10659
  });
10519
10660
  },
10520
- iframe: node => {
10521
- visitAttributeAsUrlSpecifier({
10522
- type: "iframe_src",
10523
- node,
10524
- attributeName: "src"
10661
+ iframe: iframeNode => {
10662
+ visitSrc(iframeNode, {
10663
+ type: "iframe_src"
10525
10664
  });
10526
10665
  },
10527
- img: node => {
10528
- visitAttributeAsUrlSpecifier({
10529
- type: "img_src",
10530
- node,
10531
- attributeName: "src"
10666
+ img: imgNode => {
10667
+ visitSrc(imgNode, {
10668
+ type: "img_src"
10532
10669
  });
10533
- visitSrcset({
10534
- type: "img_srcset",
10535
- node
10670
+ visitSrcset(imgNode, {
10671
+ type: "img_srcset"
10536
10672
  });
10537
10673
  },
10538
- source: node => {
10539
- visitAttributeAsUrlSpecifier({
10540
- type: "source_src",
10541
- node,
10542
- attributeName: "src"
10674
+ source: sourceNode => {
10675
+ visitSrc(sourceNode, {
10676
+ type: "source_src"
10543
10677
  });
10544
- visitSrcset({
10545
- type: "source_srcset",
10546
- node
10678
+ visitSrcset(sourceNode, {
10679
+ type: "source_srcset"
10547
10680
  });
10548
10681
  },
10549
10682
  // svg <image> tag
10550
- image: node => {
10551
- visitAttributeAsUrlSpecifier({
10552
- type: "image_href",
10553
- node,
10554
- attributeName: "href"
10683
+ image: imageNode => {
10684
+ visitHref(imageNode, {
10685
+ type: "image_href"
10555
10686
  });
10556
10687
  },
10557
- use: node => {
10558
- visitAttributeAsUrlSpecifier({
10559
- type: "use_href",
10560
- node,
10561
- attributeName: "href"
10688
+ use: useNode => {
10689
+ visitHref(useNode, {
10690
+ type: "use_href"
10562
10691
  });
10563
10692
  }
10564
10693
  });
10565
10694
  finalizeCallbacks.forEach(finalizeCallback => {
10566
10695
  finalizeCallback();
10567
10696
  });
10568
- return mentions;
10697
+ if (actions.length > 0) {
10698
+ await Promise.all(actions.map(action => action()));
10699
+ }
10700
+ if (mutations.length === 0) {
10701
+ return null;
10702
+ }
10703
+ mutations.forEach(mutation => mutation());
10704
+ return stringifyHtmlAst(htmlAst);
10705
+ };
10706
+ const cookInlineContent = async ({
10707
+ context,
10708
+ inlineContentUrlInfo,
10709
+ inlineContentReference
10710
+ }) => {
10711
+ try {
10712
+ await context.cook(inlineContentUrlInfo, {
10713
+ reference: inlineContentReference
10714
+ });
10715
+ } catch (e) {
10716
+ if (e.code === "PARSE_ERROR") {
10717
+ // When something like <style> or <script> contains syntax error
10718
+ // the HTML in itself it still valid
10719
+ // keep the syntax error and continue with the HTML
10720
+ const messageStart = inlineContentUrlInfo.type === "css" ? `Syntax error on css declared inside <style>` : `Syntax error on js declared inside <script>`;
10721
+ context.logger.error(`${messageStart}: ${e.cause.reasonCode}
10722
+ ${e.traceMessage}`);
10723
+ } else {
10724
+ throw e;
10725
+ }
10726
+ }
10727
+ };
10728
+ const crossOriginCompatibleTagNames = ["script", "link", "img", "source"];
10729
+ const integrityCompatibleTagNames = ["script", "link", "img", "source"];
10730
+ const readFetchMetas = node => {
10731
+ const meta = {};
10732
+ if (crossOriginCompatibleTagNames.includes(node.nodeName)) {
10733
+ const crossorigin = getHtmlNodeAttribute(node, "crossorigin") !== undefined;
10734
+ meta.crossorigin = crossorigin;
10735
+ }
10736
+ if (integrityCompatibleTagNames.includes(node.nodeName)) {
10737
+ const integrity = getHtmlNodeAttribute(node, "integrity");
10738
+ meta.integrity = integrity;
10739
+ }
10740
+ return meta;
10569
10741
  };
10570
- const decideLinkExpectedType = (linkMention, mentions) => {
10571
- const rel = getHtmlNodeAttribute(linkMention.node, "rel");
10742
+ const decideLinkExpectedType = (linkReference, context) => {
10743
+ const rel = getHtmlNodeAttribute(linkReference.node, "rel");
10572
10744
  if (rel === "webmanifest") {
10573
10745
  return "webmanifest";
10574
10746
  }
@@ -10580,7 +10752,7 @@ const decideLinkExpectedType = (linkMention, mentions) => {
10580
10752
  }
10581
10753
  if (rel === "preload") {
10582
10754
  // https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload#what_types_of_content_can_be_preloaded
10583
- const as = getHtmlNodeAttribute(linkMention.node, "as");
10755
+ const as = getHtmlNodeAttribute(linkReference.node, "as");
10584
10756
  if (as === "document") {
10585
10757
  return "html";
10586
10758
  }
@@ -10588,7 +10760,7 @@ const decideLinkExpectedType = (linkMention, mentions) => {
10588
10760
  return "css";
10589
10761
  }
10590
10762
  if (as === "script") {
10591
- const firstScriptOnThisUrl = mentions.find(mentionCandidate => mentionCandidate.url === linkMention.url && mentionCandidate.type === "script");
10763
+ const firstScriptOnThisUrl = context.referenceUtils.find(refCandidate => refCandidate.url === linkReference.url && refCandidate.type === "script");
10592
10764
  if (firstScriptOnThisUrl) {
10593
10765
  return firstScriptOnThisUrl.expectedType;
10594
10766
  }
@@ -10598,9 +10770,53 @@ const decideLinkExpectedType = (linkMention, mentions) => {
10598
10770
  return undefined;
10599
10771
  };
10600
10772
 
10773
+ // css: parseAndTransformCssUrls,
10774
+
10775
+ const jsenvPluginWebmanifestReferenceAnalysis = () => {
10776
+ return {
10777
+ name: "jsenv:webmanifest_reference_analysis",
10778
+ appliesDuring: "*",
10779
+ transformUrlContent: {
10780
+ webmanifest: parseAndTransformWebmanifestUrls
10781
+ }
10782
+ };
10783
+ };
10784
+ const parseAndTransformWebmanifestUrls = async (urlInfo, context) => {
10785
+ const content = urlInfo.content;
10786
+ const manifest = JSON.parse(content);
10787
+ const actions = [];
10788
+ const {
10789
+ icons = []
10790
+ } = manifest;
10791
+ icons.forEach(icon => {
10792
+ const [reference] = context.referenceUtils.found({
10793
+ type: "webmanifest_icon_src",
10794
+ specifier: icon.src
10795
+ });
10796
+ actions.push(async () => {
10797
+ icon.src = await context.referenceUtils.readGeneratedSpecifier(reference);
10798
+ });
10799
+ });
10800
+ if (actions.length === 0) {
10801
+ return null;
10802
+ }
10803
+ await Promise.all(actions.map(action => action()));
10804
+ return JSON.stringify(manifest, null, " ");
10805
+ };
10806
+
10601
10807
  /*
10602
10808
  * https://github.com/parcel-bundler/parcel/blob/v2/packages/transformers/css/src/CSSTransformer.js
10603
10809
  */
10810
+
10811
+ const jsenvPluginCssReferenceAnalysis = () => {
10812
+ return {
10813
+ name: "jsenv:css_reference_analysis",
10814
+ appliesDuring: "*",
10815
+ transformUrlContent: {
10816
+ css: parseAndTransformCssUrls
10817
+ }
10818
+ };
10819
+ };
10604
10820
  const parseAndTransformCssUrls = async (urlInfo, context) => {
10605
10821
  const cssUrls = await parseCssUrls({
10606
10822
  css: urlInfo.content,
@@ -10632,121 +10848,315 @@ const parseAndTransformCssUrls = async (urlInfo, context) => {
10632
10848
  return magicSource.toContentAndSourcemap();
10633
10849
  };
10634
10850
 
10635
- const parseAndTransformJsUrls = async (urlInfo, context) => {
10636
- const jsMentions = await parseJsUrls({
10637
- js: urlInfo.content,
10638
- url: urlInfo.originalUrl,
10639
- isJsModule: urlInfo.type === "js_module",
10640
- isWebWorker: isWebWorkerUrlInfo(urlInfo)
10641
- });
10642
- const actions = [];
10643
- const magicSource = createMagicSource(urlInfo.content);
10644
- for (const jsMention of jsMentions) {
10645
- if (jsMention.subtype === "import_static" || jsMention.subtype === "import_dynamic") {
10646
- urlInfo.data.usesImport = true;
10851
+ const isEscaped = (i, string) => {
10852
+ let backslashBeforeCount = 0;
10853
+ while (i--) {
10854
+ const previousChar = string[i];
10855
+ if (previousChar === "\\") {
10856
+ backslashBeforeCount++;
10647
10857
  }
10648
- const [reference] = context.referenceUtils.found({
10649
- node: jsMention.node,
10650
- type: jsMention.type,
10651
- subtype: jsMention.subtype,
10652
- expectedType: jsMention.expectedType,
10653
- expectedSubtype: jsMention.expectedSubtype || urlInfo.subtype,
10654
- specifier: jsMention.specifier,
10655
- specifierStart: jsMention.start,
10656
- specifierEnd: jsMention.end,
10657
- specifierLine: jsMention.line,
10658
- specifierColumn: jsMention.column,
10659
- data: jsMention.data,
10660
- baseUrl: {
10661
- "StringLiteral": jsMention.baseUrl,
10662
- "window.location": urlInfo.url,
10663
- "window.origin": context.rootDirectoryUrl,
10664
- "import.meta.url": urlInfo.url,
10665
- "context.meta.url": urlInfo.url,
10666
- "document.currentScript.src": urlInfo.url
10667
- }[jsMention.baseUrlType],
10668
- assert: jsMention.assert,
10669
- assertNode: jsMention.assertNode,
10670
- typePropertyNode: jsMention.typePropertyNode
10671
- });
10672
- actions.push(async () => {
10673
- const replacement = await context.referenceUtils.readGeneratedSpecifier(reference);
10674
- magicSource.replace({
10675
- start: jsMention.start,
10676
- end: jsMention.end,
10677
- replacement
10678
- });
10679
- if (reference.mutation) {
10680
- reference.mutation(magicSource);
10681
- }
10682
- });
10683
- }
10684
- if (actions.length > 0) {
10685
- await Promise.all(actions.map(action => action()));
10686
- }
10687
- const {
10688
- content,
10689
- sourcemap
10690
- } = magicSource.toContentAndSourcemap();
10691
- return {
10692
- content,
10693
- sourcemap
10694
- };
10695
- };
10696
-
10697
- const parseAndTransformWebmanifestUrls = async (urlInfo, context) => {
10698
- const content = urlInfo.content;
10699
- const manifest = JSON.parse(content);
10700
- const actions = [];
10701
- const {
10702
- icons = []
10703
- } = manifest;
10704
- icons.forEach(icon => {
10705
- const [reference] = context.referenceUtils.found({
10706
- type: "webmanifest_icon_src",
10707
- specifier: icon.src
10708
- });
10709
- actions.push(async () => {
10710
- icon.src = await context.referenceUtils.readGeneratedSpecifier(reference);
10711
- });
10712
- });
10713
- if (actions.length === 0) {
10714
- return null;
10858
+ break;
10715
10859
  }
10716
- await Promise.all(actions.map(action => action()));
10717
- return JSON.stringify(manifest, null, " ");
10860
+ const isEven = backslashBeforeCount % 2 === 0;
10861
+ return !isEven;
10718
10862
  };
10719
10863
 
10720
- const jsenvPluginUrlAnalysis = ({
10721
- rootDirectoryUrl,
10722
- include,
10723
- supportedProtocols = ["file:", "data:", "virtual:", "http:", "https:"]
10724
- }) => {
10725
- // eslint-disable-next-line no-unused-vars
10726
- let getIncludeInfo = url => undefined;
10727
- if (include) {
10728
- const associations = URL_META.resolveAssociations({
10729
- include
10730
- }, rootDirectoryUrl);
10731
- getIncludeInfo = url => {
10732
- const {
10733
- include
10734
- } = URL_META.applyAssociations({
10735
- url,
10736
- associations
10737
- });
10738
- return include;
10739
- };
10740
- }
10741
- return [{
10742
- name: "jsenv:url_analysis",
10743
- appliesDuring: "*",
10744
- redirectUrl: reference => {
10745
- if (reference.shouldHandle !== undefined) {
10746
- return;
10747
- }
10748
- if (reference.specifier[0] === "#" &&
10749
- // For Html, css and in general "#" refer to a resource in the page
10864
+ const JS_QUOTES = {
10865
+ pickBest: (string, {
10866
+ canUseTemplateString,
10867
+ defaultQuote = DOUBLE
10868
+ } = {}) => {
10869
+ // check default first, once tested do no re-test it
10870
+ if (!string.includes(defaultQuote)) {
10871
+ return defaultQuote;
10872
+ }
10873
+ if (defaultQuote !== DOUBLE && !string.includes(DOUBLE)) {
10874
+ return DOUBLE;
10875
+ }
10876
+ if (defaultQuote !== SINGLE && !string.includes(SINGLE)) {
10877
+ return SINGLE;
10878
+ }
10879
+ if (canUseTemplateString && defaultQuote !== BACKTICK && !string.includes(BACKTICK)) {
10880
+ return BACKTICK;
10881
+ }
10882
+ return defaultQuote;
10883
+ },
10884
+ escapeSpecialChars: (string, {
10885
+ quote = "pickBest",
10886
+ canUseTemplateString,
10887
+ defaultQuote,
10888
+ allowEscapeForVersioning = false
10889
+ }) => {
10890
+ quote = quote === "pickBest" ? JS_QUOTES.pickBest(string, {
10891
+ canUseTemplateString,
10892
+ defaultQuote
10893
+ }) : quote;
10894
+ const replacements = JS_QUOTE_REPLACEMENTS[quote];
10895
+ let result = "";
10896
+ let last = 0;
10897
+ let i = 0;
10898
+ while (i < string.length) {
10899
+ const char = string[i];
10900
+ i++;
10901
+ if (isEscaped(i - 1, string)) continue;
10902
+ const replacement = replacements[char];
10903
+ if (replacement) {
10904
+ if (allowEscapeForVersioning && char === quote && string.slice(i, i + 6) === "+__v__") {
10905
+ let isVersioningConcatenation = false;
10906
+ let j = i + 6; // start after the +
10907
+ while (j < string.length) {
10908
+ const lookAheadChar = string[j];
10909
+ j++;
10910
+ if (lookAheadChar === "+" && string[j] === quote && !isEscaped(j - 1, string)) {
10911
+ isVersioningConcatenation = true;
10912
+ break;
10913
+ }
10914
+ }
10915
+ if (isVersioningConcatenation) {
10916
+ // it's a concatenation
10917
+ // skip until the end of concatenation (the second +)
10918
+ // and resume from there
10919
+ i = j + 1;
10920
+ continue;
10921
+ }
10922
+ }
10923
+ if (last === i - 1) {
10924
+ result += replacement;
10925
+ } else {
10926
+ result += `${string.slice(last, i - 1)}${replacement}`;
10927
+ }
10928
+ last = i;
10929
+ }
10930
+ }
10931
+ if (last !== string.length) {
10932
+ result += string.slice(last);
10933
+ }
10934
+ return `${quote}${result}${quote}`;
10935
+ }
10936
+ };
10937
+ const DOUBLE = `"`;
10938
+ const SINGLE = `'`;
10939
+ const BACKTICK = "`";
10940
+ const lineEndingEscapes = {
10941
+ "\n": "\\n",
10942
+ "\r": "\\r",
10943
+ "\u2028": "\\u2028",
10944
+ "\u2029": "\\u2029"
10945
+ };
10946
+ const JS_QUOTE_REPLACEMENTS = {
10947
+ [DOUBLE]: {
10948
+ '"': '\\"',
10949
+ ...lineEndingEscapes
10950
+ },
10951
+ [SINGLE]: {
10952
+ "'": "\\'",
10953
+ ...lineEndingEscapes
10954
+ },
10955
+ [BACKTICK]: {
10956
+ "`": "\\`",
10957
+ "$": "\\$"
10958
+ }
10959
+ };
10960
+
10961
+ const jsenvPluginJsReferenceAnalysis = ({
10962
+ inlineContent,
10963
+ allowEscapeForVersioning
10964
+ }) => {
10965
+ return [{
10966
+ name: "jsenv:js_reference_analysis",
10967
+ appliesDuring: "*",
10968
+ transformUrlContent: {
10969
+ js_classic: (urlInfo, context) => parseAndTransformJsReferences(urlInfo, context, {
10970
+ inlineContent,
10971
+ allowEscapeForVersioning
10972
+ }),
10973
+ js_module: (urlInfo, context) => parseAndTransformJsReferences(urlInfo, context, {
10974
+ inlineContent,
10975
+ allowEscapeForVersioning
10976
+ })
10977
+ }
10978
+ }];
10979
+ };
10980
+ const parseAndTransformJsReferences = async (urlInfo, context, {
10981
+ inlineContent,
10982
+ allowEscapeForVersioning
10983
+ }) => {
10984
+ const magicSource = createMagicSource(urlInfo.content);
10985
+ const parallelActions = [];
10986
+ const sequentialActions = [];
10987
+ const onInlineReference = inlineReferenceInfo => {
10988
+ const inlineUrl = generateInlineContentUrl({
10989
+ url: urlInfo.url,
10990
+ extension: CONTENT_TYPE.asFileExtension(inlineReferenceInfo.contentType),
10991
+ line: inlineReferenceInfo.line,
10992
+ column: inlineReferenceInfo.column,
10993
+ lineEnd: inlineReferenceInfo.lineEnd,
10994
+ columnEnd: inlineReferenceInfo.columnEnd
10995
+ });
10996
+ let {
10997
+ quote
10998
+ } = inlineReferenceInfo;
10999
+ if (quote === "`" && !context.isSupportedOnCurrentClients("template_literals")) {
11000
+ // if quote is "`" and template literals are not supported
11001
+ // we'll use a regular string (single or double quote)
11002
+ // when rendering the string
11003
+ quote = JS_QUOTES.pickBest(inlineReferenceInfo.content);
11004
+ }
11005
+ const [inlineReference, inlineUrlInfo] = context.referenceUtils.foundInline({
11006
+ type: "js_inline_content",
11007
+ subtype: inlineReferenceInfo.type,
11008
+ // "new_blob_first_arg", "new_inline_content_first_arg", "json_parse_first_arg"
11009
+ isOriginalPosition: urlInfo.content === urlInfo.originalContent,
11010
+ specifierLine: inlineReferenceInfo.line,
11011
+ specifierColumn: inlineReferenceInfo.column,
11012
+ specifier: inlineUrl,
11013
+ contentType: inlineReferenceInfo.contentType,
11014
+ content: inlineReferenceInfo.content
11015
+ });
11016
+ inlineUrlInfo.jsQuote = quote;
11017
+ inlineReference.escape = value => JS_QUOTES.escapeSpecialChars(value.slice(1, -1), {
11018
+ quote
11019
+ });
11020
+ sequentialActions.push(async () => {
11021
+ await context.cook(inlineUrlInfo, {
11022
+ reference: inlineReference
11023
+ });
11024
+ const replacement = JS_QUOTES.escapeSpecialChars(inlineUrlInfo.content, {
11025
+ quote,
11026
+ allowEscapeForVersioning
11027
+ });
11028
+ magicSource.replace({
11029
+ start: inlineReferenceInfo.start,
11030
+ end: inlineReferenceInfo.end,
11031
+ replacement
11032
+ });
11033
+ });
11034
+ };
11035
+ const onExternalReference = externalReferenceInfo => {
11036
+ if (externalReferenceInfo.subtype === "import_static" || externalReferenceInfo.subtype === "import_dynamic") {
11037
+ urlInfo.data.usesImport = true;
11038
+ }
11039
+ const [reference] = context.referenceUtils.found({
11040
+ node: externalReferenceInfo.node,
11041
+ type: externalReferenceInfo.type,
11042
+ subtype: externalReferenceInfo.subtype,
11043
+ expectedType: externalReferenceInfo.expectedType,
11044
+ expectedSubtype: externalReferenceInfo.expectedSubtype || urlInfo.subtype,
11045
+ specifier: externalReferenceInfo.specifier,
11046
+ specifierStart: externalReferenceInfo.start,
11047
+ specifierEnd: externalReferenceInfo.end,
11048
+ specifierLine: externalReferenceInfo.line,
11049
+ specifierColumn: externalReferenceInfo.column,
11050
+ data: externalReferenceInfo.data,
11051
+ baseUrl: {
11052
+ "StringLiteral": externalReferenceInfo.baseUrl,
11053
+ "window.location": urlInfo.url,
11054
+ "window.origin": context.rootDirectoryUrl,
11055
+ "import.meta.url": urlInfo.url,
11056
+ "context.meta.url": urlInfo.url,
11057
+ "document.currentScript.src": urlInfo.url
11058
+ }[externalReferenceInfo.baseUrlType],
11059
+ assert: externalReferenceInfo.assert,
11060
+ assertNode: externalReferenceInfo.assertNode,
11061
+ typePropertyNode: externalReferenceInfo.typePropertyNode
11062
+ });
11063
+ parallelActions.push(async () => {
11064
+ const replacement = await context.referenceUtils.readGeneratedSpecifier(reference);
11065
+ magicSource.replace({
11066
+ start: externalReferenceInfo.start,
11067
+ end: externalReferenceInfo.end,
11068
+ replacement
11069
+ });
11070
+ if (reference.mutation) {
11071
+ reference.mutation(magicSource);
11072
+ }
11073
+ });
11074
+ };
11075
+ const jsReferenceInfos = await parseJsUrls({
11076
+ js: urlInfo.content,
11077
+ url: urlInfo.originalUrl,
11078
+ isJsModule: urlInfo.type === "js_module",
11079
+ isWebWorker: isWebWorkerUrlInfo(urlInfo),
11080
+ inlineContent
11081
+ });
11082
+ for (const jsReferenceInfo of jsReferenceInfos) {
11083
+ if (jsReferenceInfo.isInline) {
11084
+ onInlineReference(jsReferenceInfo);
11085
+ } else {
11086
+ onExternalReference(jsReferenceInfo);
11087
+ }
11088
+ }
11089
+ if (parallelActions.length > 0) {
11090
+ await Promise.all(parallelActions.map(action => action()));
11091
+ }
11092
+ if (sequentialActions.length > 0) {
11093
+ await sequentialActions.reduce(async (previous, action) => {
11094
+ await previous;
11095
+ await action();
11096
+ }, Promise.resolve());
11097
+ }
11098
+ const {
11099
+ content,
11100
+ sourcemap
11101
+ } = magicSource.toContentAndSourcemap();
11102
+ return {
11103
+ content,
11104
+ sourcemap
11105
+ };
11106
+ };
11107
+
11108
+ const jsenvPluginReferenceAnalysis = ({
11109
+ include,
11110
+ supportedProtocols = ["file:", "data:", "virtual:", "http:", "https:"],
11111
+ inlineContent = true,
11112
+ inlineConvertedScript = false,
11113
+ fetchInlineUrls = true,
11114
+ allowEscapeForVersioning = false
11115
+ }) => {
11116
+ return [jsenvPluginReferenceAnalysisInclude({
11117
+ include,
11118
+ supportedProtocols
11119
+ }), jsenvPluginDirectoryReferenceAnalysis(), jsenvPluginHtmlReferenceAnalysis({
11120
+ inlineContent,
11121
+ inlineConvertedScript
11122
+ }), jsenvPluginWebmanifestReferenceAnalysis(), jsenvPluginCssReferenceAnalysis(), jsenvPluginJsReferenceAnalysis({
11123
+ inlineContent,
11124
+ allowEscapeForVersioning
11125
+ }), ...(inlineContent ? [jsenvPluginDataUrlsAnalysis()] : []), ...(inlineContent && fetchInlineUrls ? [jsenvPluginInlineContentFetcher()] : []), jsenvPluginReferenceExpectedTypes()];
11126
+ };
11127
+ const jsenvPluginReferenceAnalysisInclude = ({
11128
+ include,
11129
+ supportedProtocols
11130
+ }) => {
11131
+ // eslint-disable-next-line no-unused-vars
11132
+ let getIncludeInfo = url => undefined;
11133
+ return {
11134
+ name: "jsenv:reference_analysis_include",
11135
+ appliesDuring: "*",
11136
+ init: ({
11137
+ rootDirectoryUrl
11138
+ }) => {
11139
+ if (include) {
11140
+ const associations = URL_META.resolveAssociations({
11141
+ include
11142
+ }, rootDirectoryUrl);
11143
+ getIncludeInfo = url => {
11144
+ const {
11145
+ include
11146
+ } = URL_META.applyAssociations({
11147
+ url,
11148
+ associations
11149
+ });
11150
+ return include;
11151
+ };
11152
+ }
11153
+ },
11154
+ redirectReference: reference => {
11155
+ if (reference.shouldHandle !== undefined) {
11156
+ return;
11157
+ }
11158
+ if (reference.specifier[0] === "#" &&
11159
+ // For Html, css and in general "#" refer to a resource in the page
10750
11160
  // so that urls must be kept intact
10751
11161
  // However for js import specifiers they have a different meaning and we want
10752
11162
  // to resolve them (https://nodejs.org/api/packages.html#imports for instance)
@@ -10770,57 +11180,32 @@ const jsenvPluginUrlAnalysis = ({
10770
11180
  if (protocolIsSupported) {
10771
11181
  reference.shouldHandle = true;
10772
11182
  }
10773
- },
10774
- transformUrlContent: {
10775
- html: parseAndTransformHtmlUrls,
10776
- css: parseAndTransformCssUrls,
10777
- js_classic: parseAndTransformJsUrls,
10778
- js_module: parseAndTransformJsUrls,
10779
- webmanifest: parseAndTransformWebmanifestUrls,
10780
- directory: (urlInfo, context) => {
10781
- const originalDirectoryReference = findOriginalDirectoryReference(urlInfo, context);
10782
- const directoryRelativeUrl = urlToRelativeUrl(urlInfo.url, context.rootDirectoryUrl);
10783
- JSON.parse(urlInfo.content).forEach(directoryEntryName => {
10784
- context.referenceUtils.found({
10785
- type: "filesystem",
10786
- subtype: "directory_entry",
10787
- specifier: directoryEntryName,
10788
- trace: {
10789
- message: `"${directoryRelativeUrl}${directoryEntryName}" entry in directory referenced by ${originalDirectoryReference.trace.message}`
10790
- }
10791
- });
10792
- });
10793
- }
10794
11183
  }
10795
- }, jsenvPluginReferenceExpectedTypes()];
11184
+ };
10796
11185
  };
10797
- const findOriginalDirectoryReference = (urlInfo, context) => {
10798
- const findNonFileSystemAncestor = urlInfo => {
10799
- for (const dependentUrl of urlInfo.dependents) {
10800
- const dependentUrlInfo = context.urlGraph.getUrlInfo(dependentUrl);
10801
- if (dependentUrlInfo.type !== "directory") {
10802
- return [dependentUrlInfo, urlInfo];
10803
- }
10804
- const found = findNonFileSystemAncestor(dependentUrlInfo);
10805
- if (found) {
10806
- return found;
11186
+ const jsenvPluginInlineContentFetcher = () => {
11187
+ return {
11188
+ name: "jsenv:inline_content_fetcher",
11189
+ appliesDuring: "*",
11190
+ fetchUrlContent: urlInfo => {
11191
+ if (!urlInfo.isInline) {
11192
+ return null;
10807
11193
  }
11194
+ return {
11195
+ // we want to fetch the original content otherwise we might re-cook
11196
+ // content already cooked
11197
+ content: urlInfo.originalContent,
11198
+ contentType: urlInfo.contentType
11199
+ };
10808
11200
  }
10809
- return [];
10810
11201
  };
10811
- const [ancestor, child] = findNonFileSystemAncestor(urlInfo);
10812
- if (!ancestor) {
10813
- return null;
10814
- }
10815
- const ref = ancestor.references.find(ref => ref.url === child.url);
10816
- return ref;
10817
11202
  };
10818
11203
 
10819
11204
  const jsenvPluginInliningAsDataUrl = () => {
10820
11205
  return {
10821
11206
  name: "jsenv:inlining_as_data_url",
10822
11207
  appliesDuring: "*",
10823
- formatUrl: {
11208
+ formatReference: {
10824
11209
  // if the referenced url is a worker we could use
10825
11210
  // https://www.oreilly.com/library/view/web-workers/9781449322120/ch04.html
10826
11211
  // but maybe we should rather use ?object_url
@@ -10846,10 +11231,19 @@ const jsenvPluginInliningAsDataUrl = () => {
10846
11231
  await context.cook(urlInfo, {
10847
11232
  reference
10848
11233
  });
11234
+ const contentAsBase64 = Buffer.from(urlInfo.content).toString("base64");
10849
11235
  const specifier = DATA_URL.stringify({
10850
11236
  mediaType: urlInfo.contentType,
10851
11237
  base64Flag: true,
10852
- data: Buffer.from(urlInfo.content).toString("base64")
11238
+ data: contentAsBase64
11239
+ });
11240
+ context.referenceUtils.becomesInline(reference, {
11241
+ line: reference.line,
11242
+ column: reference.column,
11243
+ isOriginal: reference.isOriginal,
11244
+ specifier,
11245
+ content: contentAsBase64,
11246
+ contentType: urlInfo.contentType
10853
11247
  });
10854
11248
  return specifier;
10855
11249
  })();
@@ -10938,789 +11332,81 @@ const jsenvPluginInliningIntoHtml = () => {
10938
11332
  isOriginal,
10939
11333
  specifier: scriptReference.generatedSpecifier,
10940
11334
  content: scriptUrlInfo.content,
10941
- contentType: scriptUrlInfo.contentType
10942
- });
10943
- mutations.push(() => {
10944
- setHtmlNodeAttributes(scriptNode, {
10945
- "inlined-from-src": src,
10946
- "src": undefined,
10947
- "crossorigin": undefined,
10948
- "integrity": undefined,
10949
- "jsenv-inlined-by": "jsenv:inlining_into_html"
10950
- });
10951
- setHtmlNodeText(scriptNode, scriptUrlInfo.content, {
10952
- indentation: "auto"
10953
- });
10954
- });
10955
- });
10956
- };
10957
- visitHtmlNodes(htmlAst, {
10958
- link: linkNode => {
10959
- const rel = getHtmlNodeAttribute(linkNode, "rel");
10960
- if (rel !== "stylesheet") {
10961
- return;
10962
- }
10963
- const href = getHtmlNodeAttribute(linkNode, "href");
10964
- if (!href) {
10965
- return;
10966
- }
10967
- onStyleSheet(linkNode, {
10968
- href
10969
- });
10970
- },
10971
- script: scriptNode => {
10972
- const {
10973
- type
10974
- } = analyzeScriptNode(scriptNode);
10975
- const scriptNodeText = getHtmlNodeText(scriptNode);
10976
- if (scriptNodeText) {
10977
- return;
10978
- }
10979
- const src = getHtmlNodeAttribute(scriptNode, "src");
10980
- if (!src) {
10981
- return;
10982
- }
10983
- onScriptWithSrc(scriptNode, {
10984
- type,
10985
- src
10986
- });
10987
- }
10988
- });
10989
- if (actions.length > 0) {
10990
- await Promise.all(actions.map(action => action()));
10991
- }
10992
- mutations.forEach(mutation => mutation());
10993
- const htmlModified = stringifyHtmlAst(htmlAst);
10994
- return htmlModified;
10995
- }
10996
- }
10997
- };
10998
- };
10999
-
11000
- const jsenvPluginInlining = () => {
11001
- return [{
11002
- name: "jsenv:inlining",
11003
- appliesDuring: "*",
11004
- redirectUrl: reference => {
11005
- const {
11006
- searchParams
11007
- } = reference;
11008
- if (searchParams.has("inline")) {
11009
- const urlObject = new URL(reference.url);
11010
- urlObject.searchParams.delete("inline");
11011
- return urlObject.href;
11012
- }
11013
- return null;
11014
- }
11015
- }, jsenvPluginInliningAsDataUrl(), jsenvPluginInliningIntoHtml()];
11016
- };
11017
-
11018
- /*
11019
- * This plugin ensure content inlined inside HTML is cooked (inline <script> for instance)
11020
- * For <script hot-accept> the script content will be moved to a virtual file
11021
- * to enable hot reloading
11022
- */
11023
- const jsenvPluginHtmlInlineContentAnalysis = ({
11024
- analyzeConvertedScripts
11025
- }) => {
11026
- const cookInlineContent = async ({
11027
- context,
11028
- inlineContentUrlInfo,
11029
- inlineContentReference
11030
- }) => {
11031
- try {
11032
- await context.cook(inlineContentUrlInfo, {
11033
- reference: inlineContentReference
11034
- });
11035
- } catch (e) {
11036
- if (e.code === "PARSE_ERROR") {
11037
- // When something like <style> or <script> contains syntax error
11038
- // the HTML in itself it still valid
11039
- // keep the syntax error and continue with the HTML
11040
- const messageStart = inlineContentUrlInfo.type === "css" ? `Syntax error on css declared inside <style>` : `Syntax error on js declared inside <script>`;
11041
- context.logger.error(`${messageStart}: ${e.cause.reasonCode}
11042
- ${e.traceMessage}`);
11043
- } else {
11044
- throw e;
11045
- }
11046
- }
11047
- };
11048
- return {
11049
- name: "jsenv:html_inline_content_analysis",
11050
- appliesDuring: "*",
11051
- transformUrlContent: {
11052
- html: async (urlInfo, context) => {
11053
- const htmlAst = parseHtmlString(urlInfo.content);
11054
- const mutations = [];
11055
- const actions = [];
11056
- visitHtmlNodes(htmlAst, {
11057
- style: styleNode => {
11058
- const styleNodeText = getHtmlNodeText(styleNode);
11059
- if (!styleNodeText) {
11060
- return;
11061
- }
11062
- const {
11063
- line,
11064
- column,
11065
- lineEnd,
11066
- columnEnd,
11067
- isOriginal
11068
- } = getHtmlNodePosition(styleNode, {
11069
- preferOriginal: true
11070
- });
11071
- const inlineStyleUrl = generateInlineContentUrl({
11072
- url: urlInfo.url,
11073
- extension: ".css",
11074
- line,
11075
- column,
11076
- lineEnd,
11077
- columnEnd
11078
- });
11079
- const debug = getHtmlNodeAttribute(styleNode, "jsenv-debug") !== undefined;
11080
- const [inlineStyleReference, inlineStyleUrlInfo] = context.referenceUtils.foundInline({
11081
- node: styleNode,
11082
- type: "style",
11083
- expectedType: "css",
11084
- isOriginalPosition: isOriginal,
11085
- // we remove 1 to the line because imagine the following html:
11086
- // <style>body { color: red; }</style>
11087
- // -> content starts same line as <style>
11088
- specifierLine: line - 1,
11089
- specifierColumn: column,
11090
- specifier: inlineStyleUrl,
11091
- contentType: "text/css",
11092
- content: styleNodeText,
11093
- debug
11094
- });
11095
- actions.push(async () => {
11096
- await cookInlineContent({
11097
- context,
11098
- inlineContentUrlInfo: inlineStyleUrlInfo,
11099
- inlineContentReference: inlineStyleReference
11100
- });
11101
- });
11102
- mutations.push(() => {
11103
- setHtmlNodeText(styleNode, inlineStyleUrlInfo.content, {
11104
- indentation: false // indentation would decrease strack trace precision
11105
- });
11106
-
11107
- setHtmlNodeAttributes(styleNode, {
11108
- "jsenv-cooked-by": "jsenv:html_inline_content_analysis"
11109
- });
11110
- });
11111
- },
11112
- script: scriptNode => {
11113
- const scriptNodeText = getHtmlNodeText(scriptNode);
11114
- if (!scriptNodeText) {
11115
- return;
11116
- }
11117
- // If the inline script was already handled by an other plugin, ignore it
11118
- // - we want to preserve inline scripts generated by html supervisor during dev
11119
- // - we want to avoid cooking twice a script during build
11120
- if (!analyzeConvertedScripts && getHtmlNodeAttribute(scriptNode, "jsenv-injected-by") === "jsenv:js_module_fallback") {
11121
- return;
11122
- }
11123
- const hotAccept = getHtmlNodeAttribute(scriptNode, "hot-accept") !== undefined;
11124
- const {
11125
- type,
11126
- contentType,
11127
- extension
11128
- } = analyzeScriptNode(scriptNode);
11129
- const {
11130
- line,
11131
- column,
11132
- lineEnd,
11133
- columnEnd,
11134
- isOriginal
11135
- } = getHtmlNodePosition(scriptNode, {
11136
- preferOriginal: true
11137
- });
11138
- let inlineScriptUrl = generateInlineContentUrl({
11139
- url: urlInfo.url,
11140
- extension: extension || CONTENT_TYPE.asFileExtension(contentType),
11141
- line,
11142
- column,
11143
- lineEnd,
11144
- columnEnd
11145
- });
11146
- const debug = getHtmlNodeAttribute(scriptNode, "jsenv-debug") !== undefined;
11147
- const [inlineScriptReference, inlineScriptUrlInfo] = context.referenceUtils.foundInline({
11148
- node: scriptNode,
11149
- type: "script",
11150
- expectedType: type,
11151
- // we remove 1 to the line because imagine the following html:
11152
- // <script>console.log('ok')</script>
11153
- // -> content starts same line as <script>
11154
- specifierLine: line - 1,
11155
- specifierColumn: column,
11156
- isOriginalPosition: isOriginal,
11157
- specifier: inlineScriptUrl,
11158
- contentType,
11159
- content: scriptNodeText,
11160
- debug
11161
- });
11162
- actions.push(async () => {
11163
- await cookInlineContent({
11164
- context,
11165
- inlineContentUrlInfo: inlineScriptUrlInfo,
11166
- inlineContentReference: inlineScriptReference
11167
- });
11168
- mutations.push(() => {
11169
- const attributes = {
11170
- "jsenv-cooked-by": "jsenv:html_inline_content_analysis",
11171
- // 1. <script type="jsx"> becomes <script>
11172
- // 2. <script type="module/jsx"> becomes <script type="module">
11173
- ...(extension ? {
11174
- type: type === "js_module" ? "module" : undefined
11175
- } : {})
11176
- };
11177
- if (hotAccept) {
11178
- removeHtmlNodeText(scriptNode);
11179
- setHtmlNodeAttributes(scriptNode, {
11180
- ...attributes
11181
- });
11182
- } else {
11183
- setHtmlNodeText(scriptNode, inlineScriptUrlInfo.content, {
11184
- indentation: false // indentation would decrease stack trace precision
11185
- });
11186
-
11187
- setHtmlNodeAttributes(scriptNode, {
11188
- ...attributes
11189
- });
11190
- }
11191
- });
11192
- });
11193
- }
11194
- });
11195
- if (actions.length > 0) {
11196
- await Promise.all(actions.map(action => action()));
11197
- }
11198
- if (mutations.length === 0) {
11199
- return null;
11200
- }
11201
- mutations.forEach(mutation => mutation());
11202
- const htmlModified = stringifyHtmlAst(htmlAst);
11203
- return htmlModified;
11204
- }
11205
- }
11206
- };
11207
- };
11208
-
11209
- const isEscaped = (i, string) => {
11210
- let backslashBeforeCount = 0;
11211
- while (i--) {
11212
- const previousChar = string[i];
11213
- if (previousChar === "\\") {
11214
- backslashBeforeCount++;
11215
- }
11216
- break;
11217
- }
11218
- const isEven = backslashBeforeCount % 2 === 0;
11219
- return !isEven;
11220
- };
11221
-
11222
- const JS_QUOTES = {
11223
- pickBest: (string, {
11224
- canUseTemplateString,
11225
- defaultQuote = DOUBLE
11226
- } = {}) => {
11227
- // check default first, once tested do no re-test it
11228
- if (!string.includes(defaultQuote)) {
11229
- return defaultQuote;
11230
- }
11231
- if (defaultQuote !== DOUBLE && !string.includes(DOUBLE)) {
11232
- return DOUBLE;
11233
- }
11234
- if (defaultQuote !== SINGLE && !string.includes(SINGLE)) {
11235
- return SINGLE;
11236
- }
11237
- if (canUseTemplateString && defaultQuote !== BACKTICK && !string.includes(BACKTICK)) {
11238
- return BACKTICK;
11239
- }
11240
- return defaultQuote;
11241
- },
11242
- escapeSpecialChars: (string, {
11243
- quote = "pickBest",
11244
- canUseTemplateString,
11245
- defaultQuote,
11246
- allowEscapeForVersioning = false
11247
- }) => {
11248
- quote = quote === "pickBest" ? JS_QUOTES.pickBest(string, {
11249
- canUseTemplateString,
11250
- defaultQuote
11251
- }) : quote;
11252
- const replacements = JS_QUOTE_REPLACEMENTS[quote];
11253
- let result = "";
11254
- let last = 0;
11255
- let i = 0;
11256
- while (i < string.length) {
11257
- const char = string[i];
11258
- i++;
11259
- if (isEscaped(i - 1, string)) continue;
11260
- const replacement = replacements[char];
11261
- if (replacement) {
11262
- if (allowEscapeForVersioning && char === quote && string.slice(i, i + 6) === "+__v__") {
11263
- let isVersioningConcatenation = false;
11264
- let j = i + 6; // start after the +
11265
- while (j < string.length) {
11266
- const lookAheadChar = string[j];
11267
- j++;
11268
- if (lookAheadChar === "+" && string[j] === quote && !isEscaped(j - 1, string)) {
11269
- isVersioningConcatenation = true;
11270
- break;
11271
- }
11272
- }
11273
- if (isVersioningConcatenation) {
11274
- // it's a concatenation
11275
- // skip until the end of concatenation (the second +)
11276
- // and resume from there
11277
- i = j + 1;
11278
- continue;
11279
- }
11280
- }
11281
- if (last === i - 1) {
11282
- result += replacement;
11283
- } else {
11284
- result += `${string.slice(last, i - 1)}${replacement}`;
11285
- }
11286
- last = i;
11287
- }
11288
- }
11289
- if (last !== string.length) {
11290
- result += string.slice(last);
11291
- }
11292
- return `${quote}${result}${quote}`;
11293
- }
11294
- };
11295
- const DOUBLE = `"`;
11296
- const SINGLE = `'`;
11297
- const BACKTICK = "`";
11298
- const lineEndingEscapes = {
11299
- "\n": "\\n",
11300
- "\r": "\\r",
11301
- "\u2028": "\\u2028",
11302
- "\u2029": "\\u2029"
11303
- };
11304
- const JS_QUOTE_REPLACEMENTS = {
11305
- [DOUBLE]: {
11306
- '"': '\\"',
11307
- ...lineEndingEscapes
11308
- },
11309
- [SINGLE]: {
11310
- "'": "\\'",
11311
- ...lineEndingEscapes
11312
- },
11313
- [BACKTICK]: {
11314
- "`": "\\`",
11315
- "$": "\\$"
11316
- }
11317
- };
11318
-
11319
- const jsenvPluginJsInlineContentAnalysis = ({
11320
- allowEscapeForVersioning
11321
- }) => {
11322
- const parseAndTransformInlineContentCalls = async (urlInfo, context) => {
11323
- const inlineContentInfos = await parseJsInlineContentInfos({
11324
- js: urlInfo.content,
11325
- url: urlInfo.originalUrl,
11326
- isJsModule: urlInfo.type === "js_module"
11327
- });
11328
- if (inlineContentInfos.length === 0) {
11329
- return null;
11330
- }
11331
- const magicSource = createMagicSource(urlInfo.content);
11332
- await inlineContentInfos.reduce(async (previous, inlineContentInfo) => {
11333
- await previous;
11334
- const inlineUrl = generateInlineContentUrl({
11335
- url: urlInfo.url,
11336
- extension: CONTENT_TYPE.asFileExtension(inlineContentInfo.contentType),
11337
- line: inlineContentInfo.line,
11338
- column: inlineContentInfo.column,
11339
- lineEnd: inlineContentInfo.lineEnd,
11340
- columnEnd: inlineContentInfo.columnEnd
11341
- });
11342
- let {
11343
- quote
11344
- } = inlineContentInfo;
11345
- if (quote === "`" && !context.isSupportedOnCurrentClients("template_literals")) {
11346
- // if quote is "`" and template literals are not supported
11347
- // we'll use a regular string (single or double quote)
11348
- // when rendering the string
11349
- quote = JS_QUOTES.pickBest(inlineContentInfo.content);
11350
- }
11351
- const [inlineReference, inlineUrlInfo] = context.referenceUtils.foundInline({
11352
- type: "js_inline_content",
11353
- subtype: inlineContentInfo.type,
11354
- // "new_blob_first_arg", "new_inline_content_first_arg", "json_parse_first_arg"
11355
- isOriginalPosition: urlInfo.content === urlInfo.originalContent,
11356
- specifierLine: inlineContentInfo.line,
11357
- specifierColumn: inlineContentInfo.column,
11358
- specifier: inlineUrl,
11359
- contentType: inlineContentInfo.contentType,
11360
- content: inlineContentInfo.content
11361
- });
11362
- inlineUrlInfo.jsQuote = quote;
11363
- inlineReference.escape = value => JS_QUOTES.escapeSpecialChars(value.slice(1, -1), {
11364
- quote
11365
- });
11366
- await context.cook(inlineUrlInfo, {
11367
- reference: inlineReference
11368
- });
11369
- magicSource.replace({
11370
- start: inlineContentInfo.start,
11371
- end: inlineContentInfo.end,
11372
- replacement: JS_QUOTES.escapeSpecialChars(inlineUrlInfo.content, {
11373
- quote,
11374
- allowEscapeForVersioning
11375
- })
11376
- });
11377
- }, Promise.resolve());
11378
- return magicSource.toContentAndSourcemap();
11379
- };
11380
- return {
11381
- name: "jsenv:js_inline_content_analysis",
11382
- appliesDuring: "*",
11383
- transformUrlContent: {
11384
- js_classic: parseAndTransformInlineContentCalls,
11385
- js_module: parseAndTransformInlineContentCalls
11386
- }
11387
- };
11388
- };
11389
- const parseJsInlineContentInfos = async ({
11390
- js,
11391
- url,
11392
- isJsModule
11393
- }) => {
11394
- if (!js.includes("InlineContent") && !js.includes("new Blob(") && !js.includes("JSON.parse(")) {
11395
- return [];
11396
- }
11397
- const {
11398
- metadata
11399
- } = await applyBabelPlugins({
11400
- babelPlugins: [babelPluginMetadataInlineContents],
11401
- urlInfo: {
11402
- originalUrl: url,
11403
- type: isJsModule ? "js_module" : "js_classic",
11404
- content: js
11405
- }
11406
- });
11407
- return metadata.inlineContentInfos;
11408
- };
11409
- const babelPluginMetadataInlineContents = () => {
11410
- return {
11411
- name: "metadata-inline-contents",
11412
- visitor: {
11413
- Program: (programPath, state) => {
11414
- const inlineContentInfos = [];
11415
- const onInlineContentInfo = inlineContentInfo => {
11416
- inlineContentInfos.push(inlineContentInfo);
11417
- };
11418
- programPath.traverse({
11419
- NewExpression: path => {
11420
- if (isNewInlineContentCall(path)) {
11421
- analyzeNewInlineContentCall(path.node, {
11422
- onInlineContentInfo
11423
- });
11424
- return;
11425
- }
11426
- if (isNewBlobCall(path.node)) {
11427
- analyzeNewBlobCall(path.node, {
11428
- onInlineContentInfo
11429
- });
11430
- return;
11431
- }
11432
- },
11433
- CallExpression: path => {
11434
- const node = path.node;
11435
- if (isJSONParseCall(node)) {
11436
- analyzeJsonParseCall(node, {
11437
- onInlineContentInfo
11438
- });
11439
- }
11440
- }
11441
- });
11442
- state.file.metadata.inlineContentInfos = inlineContentInfos;
11443
- }
11444
- }
11445
- };
11446
- };
11447
- const isNewInlineContentCall = path => {
11448
- const node = path.node;
11449
- if (node.callee.type === "Identifier") {
11450
- // terser rename import to use a shorter name
11451
- const name = getOriginalName(path, node.callee.name);
11452
- return name === "InlineContent";
11453
- }
11454
- if (node.callee.id && node.callee.id.type === "Identifier") {
11455
- const name = getOriginalName(path, node.callee.id.name);
11456
- return name === "InlineContent";
11457
- }
11458
- return false;
11459
- };
11460
- const analyzeNewInlineContentCall = (node, {
11461
- onInlineContentInfo
11462
- }) => {
11463
- analyzeArguments({
11464
- node,
11465
- onInlineContentInfo,
11466
- nodeHoldingContent: node.arguments[0],
11467
- type: "new_inline_content_first_arg"
11468
- });
11469
- };
11470
- const isNewBlobCall = node => {
11471
- return node.callee.type === "Identifier" && node.callee.name === "Blob";
11472
- };
11473
- const analyzeNewBlobCall = (node, {
11474
- onInlineContentInfo
11475
- }) => {
11476
- const firstArg = node.arguments[0];
11477
- if (!firstArg) {
11478
- return;
11479
- }
11480
- if (firstArg.type !== "ArrayExpression") {
11481
- return;
11482
- }
11483
- if (firstArg.elements.length !== 1) {
11484
- return;
11485
- }
11486
- analyzeArguments({
11487
- node,
11488
- onInlineContentInfo,
11489
- nodeHoldingContent: firstArg.elements[0],
11490
- type: "new_blob_first_arg"
11491
- });
11492
- };
11493
- const analyzeArguments = ({
11494
- node,
11495
- onInlineContentInfo,
11496
- nodeHoldingContent,
11497
- type
11498
- }) => {
11499
- if (node.arguments.length !== 2) {
11500
- return;
11501
- }
11502
- const [, secondArg] = node.arguments;
11503
- const typePropertyNode = getTypePropertyNode(secondArg);
11504
- if (!typePropertyNode) {
11505
- return;
11506
- }
11507
- const typePropertyValueNode = typePropertyNode.value;
11508
- if (typePropertyValueNode.type !== "StringLiteral") {
11509
- return;
11510
- }
11511
- const contentType = typePropertyValueNode.value;
11512
- const contentDetails = extractContentDetails(nodeHoldingContent);
11513
- if (contentDetails) {
11514
- onInlineContentInfo({
11515
- node: nodeHoldingContent,
11516
- ...getNodePosition(nodeHoldingContent),
11517
- type,
11518
- contentType,
11519
- ...contentDetails
11520
- });
11521
- }
11522
- };
11523
- const extractContentDetails = node => {
11524
- if (node.type === "StringLiteral") {
11525
- return {
11526
- nodeType: "StringLiteral",
11527
- quote: node.extra.raw[0],
11528
- content: node.value
11529
- };
11530
- }
11531
- if (node.type === "TemplateLiteral") {
11532
- const quasis = node.quasis;
11533
- if (quasis.length !== 1) {
11534
- return null;
11535
- }
11536
- const templateElementNode = quasis[0];
11537
- return {
11538
- nodeType: "TemplateLiteral",
11539
- quote: "`",
11540
- content: templateElementNode.value.cooked
11541
- };
11542
- }
11543
- return null;
11544
- };
11545
- const isJSONParseCall = node => {
11546
- const callee = node.callee;
11547
- return callee.type === "MemberExpression" && callee.object.type === "Identifier" && callee.object.name === "JSON" && callee.property.type === "Identifier" && callee.property.name === "parse";
11548
- };
11549
- const analyzeJsonParseCall = (node, {
11550
- onInlineContentInfo
11551
- }) => {
11552
- const firstArgNode = node.arguments[0];
11553
- const contentDetails = extractContentDetails(firstArgNode);
11554
- if (contentDetails) {
11555
- onInlineContentInfo({
11556
- node: firstArgNode,
11557
- ...getNodePosition(firstArgNode),
11558
- type: "json_parse_first_arg",
11559
- contentType: "application/json",
11560
- ...contentDetails
11561
- });
11562
- }
11563
- };
11564
- const getNodePosition = node => {
11565
- return {
11566
- start: node.start,
11567
- end: node.end,
11568
- line: node.loc.start.line,
11569
- column: node.loc.start.column,
11570
- lineEnd: node.loc.end.line,
11571
- columnEnd: node.loc.end.column
11572
- };
11573
- };
11574
- const getOriginalName = (path, name) => {
11575
- const binding = path.scope.getBinding(name);
11576
- if (!binding) {
11577
- return name;
11578
- }
11579
- if (binding.path.type === "ImportSpecifier") {
11580
- const importedName = binding.path.node.imported.name;
11581
- if (name === importedName) {
11582
- return name;
11583
- }
11584
- return getOriginalName(path, importedName);
11585
- }
11586
- if (binding.path.type === "VariableDeclarator") {
11587
- const {
11588
- node
11589
- } = binding.path;
11590
- const {
11591
- init
11592
- } = node;
11593
- if (init && init.type === "Identifier") {
11594
- const previousName = init.name;
11595
- return getOriginalName(path, previousName);
11596
- }
11597
- if (node.id && node.id.type === "Identifier") {
11598
- const {
11599
- constantViolations
11600
- } = binding;
11601
- if (constantViolations && constantViolations.length > 0) {
11602
- const lastViolation = constantViolations[constantViolations.length - 1];
11603
- if (lastViolation && lastViolation.node.type === "AssignmentExpression" && lastViolation.node.right.type === "MemberExpression" && lastViolation.node.right.property.type === "Identifier") {
11604
- return lastViolation.node.right.property.name;
11605
- }
11606
- }
11607
- }
11608
- }
11609
- return name;
11610
- };
11611
- const getTypePropertyNode = node => {
11612
- if (node.type !== "ObjectExpression") {
11613
- return null;
11614
- }
11615
- const {
11616
- properties
11617
- } = node;
11618
- return properties.find(property => {
11619
- return property.type === "ObjectProperty" && property.key.type === "Identifier" && property.key.name === "type";
11620
- });
11621
- };
11622
-
11623
- const jsenvPluginDataUrls = () => {
11624
- return {
11625
- name: "jsenv:data_urls",
11626
- appliesDuring: "*",
11627
- resolveUrl: reference => {
11628
- if (!reference.specifier.startsWith("data:")) {
11629
- return null;
11630
- }
11631
- return reference.specifier;
11632
- },
11633
- fetchUrlContent: urlInfo => {
11634
- if (!urlInfo.url.startsWith("data:")) {
11635
- return null;
11636
- }
11637
- const {
11638
- contentType,
11639
- base64Flag,
11640
- data: urlData
11641
- } = DATA_URL.parse(urlInfo.url);
11642
- urlInfo.data.base64Flag = base64Flag;
11643
- return {
11644
- content: contentFromUrlData({
11645
- contentType,
11646
- base64Flag,
11647
- urlData
11648
- }),
11649
- contentType
11650
- };
11651
- },
11652
- formatUrl: (reference, context) => {
11653
- if (!reference.generatedUrl.startsWith("data:")) {
11654
- return null;
11655
- }
11656
- if (reference.type === "sourcemap_comment") {
11657
- return null;
11658
- }
11659
- return (async () => {
11660
- const urlInfo = context.urlGraph.getUrlInfo(reference.url);
11661
- await context.cook(urlInfo, {
11662
- reference
11335
+ contentType: scriptUrlInfo.contentType
11336
+ });
11337
+ mutations.push(() => {
11338
+ setHtmlNodeAttributes(scriptNode, {
11339
+ "inlined-from-src": src,
11340
+ "src": undefined,
11341
+ "crossorigin": undefined,
11342
+ "integrity": undefined,
11343
+ "jsenv-inlined-by": "jsenv:inlining_into_html"
11344
+ });
11345
+ setHtmlNodeText(scriptNode, scriptUrlInfo.content, {
11346
+ indentation: "auto"
11347
+ });
11348
+ });
11349
+ });
11350
+ };
11351
+ visitHtmlNodes(htmlAst, {
11352
+ link: linkNode => {
11353
+ const rel = getHtmlNodeAttribute(linkNode, "rel");
11354
+ if (rel !== "stylesheet") {
11355
+ return;
11356
+ }
11357
+ const href = getHtmlNodeAttribute(linkNode, "href");
11358
+ if (!href) {
11359
+ return;
11360
+ }
11361
+ onStyleSheet(linkNode, {
11362
+ href
11363
+ });
11364
+ },
11365
+ script: scriptNode => {
11366
+ const {
11367
+ type
11368
+ } = analyzeScriptNode(scriptNode);
11369
+ const scriptNodeText = getHtmlNodeText(scriptNode);
11370
+ if (scriptNodeText) {
11371
+ return;
11372
+ }
11373
+ const src = getHtmlNodeAttribute(scriptNode, "src");
11374
+ if (!src) {
11375
+ return;
11376
+ }
11377
+ onScriptWithSrc(scriptNode, {
11378
+ type,
11379
+ src
11380
+ });
11381
+ }
11663
11382
  });
11664
- if (urlInfo.originalContent === urlInfo.content) {
11665
- return reference.generatedUrl;
11383
+ if (actions.length > 0) {
11384
+ await Promise.all(actions.map(action => action()));
11666
11385
  }
11667
- const specifier = DATA_URL.stringify({
11668
- contentType: urlInfo.contentType,
11669
- base64Flag: urlInfo.data.base64Flag,
11670
- data: urlInfo.data.base64Flag ? dataToBase64(urlInfo.content) : String(urlInfo.content)
11671
- });
11672
- return specifier;
11673
- })();
11386
+ mutations.forEach(mutation => mutation());
11387
+ const htmlModified = stringifyHtmlAst(htmlAst);
11388
+ return htmlModified;
11389
+ }
11674
11390
  }
11675
11391
  };
11676
11392
  };
11677
- const contentFromUrlData = ({
11678
- contentType,
11679
- base64Flag,
11680
- urlData
11681
- }) => {
11682
- if (CONTENT_TYPE.isTextual(contentType)) {
11683
- if (base64Flag) {
11684
- return base64ToString(urlData);
11685
- }
11686
- return urlData;
11687
- }
11688
- if (base64Flag) {
11689
- return base64ToBuffer(urlData);
11690
- }
11691
- return Buffer.from(urlData);
11692
- };
11693
- const base64ToBuffer = base64String => Buffer.from(base64String, "base64");
11694
- const base64ToString = base64String => Buffer.from(base64String, "base64").toString("utf8");
11695
- const dataToBase64 = data => Buffer.from(data).toString("base64");
11696
11393
 
11697
- const jsenvPluginInlineContentAnalysis = ({
11698
- fetchInlineUrls = true,
11699
- analyzeConvertedScripts = false,
11700
- allowEscapeForVersioning = false
11701
- } = {}) => {
11702
- return [...(fetchInlineUrls ? [jsenvPluginInlineContentFetcher()] : []), jsenvPluginHtmlInlineContentAnalysis({
11703
- analyzeConvertedScripts
11704
- }), jsenvPluginJsInlineContentAnalysis({
11705
- allowEscapeForVersioning
11706
- }), jsenvPluginDataUrls()];
11707
- };
11708
- const jsenvPluginInlineContentFetcher = () => {
11709
- return {
11710
- name: "jsenv:inline_content_fetcher",
11394
+ const jsenvPluginInlining = () => {
11395
+ return [{
11396
+ name: "jsenv:inlining",
11711
11397
  appliesDuring: "*",
11712
- fetchUrlContent: urlInfo => {
11713
- if (!urlInfo.isInline) {
11714
- return null;
11398
+ redirectReference: reference => {
11399
+ const {
11400
+ searchParams
11401
+ } = reference;
11402
+ if (searchParams.has("inline")) {
11403
+ const urlObject = new URL(reference.url);
11404
+ urlObject.searchParams.delete("inline");
11405
+ return urlObject.href;
11715
11406
  }
11716
- return {
11717
- // we want to fetch the original content otherwise we might re-cook
11718
- // content already cooked
11719
- content: urlInfo.originalContent,
11720
- contentType: urlInfo.contentType
11721
- };
11407
+ return null;
11722
11408
  }
11723
- };
11409
+ }, jsenvPluginInliningAsDataUrl(), jsenvPluginInliningIntoHtml()];
11724
11410
  };
11725
11411
 
11726
11412
  const requireFromJsenv = createRequire(import.meta.url);
@@ -15460,6 +15146,7 @@ const babelPluginRelativeImports = babel => {
15460
15146
  * - propagate "?js_module_fallback" query string param on urls
15461
15147
  * - perform conversion from js module to js classic when url uses "?js_module_fallback"
15462
15148
  */
15149
+
15463
15150
  const jsenvPluginJsModuleConversion = ({
15464
15151
  systemJsInjection,
15465
15152
  systemJsClientFileUrl,
@@ -15499,7 +15186,7 @@ const jsenvPluginJsModuleConversion = ({
15499
15186
  return {
15500
15187
  name: "jsenv:js_module_conversion",
15501
15188
  appliesDuring: "*",
15502
- redirectUrl: (reference, context) => {
15189
+ redirectReference: (reference, context) => {
15503
15190
  if (reference.searchParams.has("js_module_fallback")) {
15504
15191
  markAsJsClassicProxy(reference);
15505
15192
  return null;
@@ -15570,6 +15257,7 @@ const jsenvPluginJsModuleConversion = ({
15570
15257
  * - js inside <script type="module"> is transformed into classic js
15571
15258
  * - <link rel="modulepreload"> are converted to <link rel="preload">
15572
15259
  */
15260
+
15573
15261
  const jsenvPluginJsModuleFallbackInsideHtml = ({
15574
15262
  systemJsInjection,
15575
15263
  systemJsClientFileUrl
@@ -15582,7 +15270,7 @@ const jsenvPluginJsModuleFallbackInsideHtml = ({
15582
15270
  return {
15583
15271
  name: "jsenv:js_module_fallback_inside_html",
15584
15272
  appliesDuring: "*",
15585
- redirectUrl: {
15273
+ redirectReference: {
15586
15274
  link_href: (reference, context) => {
15587
15275
  if (context.systemJsTranspilation && reference.subtype === "modulepreload") {
15588
15276
  return turnIntoJsClassicProxy(reference);
@@ -15737,11 +15425,10 @@ const jsenvPluginJsModuleFallbackInsideHtml = ({
15737
15425
  });
15738
15426
  }
15739
15427
  }
15740
- if (mutations.length === 0) {
15741
- return null;
15742
- }
15743
15428
  await Promise.all(mutations.map(mutation => mutation()));
15744
- return stringifyHtmlAst(htmlAst);
15429
+ return stringifyHtmlAst(htmlAst, {
15430
+ cleanupPositionAttributes: context.dev
15431
+ });
15745
15432
  }
15746
15433
  }
15747
15434
  };
@@ -15771,6 +15458,7 @@ const isExpectingJsModule = reference => {
15771
15458
  * transformed into
15772
15459
  * new SharedWorker("shared_worker.js?js_module_fallback", { type: "classic" })
15773
15460
  */
15461
+
15774
15462
  const jsenvPluginJsModuleFallbackOnWorkers = () => {
15775
15463
  const turnIntoJsClassicProxy = reference => {
15776
15464
  reference.mutation = magicSource => {
@@ -15787,7 +15475,7 @@ const jsenvPluginJsModuleFallbackOnWorkers = () => {
15787
15475
  return {
15788
15476
  name: "jsenv:js_module_fallback_on_workers",
15789
15477
  appliesDuring: "*",
15790
- redirectUrl: {
15478
+ redirectReference: {
15791
15479
  js_url: (reference, context) => {
15792
15480
  if (reference.expectedType !== "js_module") {
15793
15481
  return null;
@@ -15928,6 +15616,7 @@ const pathnameToParentPathname = pathname => {
15928
15616
  };
15929
15617
 
15930
15618
  // could be useful: https://url.spec.whatwg.org/#url-miscellaneous
15619
+
15931
15620
  const resolveUrl = (specifier, baseUrl) => {
15932
15621
  if (baseUrl) {
15933
15622
  if (typeof baseUrl !== "string") {
@@ -16459,6 +16148,7 @@ const applyDefaultExtension = ({
16459
16148
  * -> The importmap resolution implemented here takes a shortcut and does the following:
16460
16149
  * - All importmap found are merged into a single one that is applied to every import specifiers
16461
16150
  */
16151
+
16462
16152
  const jsenvPluginImportmap = () => {
16463
16153
  let finalImportmap = null;
16464
16154
  const importmaps = {};
@@ -16478,7 +16168,7 @@ const jsenvPluginImportmap = () => {
16478
16168
  return {
16479
16169
  name: "jsenv:importmap",
16480
16170
  appliesDuring: "*",
16481
- resolveUrl: {
16171
+ resolveReference: {
16482
16172
  js_import: reference => {
16483
16173
  if (!finalImportmap) {
16484
16174
  return null;
@@ -16636,6 +16326,20 @@ const jsenvPluginImportmap = () => {
16636
16326
  };
16637
16327
  };
16638
16328
 
16329
+ const urlTypeFromReference = (reference, context) => {
16330
+ if (reference.type === "sourcemap_comment") {
16331
+ return "sourcemap";
16332
+ }
16333
+ if (reference.injected) {
16334
+ return reference.expectedType;
16335
+ }
16336
+ const parentUrlInfo = context.urlGraph.getUrlInfo(reference.parentUrl);
16337
+ if (parentUrlInfo) {
16338
+ return parentUrlInfo.type;
16339
+ }
16340
+ return "entry_point";
16341
+ };
16342
+
16639
16343
  const isSpecifierForNodeBuiltin = specifier => {
16640
16344
  return specifier.startsWith("node:") || NODE_BUILTIN_MODULE_SPECIFIERS.includes(specifier);
16641
16345
  };
@@ -17591,6 +17295,7 @@ const getExtensionsToTry = (magicExtensions, importer) => {
17591
17295
  * if that comes from node resolution or anything else (not even magic resolution)
17592
17296
  * it should likely be an other plugin happening after the others
17593
17297
  */
17298
+
17594
17299
  const createNodeEsmResolver = ({
17595
17300
  runtimeCompat,
17596
17301
  packageConditions,
@@ -17603,6 +17308,16 @@ const createNodeEsmResolver = ({
17603
17308
  if (reference.type === "package_json") {
17604
17309
  return reference.specifier;
17605
17310
  }
17311
+ if (reference.specifier === "/") {
17312
+ const {
17313
+ mainFilePath,
17314
+ rootDirectoryUrl
17315
+ } = context;
17316
+ return String(new URL(mainFilePath, rootDirectoryUrl));
17317
+ }
17318
+ if (reference.specifier[0] === "/") {
17319
+ return new URL(reference.specifier.slice(1), context.rootDirectoryUrl).href;
17320
+ }
17606
17321
  const parentUrl = reference.baseUrl || reference.parentUrl;
17607
17322
  if (!parentUrl.startsWith("file:")) {
17608
17323
  return new URL(reference.specifier, parentUrl).href;
@@ -17681,126 +17396,64 @@ const addRelationshipWithPackageJson = ({
17681
17396
  }
17682
17397
  };
17683
17398
 
17684
- /*
17685
- * This plugin is responsible to resolve urls except for a few cases:
17686
- * - A custom plugin implements a resolveUrl hook returning something
17687
- * - The reference.type is "filesystem" -> it is handled by jsenv_plugin_file_urls.js
17688
- *
17689
- * By default node esm resolution applies inside js modules
17690
- * and the rest uses the web standard url resolution (new URL):
17691
- * - "http_request"
17692
- * - "entry_point"
17693
- * - "link_href"
17694
- * - "style"
17695
- * - "script"
17696
- * - "a_href"
17697
- * - "iframe_src
17698
- * - "img_src"
17699
- * - "img_srcset"
17700
- * - "source_src"
17701
- * - "source_srcset"
17702
- * - "image_href"
17703
- * - "use_href"
17704
- * - "css_@import"
17705
- * - "css_url"
17706
- * - "js_import"
17707
- * - "js_import_script"
17708
- * - "js_url"
17709
- * - "js_inline_content"
17710
- * - "sourcemap_comment"
17711
- * - "webmanifest_icon_src"
17712
- * - "package_json"
17713
- */
17714
- const jsenvPluginUrlResolution = ({
17715
- runtimeCompat,
17716
- defaultFileUrl,
17717
- urlResolution
17718
- }) => {
17719
- const resolveUrlUsingWebResolution = reference => {
17720
- return new URL(reference.specifier,
17721
- // baseUrl happens second argument to new URL() is different from
17722
- // import.meta.url or document.currentScript.src
17723
- reference.baseUrl || reference.parentUrl).href;
17724
- };
17399
+ const jsenvPluginNodeEsmResolution = (resolutionConfig = {}) => {
17400
+ let nodeEsmResolverDefault;
17725
17401
  const resolvers = {};
17726
- Object.keys(urlResolution).forEach(urlType => {
17727
- const resolver = urlResolution[urlType];
17728
- if (typeof resolver !== "object") {
17729
- throw new Error(`urlResolution values must be objects, got ${resolver} on "${urlType}"`);
17730
- }
17731
- let {
17732
- web,
17733
- node_esm,
17734
- ...rest
17735
- } = resolver;
17736
- const unexpectedKeys = Object.keys(rest);
17737
- if (unexpectedKeys.length) {
17738
- throw new TypeError(`${unexpectedKeys.join(",")}: there is no such configuration on "${urlType}"`);
17739
- }
17740
- if (node_esm === undefined) {
17741
- node_esm = urlType === "js_import";
17742
- }
17743
- if (web === undefined) {
17744
- web = true;
17745
- }
17746
- if (node_esm) {
17747
- if (node_esm === true) node_esm = {};
17402
+ Object.keys(resolutionConfig).forEach(urlType => {
17403
+ const config = resolutionConfig[urlType];
17404
+ if (config === true) {
17405
+ resolvers[urlType] = (...args) => nodeEsmResolverDefault(...args);
17406
+ } else if (config === false) {
17407
+ resolvers[urlType] = () => null;
17408
+ } else if (typeof config === "object") {
17748
17409
  const {
17410
+ runtimeCompat,
17749
17411
  packageConditions,
17750
- preservesSymlink
17751
- } = node_esm;
17412
+ preservesSymlink,
17413
+ ...rest
17414
+ } = config;
17415
+ const unexpectedKeys = Object.keys(rest);
17416
+ if (unexpectedKeys.length) {
17417
+ throw new TypeError(`${unexpectedKeys.join(",")}: there is no such configuration on "${urlType}"`);
17418
+ }
17752
17419
  resolvers[urlType] = createNodeEsmResolver({
17753
17420
  runtimeCompat,
17754
17421
  packageConditions,
17755
17422
  preservesSymlink
17756
17423
  });
17757
- } else if (web) {
17758
- resolvers[urlType] = resolveUrlUsingWebResolution;
17424
+ } else {
17425
+ throw new TypeError(`config must be true, false or an object, got ${config} on "${urlType}"`);
17759
17426
  }
17760
17427
  });
17761
- const nodeEsmResolverDefault = createNodeEsmResolver({
17762
- runtimeCompat,
17763
- preservesSymlink: true
17764
- });
17765
- if (!resolvers.js_module) {
17766
- resolvers.js_module = nodeEsmResolverDefault;
17767
- }
17768
- if (!resolvers.js_classic) {
17769
- resolvers.js_classic = (reference, context) => {
17770
- if (reference.subtype === "self_import_scripts_arg") {
17771
- return nodeEsmResolverDefault(reference, context);
17772
- }
17773
- return resolveUrlUsingWebResolution(reference);
17774
- };
17775
- }
17776
- if (!resolvers["*"]) {
17777
- resolvers["*"] = resolveUrlUsingWebResolution;
17778
- }
17779
17428
  return {
17780
- name: "jsenv:url_resolution",
17429
+ name: "jsenv:node_esm_resolution",
17781
17430
  appliesDuring: "*",
17782
- resolveUrl: (reference, context) => {
17783
- if (reference.specifier === "/") {
17784
- return String(defaultFileUrl);
17785
- }
17786
- if (reference.specifier[0] === "/") {
17787
- return new URL(reference.specifier.slice(1), context.rootDirectoryUrl).href;
17788
- }
17789
- if (reference.type === "sourcemap_comment") {
17790
- return resolveUrlUsingWebResolution(reference);
17431
+ init: ({
17432
+ runtimeCompat
17433
+ }) => {
17434
+ nodeEsmResolverDefault = createNodeEsmResolver({
17435
+ runtimeCompat,
17436
+ preservesSymlink: true
17437
+ });
17438
+ if (!resolvers.js_module) {
17439
+ resolvers.js_module = nodeEsmResolverDefault;
17791
17440
  }
17792
- let urlType;
17793
- if (reference.injected) {
17794
- urlType = reference.expectedType;
17795
- } else {
17796
- const parentUrlInfo = context.urlGraph.getUrlInfo(reference.parentUrl);
17797
- urlType = parentUrlInfo ? parentUrlInfo.type : "entry_point";
17441
+ if (!resolvers.js_classic) {
17442
+ resolvers.js_classic = (reference, context) => {
17443
+ if (reference.subtype === "self_import_scripts_arg") {
17444
+ return nodeEsmResolverDefault(reference, context);
17445
+ }
17446
+ return null;
17447
+ };
17798
17448
  }
17799
- const resolver = resolvers[urlType] || resolvers["*"];
17800
- return resolver(reference, context);
17449
+ },
17450
+ resolveReference: (reference, context) => {
17451
+ const urlType = urlTypeFromReference(reference, context);
17452
+ const resolver = resolvers[urlType];
17453
+ return resolver ? resolver(reference, context) : null;
17801
17454
  },
17802
17455
  // when specifier is prefixed by "file:///@ignore/"
17803
- // we return an empty js module (used by node esm)
17456
+ // we return an empty js module
17804
17457
  fetchUrlContent: urlInfo => {
17805
17458
  if (urlInfo.url.startsWith("file:///@ignore/")) {
17806
17459
  return {
@@ -17814,11 +17467,50 @@ const jsenvPluginUrlResolution = ({
17814
17467
  };
17815
17468
  };
17816
17469
 
17817
- const jsenvPluginUrlVersion = () => {
17470
+ const jsenvPluginWebResolution = (resolutionConfig = {}) => {
17471
+ const resolvers = {};
17472
+ const resolveUsingWebResolution = (reference, context) => {
17473
+ if (reference.specifier === "/") {
17474
+ const {
17475
+ mainFilePath,
17476
+ rootDirectoryUrl
17477
+ } = context;
17478
+ return String(new URL(mainFilePath, rootDirectoryUrl));
17479
+ }
17480
+ if (reference.specifier[0] === "/") {
17481
+ return new URL(reference.specifier.slice(1), context.rootDirectoryUrl).href;
17482
+ }
17483
+ return new URL(reference.specifier,
17484
+ // baseUrl happens second argument to new URL() is different from
17485
+ // import.meta.url or document.currentScript.src
17486
+ reference.baseUrl || reference.parentUrl).href;
17487
+ };
17488
+ Object.keys(resolutionConfig).forEach(urlType => {
17489
+ const config = resolutionConfig[urlType];
17490
+ if (config === true) {
17491
+ resolvers[urlType] = resolveUsingWebResolution;
17492
+ } else if (config === false) {
17493
+ resolvers[urlType] = () => null;
17494
+ } else {
17495
+ throw new TypeError(`config must be true or false, got ${config} on "${urlType}"`);
17496
+ }
17497
+ });
17498
+ return {
17499
+ name: "jsenv:web_resolution",
17500
+ appliesDuring: "*",
17501
+ resolveReference: (reference, context) => {
17502
+ const urlType = urlTypeFromReference(reference, context);
17503
+ const resolver = resolvers[urlType];
17504
+ return resolver ? resolver(reference, context) : resolveUsingWebResolution(reference, context);
17505
+ }
17506
+ };
17507
+ };
17508
+
17509
+ const jsenvPluginVersionSearchParam = () => {
17818
17510
  return {
17819
- name: "jsenv:url_version",
17511
+ name: "jsenv:version_search_param",
17820
17512
  appliesDuring: "dev",
17821
- redirectUrl: reference => {
17513
+ redirectReference: reference => {
17822
17514
  // "v" search param goal is to enable long-term cache
17823
17515
  // for server response headers
17824
17516
  // it is also used by hmr to bypass browser cache
@@ -17834,7 +17526,7 @@ const jsenvPluginUrlVersion = () => {
17834
17526
  }
17835
17527
  return null;
17836
17528
  },
17837
- transformUrlSearchParams: reference => {
17529
+ transformReferenceSearchParams: reference => {
17838
17530
  if (!reference.version) {
17839
17531
  return null;
17840
17532
  }
@@ -17857,7 +17549,7 @@ const jsenvPluginFileUrls = ({
17857
17549
  return [{
17858
17550
  name: "jsenv:file_url_resolution",
17859
17551
  appliesDuring: "*",
17860
- redirectUrl: reference => {
17552
+ redirectReference: reference => {
17861
17553
  // http, https, data, about, ...
17862
17554
  if (!reference.url.startsWith("file:")) {
17863
17555
  return null;
@@ -17936,7 +17628,7 @@ const jsenvPluginFileUrls = ({
17936
17628
  }, {
17937
17629
  name: "jsenv:filesystem_resolution",
17938
17630
  appliesDuring: "*",
17939
- resolveUrl: {
17631
+ resolveReference: {
17940
17632
  filesystem: (reference, context) => {
17941
17633
  const {
17942
17634
  parentUrl
@@ -17952,14 +17644,14 @@ const jsenvPluginFileUrls = ({
17952
17644
  // so absolute file urls needs to be relativized
17953
17645
  // during build it's fine to use file:// urls
17954
17646
  appliesDuring: "dev",
17955
- resolveUrl: reference => {
17647
+ resolveReference: reference => {
17956
17648
  if (reference.specifier.startsWith("/@fs/")) {
17957
17649
  const fsRootRelativeUrl = reference.specifier.slice("/@fs/".length);
17958
17650
  return `file:///${fsRootRelativeUrl}`;
17959
17651
  }
17960
17652
  return null;
17961
17653
  },
17962
- formatUrl: (reference, context) => {
17654
+ formatReference: (reference, context) => {
17963
17655
  if (!reference.generatedUrl.startsWith("file:")) {
17964
17656
  return null;
17965
17657
  }
@@ -18009,7 +17701,7 @@ const jsenvPluginHttpUrls = () => {
18009
17701
  return {
18010
17702
  name: "jsenv:http_urls",
18011
17703
  appliesDuring: "*",
18012
- redirectUrl: reference => {
17704
+ redirectReference: reference => {
18013
17705
  if (reference.url.startsWith("http:") || reference.url.startsWith("https:")) {
18014
17706
  reference.shouldHandle = false;
18015
17707
  }
@@ -18056,6 +17748,7 @@ const jsenvPluginHttpUrls = () => {
18056
17748
  * While dynamic import will work just fine
18057
17749
  * and create a variable named "undefined"
18058
17750
  */
17751
+
18059
17752
  const injectSupervisorIntoJs = async ({
18060
17753
  webServer,
18061
17754
  content,
@@ -18284,6 +17977,7 @@ const createSupervisionCall = ({
18284
17977
  * -> No changes required on js source code, it's only the HTML that is modified
18285
17978
  * - Also allow to catch syntax errors and export missing
18286
17979
  */
17980
+
18287
17981
  const supervisorFileUrl$1 = new URL("./js/supervisor.js", import.meta.url).href;
18288
17982
  const injectSupervisorIntoHTML = async ({
18289
17983
  content,
@@ -18517,6 +18211,7 @@ const generateCodeToSuperviseScriptWithSrc = ({
18517
18211
  /*
18518
18212
  * This plugin provides a way for jsenv to know when js execution is done
18519
18213
  */
18214
+
18520
18215
  const supervisorFileUrl = new URL("./js/supervisor.js", import.meta.url).href;
18521
18216
  const jsenvPluginSupervisor = ({
18522
18217
  logs = false,
@@ -18723,6 +18418,7 @@ const jsenvPluginSupervisor = ({
18723
18418
  * - __dirname
18724
18419
  * - global
18725
18420
  */
18421
+
18726
18422
  const jsenvPluginCommonJsGlobals = () => {
18727
18423
  const transformCommonJsGlobals = async (urlInfo, context) => {
18728
18424
  if (!urlInfo.content.includes("process.env.NODE_ENV") && !urlInfo.content.includes("__filename") && !urlInfo.content.includes("__dirname")) {
@@ -18874,6 +18570,7 @@ const babelPluginMetadataExpressionPaths = (babel, {
18874
18570
  * - left as is to be evaluated to undefined (import.meta.build but it's the dev server)
18875
18571
  * - replaced by undefined (import.meta.dev but it's build; the goal is to ensure it's tree-shaked)
18876
18572
  */
18573
+
18877
18574
  const jsenvPluginImportMetaScenarios = () => {
18878
18575
  return {
18879
18576
  name: "jsenv:import_meta_scenario",
@@ -19004,6 +18701,7 @@ const replacePlaceholders = (urlInfo, replacements) => {
19004
18701
  * - __build__
19005
18702
  * A global will be injected with true/false when needed
19006
18703
  */
18704
+
19007
18705
  const jsenvPluginGlobalScenarios = () => {
19008
18706
  const transformIfNeeded = (urlInfo, context) => {
19009
18707
  return replacePlaceholders(urlInfo, {
@@ -19090,6 +18788,7 @@ const versionToBits = version => {
19090
18788
  * do not support import assertions
19091
18789
  * But for now (as it is simpler) we let the browser throw the error
19092
18790
  */
18791
+
19093
18792
  const jsenvPluginImportAssertions = ({
19094
18793
  json = "auto",
19095
18794
  css = "auto",
@@ -19155,7 +18854,7 @@ const jsenvPluginImportAssertions = ({
19155
18854
  transpilations.text = true;
19156
18855
  }
19157
18856
  },
19158
- redirectUrl: (reference, context) => {
18857
+ redirectReference: (reference, context) => {
19159
18858
  if (!reference.assert) {
19160
18859
  return null;
19161
18860
  }
@@ -19250,9 +18949,9 @@ const jsenvPluginAsModules = () => {
19250
18949
  canUseTemplateString: true
19251
18950
  });
19252
18951
  return {
19253
- content: `import { InlineContent } from ${JSON.stringify(inlineContentClientFileUrl)}
18952
+ content: `import ${JSON.stringify(inlineContentClientFileUrl)}
19254
18953
 
19255
- const inlineContent = new InlineContent(${cssText}, { type: "text/css" })
18954
+ const inlineContent = new __InlineContent__(${cssText}, { type: "text/css" })
19256
18955
  const stylesheet = new CSSStyleSheet()
19257
18956
  stylesheet.replaceSync(inlineContent.text)
19258
18957
  export default stylesheet`,
@@ -19362,11 +19061,12 @@ const babelPluginReplaceTopLevelThis = () => {
19362
19061
  * This plugin fix this issue by rewriting top level this into window
19363
19062
  * and can be used like this for instance import("hls?as_js_module")
19364
19063
  */
19064
+
19365
19065
  const jsenvPluginAsJsModule = () => {
19366
19066
  return {
19367
19067
  name: "jsenv:as_js_module",
19368
19068
  appliesDuring: "*",
19369
- redirectUrl: reference => {
19069
+ redirectReference: reference => {
19370
19070
  if (reference.searchParams.has("as_js_module")) {
19371
19071
  reference.expectedType = "js_module";
19372
19072
  const filename = urlToFilename$1(reference.url);
@@ -20285,7 +19985,7 @@ const jsenvPluginBabel = ({
20285
19985
  return {
20286
19986
  name: "jsenv:babel",
20287
19987
  appliesDuring: "*",
20288
- finalizeUrlContent: {
19988
+ transformUrlContent: {
20289
19989
  js_classic: transformWithBabel,
20290
19990
  js_module: transformWithBabel
20291
19991
  }
@@ -20427,6 +20127,7 @@ const jsenvPluginImportMetaResolve = () => {
20427
20127
  * Anything that is not standard (import.meta.dev for instance) is outside the scope
20428
20128
  * of this plugin
20429
20129
  */
20130
+
20430
20131
  const jsenvPluginTranspilation = ({
20431
20132
  importAssertions = true,
20432
20133
  css = true,
@@ -20819,7 +20520,7 @@ const jsenvPluginHmr = () => {
20819
20520
  return {
20820
20521
  name: "jsenv:hmr",
20821
20522
  appliesDuring: "dev",
20822
- redirectUrl: reference => {
20523
+ redirectReference: reference => {
20823
20524
  if (!reference.searchParams.has("hmr")) {
20824
20525
  reference.data.hmr = false;
20825
20526
  return null;
@@ -20834,7 +20535,7 @@ const jsenvPluginHmr = () => {
20834
20535
  urlObject.searchParams.delete("v");
20835
20536
  return urlObject.href;
20836
20537
  },
20837
- transformUrlSearchParams: (reference, context) => {
20538
+ transformReferenceSearchParams: (reference, context) => {
20838
20539
  if (reference.type === "package_json") {
20839
20540
  // maybe the if above shoulb be .isImplicit but it's just a detail anyway
20840
20541
  return null;
@@ -21137,72 +20838,6 @@ const jsenvPluginCacheControl = ({
21137
20838
  };
21138
20839
  const SECONDS_IN_30_DAYS$1 = 60 * 60 * 24 * 30;
21139
20840
 
21140
- const explorerHtmlFileUrl = String(new URL("./html/explorer.html", import.meta.url));
21141
- const jsenvPluginExplorer = ({
21142
- groups = {
21143
- src: {
21144
- "./**/*.html": true,
21145
- "./**/*.test.html": false
21146
- },
21147
- tests: {
21148
- "./**/*.test.html": true
21149
- }
21150
- }
21151
- }) => {
21152
- const faviconClientFileUrl = new URL("./other/jsenv.png", import.meta.url);
21153
- return {
21154
- name: "jsenv:explorer",
21155
- appliesDuring: "dev",
21156
- transformUrlContent: {
21157
- html: async (urlInfo, context) => {
21158
- if (urlInfo.url !== explorerHtmlFileUrl) {
21159
- return null;
21160
- }
21161
- let html = urlInfo.content;
21162
- if (html.includes("ignore:FAVICON_HREF")) {
21163
- html = html.replace("ignore:FAVICON_HREF", DATA_URL.stringify({
21164
- contentType: CONTENT_TYPE.fromUrlExtension(faviconClientFileUrl),
21165
- base64Flag: true,
21166
- data: readFileSync$1(new URL(faviconClientFileUrl)).toString("base64")
21167
- }));
21168
- }
21169
- if (html.includes("SERVER_PARAMS")) {
21170
- const associationsForExplorable = {};
21171
- Object.keys(groups).forEach(groupName => {
21172
- const groupConfig = groups[groupName];
21173
- associationsForExplorable[groupName] = {
21174
- "**/.jsenv/": false,
21175
- // avoid visting .jsenv directory in jsenv itself
21176
- ...groupConfig
21177
- };
21178
- });
21179
- const matchingFileResultArray = await collectFiles({
21180
- directoryUrl: context.rootDirectoryUrl,
21181
- associations: associationsForExplorable,
21182
- predicate: meta => Object.keys(meta).some(group => Boolean(meta[group]))
21183
- });
21184
- const files = matchingFileResultArray.map(({
21185
- relativeUrl,
21186
- meta
21187
- }) => ({
21188
- relativeUrl,
21189
- meta
21190
- }));
21191
- html = html.replace("SERVER_PARAMS", JSON.stringify({
21192
- rootDirectoryUrl: context.rootDirectoryUrl,
21193
- groups,
21194
- files
21195
- }, null, " "));
21196
- Object.assign(urlInfo.headers, {
21197
- "cache-control": "no-store"
21198
- });
21199
- }
21200
- return html;
21201
- }
21202
- }
21203
- };
21204
- };
21205
-
21206
20841
  const jsenvPluginRibbon = ({
21207
20842
  rootDirectoryUrl,
21208
20843
  htmlInclude = "/**/*.html"
@@ -21255,10 +20890,10 @@ injectRibbon(${paramsJson});`
21255
20890
 
21256
20891
  const getCorePlugins = ({
21257
20892
  rootDirectoryUrl,
21258
- defaultFileUrl,
21259
20893
  runtimeCompat,
21260
- urlAnalysis = {},
21261
- urlResolution = {},
20894
+ referenceAnalysis = {},
20895
+ nodeEsmResolution = {},
20896
+ webResolution = {},
21262
20897
  fileSystemMagicRedirection,
21263
20898
  directoryReferenceAllowed,
21264
20899
  supervisor,
@@ -21267,14 +20902,10 @@ const getCorePlugins = ({
21267
20902
  clientAutoreload = false,
21268
20903
  clientFileChangeCallbackList,
21269
20904
  clientFilesPruneCallbackList,
21270
- explorer,
21271
20905
  cacheControl,
21272
20906
  scenarioPlaceholders = true,
21273
20907
  ribbon = true
21274
20908
  } = {}) => {
21275
- if (explorer === true) {
21276
- explorer = {};
21277
- }
21278
20909
  if (cacheControl === true) {
21279
20910
  cacheControl = {};
21280
20911
  }
@@ -21290,30 +20921,25 @@ const getCorePlugins = ({
21290
20921
  if (ribbon === true) {
21291
20922
  ribbon = {};
21292
20923
  }
21293
- return [jsenvPluginUrlAnalysis({
21294
- rootDirectoryUrl,
21295
- ...urlAnalysis
21296
- }), jsenvPluginTranspilation(transpilation), jsenvPluginImportmap(),
21297
- // before node esm to handle bare specifiers
21298
- // + before node esm to handle importmap before inline content
21299
- jsenvPluginInlineContentAnalysis(),
21300
- // before "file urls" to resolve and load inline urls
21301
- ...(inlining ? [jsenvPluginInlining()] : []), ...(supervisor ? [jsenvPluginSupervisor(supervisor)] : []),
20924
+ return [jsenvPluginReferenceAnalysis(referenceAnalysis), jsenvPluginTranspilation(transpilation), jsenvPluginImportmap(), ...(inlining ? [jsenvPluginInlining()] : []), ...(supervisor ? [jsenvPluginSupervisor(supervisor)] : []),
21302
20925
  // after inline as it needs inline script to be cooked
20926
+
20927
+ /* When resolving references the following applies by default:
20928
+ - http urls are resolved by jsenvPluginHttpUrls
20929
+ - reference.type === "filesystem" -> resolved by jsenv_plugin_file_urls.js
20930
+ - reference inside a js module -> resolved by node esm
20931
+ - All the rest uses web standard url resolution
20932
+ */
21303
20933
  jsenvPluginFileUrls({
21304
20934
  directoryReferenceAllowed,
21305
20935
  ...fileSystemMagicRedirection
21306
- }), jsenvPluginHttpUrls(), jsenvPluginUrlResolution({
21307
- runtimeCompat,
21308
- defaultFileUrl,
21309
- urlResolution
21310
- }), jsenvPluginUrlVersion(), jsenvPluginCommonJsGlobals(), jsenvPluginImportMetaScenarios(), ...(scenarioPlaceholders ? [jsenvPluginGlobalScenarios()] : []), jsenvPluginNodeRuntime({
20936
+ }), jsenvPluginHttpUrls(), ...(nodeEsmResolution ? [jsenvPluginNodeEsmResolution(nodeEsmResolution)] : []), jsenvPluginWebResolution(webResolution), jsenvPluginVersionSearchParam(), jsenvPluginCommonJsGlobals(), jsenvPluginImportMetaScenarios(), ...(scenarioPlaceholders ? [jsenvPluginGlobalScenarios()] : []), jsenvPluginNodeRuntime({
21311
20937
  runtimeCompat
21312
20938
  }), jsenvPluginImportMetaHot(), ...(clientAutoreload ? [jsenvPluginAutoreload({
21313
20939
  ...clientAutoreload,
21314
20940
  clientFileChangeCallbackList,
21315
20941
  clientFilesPruneCallbackList
21316
- })] : []), ...(cacheControl ? [jsenvPluginCacheControl(cacheControl)] : []), ...(explorer ? [jsenvPluginExplorer(explorer)] : []), ...(ribbon ? [jsenvPluginRibbon({
20942
+ })] : []), ...(cacheControl ? [jsenvPluginCacheControl(cacheControl)] : []), ...(ribbon ? [jsenvPluginRibbon({
21317
20943
  rootDirectoryUrl,
21318
20944
  ...ribbon
21319
20945
  })] : [])];
@@ -21522,6 +21148,7 @@ const determineDirectoryPath = ({
21522
21148
  };
21523
21149
 
21524
21150
  // https://bundlers.tooling.report/hashing/avoid-cascade/
21151
+
21525
21152
  const injectVersionMappingsAsGlobal = async ({
21526
21153
  urlInfo,
21527
21154
  kitchen,
@@ -21650,6 +21277,7 @@ const createVersionGenerator = () => {
21650
21277
  * - injecting urls into service workers
21651
21278
  */
21652
21279
 
21280
+
21653
21281
  // default runtimeCompat corresponds to
21654
21282
  // "we can keep <script type="module"> intact":
21655
21283
  // so script_type_module + dynamic_import + import_meta
@@ -21669,11 +21297,11 @@ const defaultRuntimeCompat = {
21669
21297
  * @param {Object} buildParameters
21670
21298
  * @param {string|url} buildParameters.sourceDirectoryUrl
21671
21299
  * Directory containing source files
21300
+ * @param {string|url} buildParameters.buildDirectoryUrl
21301
+ * Directory where optimized files will be written
21672
21302
  * @param {object} buildParameters.entryPoints
21673
21303
  * Object where keys are paths to source files and values are their future name in the build directory.
21674
21304
  * Keys are relative to sourceDirectoryUrl
21675
- * @param {string|url} buildParameters.buildDirectoryUrl
21676
- * Directory where optimized files will be written
21677
21305
  * @param {object} buildParameters.runtimeCompat
21678
21306
  * Code generated will be compatible with these runtimes
21679
21307
  * @param {string} [buildParameters.assetsDirectory=""]
@@ -21699,16 +21327,15 @@ const build = async ({
21699
21327
  handleSIGINT = true,
21700
21328
  logLevel = "info",
21701
21329
  sourceDirectoryUrl,
21702
- entryPoints = {},
21703
21330
  buildDirectoryUrl,
21331
+ entryPoints = {},
21704
21332
  assetsDirectory = "",
21705
21333
  runtimeCompat = defaultRuntimeCompat,
21706
21334
  base = runtimeCompat.node ? "./" : "/",
21707
21335
  plugins = [],
21708
- sourcemaps = "none",
21709
- sourcemapsSourcesContent,
21710
- urlAnalysis = {},
21711
- urlResolution,
21336
+ referenceAnalysis = {},
21337
+ nodeEsmResolution,
21338
+ webResolution,
21712
21339
  fileSystemMagicRedirection,
21713
21340
  directoryReferenceAllowed,
21714
21341
  scenarioPlaceholders,
@@ -21722,6 +21349,8 @@ const build = async ({
21722
21349
  cooldownBetweenFileEvents,
21723
21350
  watch = false,
21724
21351
  directoryToClean,
21352
+ sourcemaps = "none",
21353
+ sourcemapsSourcesContent,
21725
21354
  writeOnFileSystem = true,
21726
21355
  outDirectoryUrl,
21727
21356
  assetManifest = versioningMethod === "filename",
@@ -21844,23 +21473,24 @@ build ${entryPointKeys.length} entry points`);
21844
21473
  ...contextSharedDuringBuild,
21845
21474
  plugins: [...plugins, {
21846
21475
  appliesDuring: "build",
21847
- fetchUrlContent: (urlInfo, context) => {
21848
- if (context.reference.original) {
21849
- rawRedirections.set(context.reference.original.url, context.reference.url);
21850
- }
21851
- },
21852
- formatUrl: reference => {
21476
+ formatReference: reference => {
21853
21477
  if (!reference.shouldHandle) {
21854
21478
  return `ignore:${reference.specifier}`;
21855
21479
  }
21856
21480
  return null;
21481
+ },
21482
+ fetchUrlContent: (urlInfo, context) => {
21483
+ if (context.reference.original) {
21484
+ rawRedirections.set(context.reference.original.url, context.reference.url);
21485
+ }
21857
21486
  }
21858
21487
  }, ...getCorePlugins({
21859
21488
  rootDirectoryUrl: sourceDirectoryUrl,
21860
21489
  urlGraph: rawGraph,
21861
21490
  runtimeCompat,
21862
- urlAnalysis,
21863
- urlResolution,
21491
+ referenceAnalysis,
21492
+ nodeEsmResolution,
21493
+ webResolution,
21864
21494
  fileSystemMagicRedirection,
21865
21495
  directoryReferenceAllowed,
21866
21496
  transpilation: {
@@ -21894,10 +21524,6 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
21894
21524
  const bundleUrlInfos = {};
21895
21525
  const bundlers = {};
21896
21526
  const finalGraph = createUrlGraph();
21897
- const urlAnalysisPlugin = jsenvPluginUrlAnalysis({
21898
- rootDirectoryUrl: sourceDirectoryUrl,
21899
- ...urlAnalysis
21900
- });
21901
21527
  const finalGraphKitchen = createKitchen({
21902
21528
  logLevel,
21903
21529
  rootDirectoryUrl: buildDirectoryUrl,
@@ -21905,14 +21531,15 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
21905
21531
  build: true,
21906
21532
  runtimeCompat,
21907
21533
  ...contextSharedDuringBuild,
21908
- plugins: [urlAnalysisPlugin, ...(lineBreakNormalization ? [jsenvPluginLineBreakNormalization()] : []), jsenvPluginJsModuleFallback({
21909
- systemJsInjection: true
21910
- }), jsenvPluginInlineContentAnalysis({
21534
+ plugins: [jsenvPluginReferenceAnalysis({
21535
+ ...referenceAnalysis,
21911
21536
  fetchInlineUrls: false
21537
+ }), ...(lineBreakNormalization ? [jsenvPluginLineBreakNormalization()] : []), jsenvPluginJsModuleFallback({
21538
+ systemJsInjection: true
21912
21539
  }), jsenvPluginInlining(), {
21913
21540
  name: "jsenv:build",
21914
21541
  appliesDuring: "build",
21915
- resolveUrl: reference => {
21542
+ resolveReference: reference => {
21916
21543
  const getUrl = () => {
21917
21544
  if (reference.type === "filesystem") {
21918
21545
  const parentRawUrl = buildDirectoryRedirections.get(reference.parentUrl);
@@ -21930,8 +21557,8 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
21930
21557
  url = bundleInternalRedirections.get(url) || url;
21931
21558
  return url;
21932
21559
  },
21933
- // redirecting urls into the build directory
21934
- redirectUrl: reference => {
21560
+ // redirecting references into the build directory
21561
+ redirectReference: reference => {
21935
21562
  if (!reference.url.startsWith("file:")) {
21936
21563
  return null;
21937
21564
  }
@@ -21985,7 +21612,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
21985
21612
  const urlBeforeRedirect = reference.original.url;
21986
21613
  const urlAfterRedirect = reference.url;
21987
21614
  const isEntryPoint = reference.isEntryPoint || isWebWorkerEntryPointReference(reference);
21988
- // the url info do not exists yet (it will be created after this "redirectUrl" hook)
21615
+ // the url info do not exists yet (it will be created after this "redirectReference" hook)
21989
21616
  // And the content will be generated when url is cooked by url graph loader.
21990
21617
  // Here we just want to reserve an url for that file
21991
21618
  const urlInfo = {
@@ -22056,7 +21683,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
22056
21683
  });
22057
21684
  return buildUrl;
22058
21685
  },
22059
- formatUrl: reference => {
21686
+ formatReference: reference => {
22060
21687
  if (!reference.generatedUrl.startsWith("file:")) {
22061
21688
  if (!versioning && reference.generatedUrl.startsWith("ignore:")) {
22062
21689
  return reference.generatedUrl.slice("ignore:".length);
@@ -22148,7 +21775,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
22148
21775
  }, {
22149
21776
  name: "jsenv:optimize",
22150
21777
  appliesDuring: "build",
22151
- finalizeUrlContent: async (urlInfo, context) => {
21778
+ transformUrlContent: async (urlInfo, context) => {
22152
21779
  await rawGraphKitchen.pluginController.callAsyncHooks("optimizeUrlContent", urlInfo, context, async optimizeReturnValue => {
22153
21780
  await finalGraphKitchen.urlInfoTransformer.applyFinalTransformations(urlInfo, optimizeReturnValue);
22154
21781
  });
@@ -22412,6 +22039,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
22412
22039
  if (!versioning) {
22413
22040
  break inject_version_in_urls;
22414
22041
  }
22042
+ logger.debug("versioning start");
22415
22043
  const versioningTask = createTaskLog("inject version in urls", {
22416
22044
  disabled: logger.levels.debug || !logger.levels.info
22417
22045
  });
@@ -22584,20 +22212,26 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
22584
22212
  build: true,
22585
22213
  runtimeCompat,
22586
22214
  ...contextSharedDuringBuild,
22587
- plugins: [urlAnalysisPlugin, jsenvPluginInlineContentAnalysis({
22215
+ plugins: [jsenvPluginReferenceAnalysis({
22216
+ ...referenceAnalysis,
22588
22217
  fetchInlineUrls: false,
22589
- analyzeConvertedScripts: true,
22218
+ inlineConvertedScript: true,
22590
22219
  // to be able to version their urls
22591
22220
  allowEscapeForVersioning: true
22592
22221
  }), {
22593
22222
  name: "jsenv:versioning",
22594
22223
  appliesDuring: "build",
22595
- resolveUrl: reference => {
22224
+ resolveReference: reference => {
22596
22225
  const buildUrl = buildUrls.get(reference.specifier);
22597
22226
  if (buildUrl) {
22598
22227
  return buildUrl;
22599
22228
  }
22600
- const urlObject = new URL(reference.specifier, reference.baseUrl || reference.parentUrl);
22229
+ let urlObject;
22230
+ if (reference.specifier[0] === "/") {
22231
+ urlObject = new URL(reference.specifier.slice(1), buildDirectoryUrl);
22232
+ } else {
22233
+ urlObject = new URL(reference.specifier, reference.baseUrl || reference.parentUrl);
22234
+ }
22601
22235
  const url = urlObject.href;
22602
22236
  // during versioning we revisit the deps
22603
22237
  // but the code used to enforce trailing slash on directories
@@ -22612,7 +22246,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
22612
22246
  }
22613
22247
  return url;
22614
22248
  },
22615
- formatUrl: reference => {
22249
+ formatReference: reference => {
22616
22250
  if (!reference.shouldHandle) {
22617
22251
  if (reference.generatedUrl.startsWith("ignore:")) {
22618
22252
  return reference.generatedUrl.slice("ignore:".length);
@@ -23154,6 +22788,7 @@ const WEB_URL_CONVERTER = {
23154
22788
  * This plugin is very special because it is here
23155
22789
  * to provide "serverEvents" used by other plugins
23156
22790
  */
22791
+
23157
22792
  const serverEventsClientFileUrl = new URL("./js/server_events_client.js", import.meta.url).href;
23158
22793
  const jsenvPluginServerEventsClientInjection = () => {
23159
22794
  return {
@@ -23218,14 +22853,14 @@ const createFileService = ({
23218
22853
  sourceFilesConfig,
23219
22854
  runtimeCompat,
23220
22855
  plugins,
23221
- urlAnalysis,
23222
- urlResolution,
22856
+ referenceAnalysis,
22857
+ nodeEsmResolution,
22858
+ webResolution,
23223
22859
  fileSystemMagicRedirection,
23224
22860
  supervisor,
23225
22861
  transpilation,
23226
22862
  clientAutoreload,
23227
22863
  cooldownBetweenFileEvents,
23228
- explorer,
23229
22864
  cacheControl,
23230
22865
  ribbon,
23231
22866
  sourcemaps,
@@ -23282,18 +22917,11 @@ const createFileService = ({
23282
22917
  const clientRuntimeCompat = {
23283
22918
  [runtimeName]: runtimeVersion
23284
22919
  };
23285
- let defaultFileUrl;
23286
- if (explorer) {
23287
- defaultFileUrl = String(explorerHtmlFileUrl);
23288
- } else if (sourceMainFilePath) {
23289
- defaultFileUrl = String(new URL(sourceMainFilePath, sourceDirectoryUrl));
23290
- } else {
23291
- defaultFileUrl = String(new URL("./index.html", sourceDirectoryUrl));
23292
- }
23293
22920
  const kitchen = createKitchen({
23294
22921
  signal,
23295
22922
  logLevel,
23296
22923
  rootDirectoryUrl: sourceDirectoryUrl,
22924
+ mainFilePath: sourceMainFilePath,
23297
22925
  urlGraph,
23298
22926
  dev: true,
23299
22927
  runtimeCompat,
@@ -23301,17 +22929,16 @@ const createFileService = ({
23301
22929
  systemJsTranspilation: !RUNTIME_COMPAT.isSupported(clientRuntimeCompat, "script_type_module") || !RUNTIME_COMPAT.isSupported(clientRuntimeCompat, "import_dynamic") || !RUNTIME_COMPAT.isSupported(clientRuntimeCompat, "import_meta"),
23302
22930
  plugins: [...plugins, ...getCorePlugins({
23303
22931
  rootDirectoryUrl: sourceDirectoryUrl,
23304
- defaultFileUrl,
23305
22932
  runtimeCompat,
23306
- urlAnalysis,
23307
- urlResolution,
22933
+ referenceAnalysis,
22934
+ nodeEsmResolution,
22935
+ webResolution,
23308
22936
  fileSystemMagicRedirection,
23309
22937
  supervisor,
23310
22938
  transpilation,
23311
22939
  clientAutoreload,
23312
22940
  clientFileChangeCallbackList,
23313
22941
  clientFilesPruneCallbackList,
23314
- explorer,
23315
22942
  cacheControl,
23316
22943
  ribbon
23317
22944
  })],
@@ -23619,7 +23246,7 @@ const inferParentFromRequest = (request, sourceDirectoryUrl) => {
23619
23246
  */
23620
23247
  const startDevServer = async ({
23621
23248
  sourceDirectoryUrl,
23622
- sourceMainFilePath,
23249
+ sourceMainFilePath = "./index.html",
23623
23250
  port = 3456,
23624
23251
  hostname,
23625
23252
  acceptAnyIp,
@@ -23642,13 +23269,12 @@ const startDevServer = async ({
23642
23269
  // code would be supported during dev but not after build
23643
23270
  runtimeCompat = defaultRuntimeCompat,
23644
23271
  plugins = [],
23645
- urlAnalysis = {},
23646
- urlResolution,
23272
+ referenceAnalysis = {},
23273
+ nodeEsmResolution,
23274
+ webResolution,
23647
23275
  supervisor = true,
23648
23276
  fileSystemMagicRedirection,
23649
23277
  transpilation,
23650
- explorer = true,
23651
- // see jsenv_plugin_explorer.js
23652
23278
  cacheControl = true,
23653
23279
  ribbon = true,
23654
23280
  // toolbar = false,
@@ -23666,6 +23292,9 @@ const startDevServer = async ({
23666
23292
  throw new TypeError(`${unexpectedParamNames.join(",")}: there is no such param`);
23667
23293
  }
23668
23294
  sourceDirectoryUrl = assertAndNormalizeDirectoryUrl(sourceDirectoryUrl, "sourceDirectoryUrl");
23295
+ if (typeof sourceMainFilePath !== "string") {
23296
+ throw new TypeError(`sourceMainFilePath must be a string, got ${sourceMainFilePath}`);
23297
+ }
23669
23298
  if (outDirectoryUrl === undefined) {
23670
23299
  if (!process.env.CI) {
23671
23300
  const packageDirectoryUrl = lookupPackageDirectory(sourceDirectoryUrl);
@@ -23759,14 +23388,14 @@ const startDevServer = async ({
23759
23388
  sourceFilesConfig,
23760
23389
  runtimeCompat,
23761
23390
  plugins,
23762
- urlAnalysis,
23763
- urlResolution,
23391
+ referenceAnalysis,
23392
+ nodeEsmResolution,
23393
+ webResolution,
23764
23394
  fileSystemMagicRedirection,
23765
23395
  supervisor,
23766
23396
  transpilation,
23767
23397
  clientAutoreload,
23768
23398
  cooldownBetweenFileEvents,
23769
- explorer,
23770
23399
  cacheControl,
23771
23400
  ribbon,
23772
23401
  sourcemaps,
@@ -23861,6 +23490,7 @@ const startDevServer = async ({
23861
23490
  * we want to be in the user shoes and we should not alter build files.
23862
23491
  */
23863
23492
 
23493
+
23864
23494
  /**
23865
23495
  * Start a server for build files.
23866
23496
  * @param {Object} buildServerParameters