@jsenv/core 27.0.0-alpha.12 → 27.0.0-alpha.13

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 (116) hide show
  1. package/main.js +4 -0
  2. package/package.json +13 -7
  3. package/readme.md +4 -12
  4. package/src/build/build.js +438 -387
  5. package/src/build/build_urls_generator.js +23 -20
  6. package/src/build/graph_utils.js +31 -0
  7. package/src/build/{inject_version_mappings.js → inject_global_version_mappings.js} +31 -14
  8. package/src/build/inject_service_worker_urls.js +66 -12
  9. package/src/build/resync_ressource_hints.js +83 -0
  10. package/src/dev/plugins/autoreload/babel_plugin_metadata_import_meta_hot.js +1 -1
  11. package/src/dev/plugins/autoreload/client/import_meta_hot.js +3 -1
  12. package/src/dev/plugins/autoreload/html_hot_dependencies.js +2 -2
  13. package/src/dev/plugins/autoreload/jsenv_plugin_autoreload.js +61 -51
  14. package/src/dev/plugins/autoreload/sse_service.js +23 -3
  15. package/src/dev/plugins/explorer/jsenv_plugin_explorer.js +2 -2
  16. package/src/dev/plugins/toolbar/jsenv_plugin_toolbar.js +3 -1
  17. package/src/dev/start_dev_server.js +10 -5
  18. package/src/execute/execute.js +10 -4
  19. package/src/execute/run.js +17 -54
  20. package/src/execute/runtimes/browsers/from_playwright.js +167 -146
  21. package/src/execute/runtimes/node/node_process.js +281 -37
  22. package/src/omega/{runtime_support/default_runtime_support.js → compat/default_runtime_compat.js} +3 -5
  23. package/src/omega/{runtime_support/features_compatibility.js → compat/features_compats.js} +30 -7
  24. package/src/omega/{runtime_support/runtime_support.js → compat/runtime_compat.js} +14 -16
  25. package/src/omega/errors.js +51 -58
  26. package/src/omega/fetched_content_compliance.js +24 -0
  27. package/src/omega/kitchen.js +396 -280
  28. package/src/omega/server/file_service.js +9 -11
  29. package/src/omega/url_graph/url_graph_load.js +13 -7
  30. package/src/omega/url_graph/url_graph_report.js +7 -5
  31. package/src/omega/url_graph.js +22 -10
  32. package/src/omega/web_workers.js +42 -0
  33. package/src/plugins/bundling/css/bundle_css.js +17 -0
  34. package/src/plugins/bundling/js_classic_workers/bundle_js_classic_workers.js +13 -0
  35. package/src/{build/plugins/bundle_js_module/jsenv_plugin_bundle_js_module.js → plugins/bundling/js_module/bundle_js_module.js} +100 -75
  36. package/src/plugins/bundling/jsenv_plugin_bundling.js +51 -0
  37. package/src/{omega/core_plugins → plugins}/commonjs_globals/jsenv_plugin_commonjs_globals.js +48 -41
  38. package/src/plugins/file_urls/jsenv_plugin_file_urls.js +66 -0
  39. package/src/{omega/core_plugins → plugins}/filesystem_magic/jsenv_plugin_filesystem_magic.js +7 -4
  40. package/src/{omega/core_plugins → plugins}/html_supervisor/client/error_in_document.js +0 -0
  41. package/src/{omega/core_plugins → plugins}/html_supervisor/client/error_in_notification.js +0 -0
  42. package/src/{omega/core_plugins → plugins}/html_supervisor/client/html_supervisor_installer.js +3 -2
  43. package/src/{omega/core_plugins → plugins}/html_supervisor/client/html_supervisor_setup.js +0 -0
  44. package/src/{omega/core_plugins → plugins}/html_supervisor/client/perf_browser.js +0 -0
  45. package/src/{omega/core_plugins → plugins}/html_supervisor/client/uneval_exception.js +0 -0
  46. package/src/{omega/core_plugins → plugins}/html_supervisor/jsenv_plugin_html_supervisor.js +38 -46
  47. package/src/plugins/http_urls/jsenv_plugin_http_urls.js +12 -0
  48. package/src/{omega/core_plugins → plugins}/import_meta_scenarios/jsenv_plugin_import_meta_scenarios.js +26 -8
  49. package/src/plugins/import_meta_url/client/import_meta_url_browser.js +52 -0
  50. package/src/plugins/import_meta_url/client/import_meta_url_commonjs.mjs +9 -0
  51. package/src/{omega/core_plugins → plugins}/importmap/jsenv_plugin_importmap.js +37 -31
  52. package/src/{omega/core_plugins → plugins}/inject_globals/jsenv_plugin_inject_globals.js +4 -6
  53. package/src/{omega/core_plugins → plugins}/inline/client/inline_content.js +0 -0
  54. package/src/{omega/core_plugins → plugins}/inline/jsenv_plugin_data_urls.js +18 -14
  55. package/src/{omega/core_plugins/inline/jsenv_plugin_js_and_css_inside_html.js → plugins/inline/jsenv_plugin_html_inline_content.js} +61 -40
  56. package/src/plugins/inline/jsenv_plugin_inline.js +36 -0
  57. package/src/{omega/core_plugins → plugins}/inline/jsenv_plugin_inline_query_param.js +6 -6
  58. package/src/plugins/inline/jsenv_plugin_js_inline_content.js +263 -0
  59. package/src/plugins/leading_slash/jsenv_plugin_leading_slash.js +13 -0
  60. package/src/plugins/minification/css/minify_css.js +9 -0
  61. package/src/plugins/minification/html/minify_html.js +15 -0
  62. package/src/{build/plugins/minify_js/jsenv_plugin_minify_js.js → plugins/minification/js/minify_js.js} +6 -22
  63. package/src/plugins/minification/jsenv_plugin_minification.js +77 -0
  64. package/src/plugins/minification/json/minify_json.js +8 -0
  65. package/src/{omega/core_plugins → plugins}/node_esm_resolution/jsenv_plugin_node_esm_resolution.js +15 -15
  66. package/src/{omega → plugins}/plugin_controller.js +18 -10
  67. package/src/plugins/plugins.js +50 -0
  68. package/src/plugins/transpilation/as_js_classic/client/s.js +808 -0
  69. package/src/plugins/transpilation/as_js_classic/client/s.js.md +1 -0
  70. package/src/plugins/transpilation/as_js_classic/helpers/babel_plugin_transform_import_meta_url.js +47 -0
  71. package/src/plugins/transpilation/as_js_classic/helpers/systemjs_old.js +43 -0
  72. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic.js +178 -0
  73. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_script_type_module_as_classic.js +156 -0
  74. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_top_level_await.js +37 -0
  75. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_workers_type_module_as_classic.js +133 -0
  76. package/src/{omega/core_plugins → plugins/transpilation}/babel/global_this/babel_plugin_global_this_as_jsenv_import.js +0 -0
  77. package/src/{omega/core_plugins → plugins/transpilation}/babel/global_this/client/global_this.js +0 -0
  78. package/src/{omega/core_plugins → plugins/transpilation}/babel/helpers/babel_plugin_babel_helpers_as_jsenv_imports.js +0 -0
  79. package/src/{omega/core_plugins → plugins/transpilation}/babel/helpers/babel_plugin_structure.js +3 -21
  80. package/src/{omega/core_plugins → plugins/transpilation}/babel/helpers/babel_plugins_compatibility.js +0 -0
  81. package/src/{omega/core_plugins → plugins/transpilation}/babel/jsenv_plugin_babel.js +29 -27
  82. package/src/{omega/core_plugins → plugins/transpilation}/babel/new_stylesheet/babel_plugin_new_stylesheet_as_jsenv_import.js +0 -0
  83. package/src/{omega/core_plugins → plugins/transpilation}/babel/new_stylesheet/client/.eslintrc.cjs +0 -0
  84. package/src/{omega/core_plugins → plugins/transpilation}/babel/new_stylesheet/client/new_stylesheet.js +0 -0
  85. package/src/{omega/core_plugins → plugins/transpilation}/babel/regenerator_runtime/babel_plugin_regenerator_runtime_as_jsenv_import.js +0 -0
  86. package/src/{omega/core_plugins → plugins/transpilation}/babel/regenerator_runtime/client/regenerator_runtime.js +0 -0
  87. package/src/plugins/transpilation/css_parcel/jsenv_plugin_css_parcel.js +18 -0
  88. package/src/{omega/core_plugins → plugins/transpilation}/import_assertions/helpers/babel_plugin_metadata_import_assertions.js +0 -0
  89. package/src/plugins/transpilation/import_assertions/jsenv_plugin_import_assertions.js +243 -0
  90. package/src/plugins/transpilation/jsenv_plugin_transpilation.js +40 -0
  91. package/src/plugins/url_references/css/css_urls.js +49 -0
  92. package/src/plugins/url_references/html/html_urls.js +273 -0
  93. package/src/plugins/url_references/js/js_urls.js +170 -0
  94. package/src/plugins/url_references/jsenv_plugin_url_references.js +18 -0
  95. package/src/plugins/url_references/webmanifest/webmanifest_urls.js +17 -0
  96. package/src/{omega/core_plugins → plugins}/url_resolution/jsenv_plugin_url_resolution.js +12 -5
  97. package/src/{omega/core_plugins → plugins}/url_version/jsenv_plugin_url_version.js +8 -8
  98. package/src/preview/preview.js +3 -0
  99. package/src/test/execute_plan.js +18 -11
  100. package/src/test/execute_test_plan.js +5 -6
  101. package/src/test/logs_file_execution.js +8 -7
  102. package/src/build/plugins/minify_html/jsenv_plugin_minify_html.js +0 -30
  103. package/src/execute/runtimes/node/controlled_process.js +0 -316
  104. package/src/omega/core_plugins/file_urls/jsenv_plugin_file_urls.js +0 -67
  105. package/src/omega/core_plugins/import_assertions/helpers/json_module.js +0 -12
  106. package/src/omega/core_plugins/import_assertions/helpers/text_module.js +0 -6
  107. package/src/omega/core_plugins/import_assertions/jsenv_plugin_import_assertions.js +0 -211
  108. package/src/omega/core_plugins/inline/jsenv_plugin_inline.js +0 -13
  109. package/src/omega/core_plugins/inline/jsenv_plugin_new_inline_content.js +0 -207
  110. package/src/omega/core_plugins/leading_slash/jsenv_plugin_leading_slash.js +0 -12
  111. package/src/omega/core_plugins.js +0 -42
  112. package/src/omega/url_mentions/css_url_mentions.js +0 -63
  113. package/src/omega/url_mentions/html_url_mentions.js +0 -185
  114. package/src/omega/url_mentions/js_module_url_mentions.js +0 -91
  115. package/src/omega/url_mentions/parse_url_mentions.js +0 -37
  116. package/src/omega/url_mentions/worker_classic_url_mentions.js +0 -37
@@ -4,23 +4,21 @@ import { memoizeByUrl } from "@jsenv/utils/memoize/memoize_by_url.js"
4
4
 
5
5
  export const createBuilUrlsGenerator = ({ buildDirectoryUrl }) => {
6
6
  const cache = {}
7
- const generate = memoizeByUrl((url, urlInfo, parentUrlInfo) => {
8
- const directoryPath = determineDirectoryPath(urlInfo, parentUrlInfo)
7
+ const generate = memoizeByUrl((url, { urlInfo, parentUrlInfo }) => {
8
+ const directoryPath = determineDirectoryPath({
9
+ urlInfo,
10
+ parentUrlInfo,
11
+ })
9
12
  let names = cache[directoryPath]
10
13
  if (!names) {
11
14
  names = []
12
15
  cache[directoryPath] = names
13
16
  }
14
-
15
- let name = urlToFilename(url)
16
- const { searchParams } = new URL(url)
17
- if (
18
- searchParams.has("css_module") ||
19
- searchParams.has("json_module") ||
20
- searchParams.has("text_module")
21
- ) {
22
- name = `${name}.js`
23
- }
17
+ const urlObject = new URL(url)
18
+ let { search, hash } = urlObject
19
+ let name = urlInfo
20
+ ? urlInfo.filename || urlToFilename(url)
21
+ : urlToFilename(url)
24
22
  const [basename, extension] = splitFileExtension(name)
25
23
  let nameCandidate = name
26
24
  let integer = 1
@@ -33,7 +31,7 @@ export const createBuilUrlsGenerator = ({ buildDirectoryUrl }) => {
33
31
  integer++
34
32
  nameCandidate = `${basename}${integer}${extension}`
35
33
  }
36
- return `${buildDirectoryUrl}${directoryPath}${nameCandidate}`
34
+ return `${buildDirectoryUrl}${directoryPath}${nameCandidate}${search}${hash}`
37
35
  })
38
36
 
39
37
  return {
@@ -49,12 +47,17 @@ const splitFileExtension = (filename) => {
49
47
  return [filename.slice(0, dotLastIndex), filename.slice(dotLastIndex)]
50
48
  }
51
49
 
52
- const determineDirectoryPath = (urlInfo, parentUrlInfo) => {
50
+ const determineDirectoryPath = ({ urlInfo, parentUrlInfo }) => {
53
51
  if (urlInfo.isInline) {
54
- const parentDirectoryPath = determineDirectoryPath(parentUrlInfo)
52
+ const parentDirectoryPath = determineDirectoryPath({
53
+ urlInfo: parentUrlInfo,
54
+ })
55
55
  return parentDirectoryPath
56
56
  }
57
- if (urlInfo.data.isEntryPoint) {
57
+ if (urlInfo.data.isEntryPoint || urlInfo.data.isWebWorkerEntryPoint) {
58
+ return ""
59
+ }
60
+ if (urlInfo.type === "importmap") {
58
61
  return ""
59
62
  }
60
63
  if (urlInfo.type === "html") {
@@ -64,10 +67,10 @@ const determineDirectoryPath = (urlInfo, parentUrlInfo) => {
64
67
  return "css/"
65
68
  }
66
69
  if (urlInfo.type === "js_module" || urlInfo.type === "js_classic") {
67
- if (urlInfo.subtype === "service_worker") {
68
- return ""
69
- }
70
70
  return "js/"
71
71
  }
72
- return "assets/"
72
+ if (urlInfo.type === "json") {
73
+ return "json/"
74
+ }
75
+ return "other/"
73
76
  }
@@ -0,0 +1,31 @@
1
+ export const GRAPH = {
2
+ map: (graph, callback) => {
3
+ return Object.keys(graph.urlInfos).map((url) => {
4
+ return callback(graph.urlInfos[url])
5
+ })
6
+ },
7
+
8
+ forEach: (graph, callback) => {
9
+ Object.keys(graph.urlInfos).forEach((url) => {
10
+ callback(graph.urlInfos[url], url)
11
+ })
12
+ },
13
+
14
+ filter: (graph, callback) => {
15
+ const urlInfos = []
16
+ Object.keys(graph.urlInfos).forEach((url) => {
17
+ const urlInfo = graph.urlInfos[url]
18
+ if (callback(urlInfo)) {
19
+ urlInfos.push(urlInfo)
20
+ }
21
+ })
22
+ return urlInfos
23
+ },
24
+
25
+ find: (graph, callback) => {
26
+ const urlFound = Object.keys(graph.urlInfos).find((url) => {
27
+ return callback(graph.urlInfos[url])
28
+ })
29
+ return graph.urlInfos[urlFound]
30
+ },
31
+ }
@@ -8,11 +8,27 @@ import {
8
8
  } from "@jsenv/utils/html_ast/html_ast.js"
9
9
  import { createMagicSource } from "@jsenv/utils/sourcemap/magic_source.js"
10
10
 
11
- export const injectVersionMappings = async ({
12
- urlInfo,
13
- kitchen,
11
+ import { GRAPH } from "./graph_utils.js"
12
+
13
+ export const injectGlobalVersionMapping = async ({
14
+ finalGraphKitchen,
15
+ finalGraph,
14
16
  versionMappings,
15
17
  }) => {
18
+ await Promise.all(
19
+ GRAPH.map(finalGraph, async (urlInfo) => {
20
+ if (urlInfo.data.isEntryPoint || urlInfo.data.isWebWorkerEntryPoint) {
21
+ await injectVersionMappings({
22
+ urlInfo,
23
+ kitchen: finalGraphKitchen,
24
+ versionMappings,
25
+ })
26
+ }
27
+ }),
28
+ )
29
+ }
30
+
31
+ const injectVersionMappings = async ({ urlInfo, kitchen, versionMappings }) => {
16
32
  const injector = injectors[urlInfo.type]
17
33
  if (injector) {
18
34
  const { content, sourcemap } = injector(urlInfo, { versionMappings })
@@ -23,6 +39,12 @@ export const injectVersionMappings = async ({
23
39
  }
24
40
  }
25
41
 
42
+ const jsInjector = (urlInfo, { versionMappings }) => {
43
+ const magicSource = createMagicSource(urlInfo.content)
44
+ magicSource.prepend(generateClientCodeForVersionMappings(versionMappings))
45
+ return magicSource.toContentAndSourcemap()
46
+ }
47
+
26
48
  const injectors = {
27
49
  html: (urlInfo, { versionMappings }) => {
28
50
  // ideally we would inject an importmap but browser support is too low
@@ -40,24 +62,19 @@ const injectors = {
40
62
  }),
41
63
  )
42
64
  return {
43
- content: stringifyHtmlAst(htmlAst, {
44
- removeOriginalPositionAttributes: true,
45
- }),
65
+ content: stringifyHtmlAst(htmlAst),
46
66
  }
47
67
  },
48
- js_module: (urlInfo, { versionMappings }) => {
49
- const magicSource = createMagicSource(urlInfo.content)
50
- magicSource.prepend(generateClientCodeForVersionMappings(versionMappings))
51
- return magicSource.toContentAndSourcemap()
52
- },
68
+ js_classic: jsInjector,
69
+ js_module: jsInjector,
53
70
  }
54
71
 
55
72
  const generateClientCodeForVersionMappings = (versionMappings) => {
56
73
  return `
57
- var __versionMappings__ = ${JSON.stringify(versionMappings, null, " ")}
58
- var __envGlobal__ = typeof self === 'undefined' ? global : self
74
+ var __versionMappings__ = ${JSON.stringify(versionMappings, null, " ")};
75
+ var __envGlobal__ = typeof self === 'undefined' ? global : self;
59
76
  __envGlobal__.__v__ = function (specifier) {
60
77
  return __versionMappings__[specifier] || specifier
61
- }
78
+ };
62
79
  `
63
80
  }
@@ -1,21 +1,75 @@
1
1
  import { createMagicSource } from "@jsenv/utils/sourcemap/magic_source.js"
2
+ import { createVersionGenerator } from "@jsenv/utils/versioning/version_generator.js"
3
+
4
+ import { GRAPH } from "./graph_utils.js"
2
5
 
3
6
  export const injectServiceWorkerUrls = async ({
4
- urlInfo,
5
- kitchen,
6
- serviceWorkerUrls,
7
+ finalGraph,
8
+ finalGraphKitchen,
9
+ lineBreakNormalization,
7
10
  }) => {
8
- const magicSource = createMagicSource(urlInfo.content)
9
- const urlsWithoutSelf = {
10
- ...serviceWorkerUrls,
11
+ const serviceWorkerEntryUrlInfos = GRAPH.filter(
12
+ finalGraph,
13
+ (finalUrlInfo) => {
14
+ return (
15
+ finalUrlInfo.subtype === "service_worker" &&
16
+ finalUrlInfo.data.isWebWorkerEntryPoint
17
+ )
18
+ },
19
+ )
20
+ if (serviceWorkerEntryUrlInfos.length === 0) {
21
+ return
11
22
  }
12
- delete urlsWithoutSelf[urlInfo.data.buildRelativeUrl]
13
- magicSource.prepend(generateClientCode(urlsWithoutSelf))
14
- const { content, sourcemap } = magicSource.toContentAndSourcemap()
15
- await kitchen.urlInfoTransformer.applyFinalTransformations(urlInfo, {
16
- content,
17
- sourcemap,
23
+ const serviceWorkerUrls = {}
24
+ GRAPH.forEach(finalGraph, (urlInfo) => {
25
+ if (urlInfo.isInline || urlInfo.external) {
26
+ return
27
+ }
28
+ if (!urlInfo.url.startsWith("file:")) {
29
+ return
30
+ }
31
+ if (urlInfo.data.buildUrlIsVersioned) {
32
+ serviceWorkerUrls[urlInfo.data.buildUrlSpecifier] = {
33
+ versioned: true,
34
+ }
35
+ return
36
+ }
37
+ if (!urlInfo.data.version) {
38
+ // when url is not versioned we compute a "version" for that url anyway
39
+ // so that service worker source still changes and navigator
40
+ // detect there is a change
41
+ const versionGenerator = createVersionGenerator()
42
+ versionGenerator.augmentWithContent({
43
+ content: urlInfo.content,
44
+ contentType: urlInfo.contentType,
45
+ lineBreakNormalization,
46
+ })
47
+ const version = versionGenerator.generate()
48
+ urlInfo.data.version = version
49
+ }
50
+ serviceWorkerUrls[urlInfo.data.buildUrlSpecifier] = {
51
+ versioned: false,
52
+ version: urlInfo.data.version,
53
+ }
18
54
  })
55
+ await Promise.all(
56
+ serviceWorkerEntryUrlInfos.map(async (serviceWorkerEntryUrlInfo) => {
57
+ const magicSource = createMagicSource(serviceWorkerEntryUrlInfo.content)
58
+ const urlsWithoutSelf = {
59
+ ...serviceWorkerUrls,
60
+ }
61
+ delete urlsWithoutSelf[serviceWorkerEntryUrlInfo.data.buildUrlSpecifier]
62
+ magicSource.prepend(generateClientCode(urlsWithoutSelf))
63
+ const { content, sourcemap } = magicSource.toContentAndSourcemap()
64
+ await finalGraphKitchen.urlInfoTransformer.applyFinalTransformations(
65
+ serviceWorkerEntryUrlInfo,
66
+ {
67
+ content,
68
+ sourcemap,
69
+ },
70
+ )
71
+ }),
72
+ )
19
73
  }
20
74
 
21
75
  const generateClientCode = (serviceWorkerUrls) => {
@@ -0,0 +1,83 @@
1
+ import {
2
+ parseHtmlString,
3
+ visitHtmlAst,
4
+ stringifyHtmlAst,
5
+ getHtmlNodeAttributeByName,
6
+ assignHtmlNodeAttributes,
7
+ removeHtmlNode,
8
+ } from "@jsenv/utils/html_ast/html_ast.js"
9
+
10
+ import { GRAPH } from "./graph_utils.js"
11
+
12
+ // update ressource hint that where targeting a file that has changed during build
13
+ // (happens for import assertions and file modified by "?as_js_classic")
14
+ export const resyncRessourceHints = async ({
15
+ finalGraphKitchen,
16
+ finalGraph,
17
+ buildUrls,
18
+ buildUrlRedirections,
19
+ }) => {
20
+ const ressourceHintActions = []
21
+ GRAPH.forEach(finalGraph, (urlInfo) => {
22
+ if (urlInfo.type !== "html") {
23
+ return
24
+ }
25
+ ressourceHintActions.push(async () => {
26
+ const htmlAst = parseHtmlString(urlInfo.content, {
27
+ storeOriginalPositions: false,
28
+ })
29
+ const visitLinkWithHref = (linkNode, hrefAttribute) => {
30
+ const href = hrefAttribute.value
31
+ if (!href || href.startsWith("data:")) {
32
+ return
33
+ }
34
+ const buildUrl = buildUrls[href]
35
+ const urlInfo = finalGraph.getUrlInfo(buildUrl)
36
+ if (!urlInfo) {
37
+ return
38
+ }
39
+ if (urlInfo.dependents.size === 0) {
40
+ removeHtmlNode(linkNode)
41
+ return
42
+ }
43
+ const buildUrlRedirected = buildUrlRedirections[buildUrl]
44
+ if (buildUrlRedirected) {
45
+ const urlInfoRedirected = finalGraph.getUrlInfo(buildUrlRedirected)
46
+ hrefAttribute.value = urlInfoRedirected.data.buildUrlSpecifier
47
+
48
+ if (
49
+ urlInfo.type === "js_module" &&
50
+ urlInfoRedirected.type === "js_classic"
51
+ ) {
52
+ const relAttribute = getHtmlNodeAttributeByName(linkNode, "rel")
53
+ if (relAttribute && relAttribute.value === "modulepreload") {
54
+ assignHtmlNodeAttributes(linkNode, {
55
+ rel: "preload",
56
+ as: "script",
57
+ })
58
+ }
59
+ }
60
+ }
61
+ }
62
+ visitHtmlAst(htmlAst, (node) => {
63
+ if (node.nodeName !== "link") {
64
+ return
65
+ }
66
+ const hrefAttribute = getHtmlNodeAttributeByName(node, "href")
67
+ if (!hrefAttribute) {
68
+ return
69
+ }
70
+ visitLinkWithHref(node, hrefAttribute)
71
+ })
72
+ await finalGraphKitchen.urlInfoTransformer.applyFinalTransformations(
73
+ urlInfo,
74
+ {
75
+ content: stringifyHtmlAst(htmlAst),
76
+ },
77
+ )
78
+ })
79
+ })
80
+ await Promise.all(
81
+ ressourceHintActions.map((ressourceHintAction) => ressourceHintAction()),
82
+ )
83
+ }
@@ -64,7 +64,7 @@ const collectImportMetaProperties = (programPath) => {
64
64
  )
65
65
  }
66
66
  return {
67
- specifierPath: firstArgPath.get(index),
67
+ specifierPath: firstArgPath.get(String(index)),
68
68
  }
69
69
  })
70
70
  return
@@ -40,7 +40,9 @@ export const createImportMetaHot = (importMetaUrl) => {
40
40
  })
41
41
  return
42
42
  }
43
- throw new Error(`invalid call to hot.accept()`)
43
+ throw new Error(
44
+ `invalid call to import.meta.hot.accept(), received ${firstArg}`,
45
+ )
44
46
  },
45
47
  dispose: (callback) => {
46
48
  addUrlMeta(url, {
@@ -53,7 +53,7 @@ export const collectHotDataFromHtmlAst = (htmlAst) => {
53
53
  })
54
54
  visitUrlSpecifierAttribute({
55
55
  node,
56
- attributeName: "content-href",
56
+ attributeName: "generated-from-href",
57
57
  hotAccepted,
58
58
  })
59
59
  }
@@ -65,7 +65,7 @@ export const collectHotDataFromHtmlAst = (htmlAst) => {
65
65
  })
66
66
  visitUrlSpecifierAttribute({
67
67
  node,
68
- attributeName: "content-src",
68
+ attributeName: "generated-from-src",
69
69
  hotAccepted,
70
70
  })
71
71
  }
@@ -1,7 +1,6 @@
1
1
  import { urlToRelativeUrl } from "@jsenv/filesystem"
2
2
  import { createCallbackList } from "@jsenv/abort"
3
3
 
4
- import { injectQueryParams } from "@jsenv/utils/urls/url_utils.js"
5
4
  import { createMagicSource } from "@jsenv/utils/sourcemap/magic_source.js"
6
5
  import {
7
6
  parseHtmlString,
@@ -19,37 +18,31 @@ export const jsenvPluginAutoreload = ({
19
18
  rootDirectoryUrl,
20
19
  urlGraph,
21
20
  autoreloadPatterns,
21
+ cooldownBetweenFileEvents,
22
22
  }) => {
23
23
  return [
24
+ jsenvPluginHmr(),
24
25
  jsenvPluginHot(),
25
26
  jsenvPluginHotSSE({
26
27
  rootDirectoryUrl,
27
28
  urlGraph,
28
29
  autoreloadPatterns,
30
+ cooldownBetweenFileEvents,
29
31
  }),
30
32
  ]
31
33
  }
32
34
 
33
- const jsenvPluginHot = () => {
34
- const eventSourceClientFileUrl = new URL(
35
- "./client/event_source_client.js",
36
- import.meta.url,
37
- ).href
38
- const importMetaHotClientFileUrl = new URL(
39
- "./client/import_meta_hot.js",
40
- import.meta.url,
41
- ).href
42
-
35
+ const jsenvPluginHmr = () => {
43
36
  return {
44
- name: "jsenv:hot",
37
+ name: "jsenv:hmr",
45
38
  appliesDuring: { dev: true },
46
- normalize: ({ url, data }) => {
47
- const urlObject = new URL(url)
39
+ normalizeUrl: (reference) => {
40
+ const urlObject = new URL(reference.url)
48
41
  if (!urlObject.searchParams.has("hmr")) {
49
- data.hmr = false
42
+ reference.data.hmr = false
50
43
  return null
51
44
  }
52
- data.hmr = true
45
+ reference.data.hmr = true
53
46
  // "hmr" search param goal is to mark url as enabling hmr:
54
47
  // this goal is achieved when we reach this part of the code
55
48
  // We get rid of this params so that urlGraph and other parts of the code
@@ -58,36 +51,54 @@ const jsenvPluginHot = () => {
58
51
  urlObject.searchParams.delete("v")
59
52
  return urlObject.href
60
53
  },
61
- transformReferencedUrl: ({ parentUrl, url, data }, { urlGraph }) => {
62
- const parentUrlInfo = urlGraph.getUrlInfo(parentUrl)
54
+ transformUrlSearchParams: (reference, context) => {
55
+ const parentUrlInfo = context.urlGraph.getUrlInfo(reference.parentUrl)
63
56
  if (!parentUrlInfo || !parentUrlInfo.data.hmr) {
64
57
  return null
65
58
  }
66
- if (!data.hmrTimestamp) {
59
+ const urlInfo = context.urlGraph.getUrlInfo(reference.url)
60
+ if (!urlInfo.data.hmrTimestamp) {
67
61
  return null
68
62
  }
69
- return injectQueryParams(url, {
63
+ return {
70
64
  hmr: "",
71
- v: data.hmrTimestamp,
72
- })
65
+ v: urlInfo.data.hmrTimestamp,
66
+ }
73
67
  },
74
- transform: {
75
- html: ({ data, content }, { referenceUtils }) => {
76
- const htmlAst = parseHtmlString(content)
68
+ }
69
+ }
70
+
71
+ const jsenvPluginHot = () => {
72
+ const eventSourceClientFileUrl = new URL(
73
+ "./client/event_source_client.js",
74
+ import.meta.url,
75
+ ).href
76
+ const importMetaHotClientFileUrl = new URL(
77
+ "./client/import_meta_hot.js",
78
+ import.meta.url,
79
+ ).href
80
+
81
+ return {
82
+ name: "jsenv:hot",
83
+ appliesDuring: { dev: true },
84
+ transformUrlContent: {
85
+ html: (htmlUrlInfo, context) => {
86
+ const htmlAst = parseHtmlString(htmlUrlInfo.content)
77
87
  const { hotReferences } = collectHotDataFromHtmlAst(htmlAst)
78
- data.hotDecline = false
79
- data.hotAcceptSelf = false
80
- data.hotAcceptDependencies = hotReferences.map(
88
+ htmlUrlInfo.data.hotDecline = false
89
+ htmlUrlInfo.data.hotAcceptSelf = false
90
+ htmlUrlInfo.data.hotAcceptDependencies = hotReferences.map(
81
91
  ({ type, specifier }) => {
82
- const [reference] = referenceUtils.inject({
92
+ const [reference] = context.referenceUtils.found({
83
93
  type,
84
94
  specifier,
85
95
  })
86
96
  return reference.url
87
97
  },
88
98
  )
89
- const [eventSourceClientReference] = referenceUtils.inject({
99
+ const [eventSourceClientReference] = context.referenceUtils.inject({
90
100
  type: "script_src",
101
+ expectedType: "js_module",
91
102
  specifier: eventSourceClientFileUrl,
92
103
  })
93
104
  injectScriptAsEarlyAsPossible(
@@ -104,20 +115,15 @@ const jsenvPluginHot = () => {
104
115
  content: htmlModified,
105
116
  }
106
117
  },
107
- css: ({ data }) => {
108
- data.hotDecline = false
109
- data.hotAcceptSelf = false
110
- data.hotAcceptDependencies = []
118
+ css: (cssUrlInfo) => {
119
+ cssUrlInfo.data.hotDecline = false
120
+ cssUrlInfo.data.hotAcceptSelf = false
121
+ cssUrlInfo.data.hotAcceptDependencies = []
111
122
  },
112
- js_module: async (
113
- { url, generatedUrl, data, content },
114
- { referenceUtils },
115
- ) => {
123
+ js_module: async (urlInfo, context) => {
116
124
  const { metadata } = await applyBabelPlugins({
117
125
  babelPlugins: [babelPluginMetadataImportMetaHot],
118
- url,
119
- generatedUrl,
120
- content,
126
+ urlInfo,
121
127
  })
122
128
  const {
123
129
  importMetaHotDetected,
@@ -125,9 +131,9 @@ const jsenvPluginHot = () => {
125
131
  hotAcceptSelf,
126
132
  hotAcceptDependencies,
127
133
  } = metadata
128
- data.hotDecline = hotDecline
129
- data.hotAcceptSelf = hotAcceptSelf
130
- data.hotAcceptDependencies = hotAcceptDependencies
134
+ urlInfo.data.hotDecline = hotDecline
135
+ urlInfo.data.hotAcceptSelf = hotAcceptSelf
136
+ urlInfo.data.hotAcceptDependencies = hotAcceptDependencies
131
137
  if (!importMetaHotDetected) {
132
138
  return null
133
139
  }
@@ -135,12 +141,14 @@ const jsenvPluginHot = () => {
135
141
  // better sourcemap than doing the equivalent with babel
136
142
  // I suspect it's because I was doing injectAstAfterImport(programPath, ast.program.body[0])
137
143
  // which is likely not well supported by babel
138
- const [importMetaHotClientFileReference] = referenceUtils.inject({
139
- parentUrl: url,
140
- type: "js_import_export",
141
- specifier: importMetaHotClientFileUrl,
142
- })
143
- const magicSource = createMagicSource(content)
144
+ const [importMetaHotClientFileReference] =
145
+ context.referenceUtils.inject({
146
+ parentUrl: urlInfo.url,
147
+ type: "js_import_export",
148
+ expectedType: "js_module",
149
+ specifier: importMetaHotClientFileUrl,
150
+ })
151
+ const magicSource = createMagicSource(urlInfo.content)
144
152
  magicSource.prepend(
145
153
  `import { createImportMetaHot } from ${importMetaHotClientFileReference.generatedSpecifier}
146
154
  import.meta.hot = createImportMetaHot(import.meta.url)
@@ -156,6 +164,7 @@ const jsenvPluginHotSSE = ({
156
164
  rootDirectoryUrl,
157
165
  urlGraph,
158
166
  autoreloadPatterns,
167
+ cooldownBetweenFileEvents,
159
168
  }) => {
160
169
  const hotUpdateCallbackList = createCallbackList()
161
170
  const notifyDeclined = ({ cause, reason, declinedBy }) => {
@@ -274,6 +283,8 @@ const jsenvPluginHotSSE = ({
274
283
  const sseService = createSSEService({
275
284
  rootDirectoryUrl,
276
285
  autoreloadPatterns,
286
+ hotUpdateCallbackList,
287
+ cooldownBetweenFileEvents,
277
288
  onFileChange: ({ relativeUrl, event }) => {
278
289
  const url = new URL(relativeUrl, rootDirectoryUrl).href
279
290
  const urlInfo = urlGraph.urlInfos[url]
@@ -297,7 +308,6 @@ const jsenvPluginHotSSE = ({
297
308
  })
298
309
  }
299
310
  },
300
- hotUpdateCallbackList,
301
311
  })
302
312
  urlGraph.prunedCallbackList.add(({ prunedUrlInfos, firstUrlInfo }) => {
303
313
  prunedUrlInfos.forEach((prunedUrlInfo) => {
@@ -10,6 +10,7 @@ export const createSSEService = ({
10
10
  autoreloadPatterns,
11
11
  onFileChange,
12
12
  hotUpdateCallbackList,
13
+ cooldownBetweenFileEvents = 0,
13
14
  }) => {
14
15
  const destroyCallbackList = createCallbackListNotifiedOnce()
15
16
  const projectFileModified = createCallbackList()
@@ -111,9 +112,11 @@ export const createSSEService = ({
111
112
  }
112
113
  },
113
114
  )
114
- const stopWatching = watchProjectFiles(({ relativeUrl, event }) => {
115
- onFileChange({ relativeUrl, event })
116
- })
115
+ const stopWatching = watchProjectFiles(
116
+ cooldownBetweenFileEvents
117
+ ? guardTooFastSecondCall(onFileChange, cooldownBetweenFileEvents)
118
+ : onFileChange,
119
+ )
117
120
  return () => {
118
121
  removeHotUpdateCallback()
119
122
  stopWatching()
@@ -147,3 +150,20 @@ export const createSSEService = ({
147
150
  },
148
151
  }
149
152
  }
153
+
154
+ const guardTooFastSecondCall = (callback, cooldownBetweenFileEvents = 40) => {
155
+ const previousCallMsMap = new Map()
156
+ return ({ relativeUrl, event }) => {
157
+ const previousCallMs = previousCallMsMap.get(relativeUrl)
158
+ const nowMs = Date.now()
159
+ if (previousCallMs) {
160
+ const msEllapsed = nowMs - previousCallMs
161
+ if (msEllapsed < cooldownBetweenFileEvents) {
162
+ previousCallMsMap.delete(relativeUrl)
163
+ return
164
+ }
165
+ }
166
+ previousCallMsMap.set(relativeUrl, nowMs)
167
+ callback({ relativeUrl, event })
168
+ }
169
+ }
@@ -1,8 +1,8 @@
1
1
  import { readFileSync } from "node:fs"
2
- import { urlToContentType } from "@jsenv/server"
3
2
  import { collectFiles, normalizeStructuredMetaMap } from "@jsenv/filesystem"
4
3
 
5
4
  import { DataUrl } from "@jsenv/utils/urls/data_url.js"
5
+ import { CONTENT_TYPE } from "@jsenv/utils/content_type/content_type.js"
6
6
 
7
7
  export const jsenvPluginExplorer = ({ groups }) => {
8
8
  const htmlClientFileUrl = new URL("./client/explorer.html", import.meta.url)
@@ -43,7 +43,7 @@ export const jsenvPluginExplorer = ({ groups }) => {
43
43
  html = html.replace(
44
44
  "FAVICON_HREF",
45
45
  DataUrl.stringify({
46
- contentType: urlToContentType(faviconClientFileUrl),
46
+ contentType: CONTENT_TYPE.fromUrlExtension(faviconClientFileUrl),
47
47
  base64Flag: true,
48
48
  data: readFileSync(new URL(faviconClientFileUrl)).toString("base64"),
49
49
  }),