@jsenv/core 27.0.0-alpha.6 → 27.0.0-alpha.62

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 (150) hide show
  1. package/dist/event_source_client.js +549 -0
  2. package/dist/event_source_client.js.map +188 -0
  3. package/dist/html_supervisor_installer.js +1236 -0
  4. package/dist/html_supervisor_installer.js.map +337 -0
  5. package/dist/html_supervisor_setup.js +95 -0
  6. package/dist/html_supervisor_setup.js.map +57 -0
  7. package/dist/import_meta_hot.js +86 -0
  8. package/dist/import_meta_hot.js.map +42 -0
  9. package/main.js +8 -1
  10. package/package.json +36 -29
  11. package/readme.md +6 -14
  12. package/src/build/build.js +961 -559
  13. package/src/build/build_urls_generator.js +48 -23
  14. package/src/build/graph_utils.js +31 -0
  15. package/src/build/{inject_version_mappings.js → inject_global_version_mappings.js} +33 -15
  16. package/src/build/inject_service_worker_urls.js +79 -0
  17. package/src/build/resync_ressource_hints.js +68 -0
  18. package/src/build/start_build_server.js +200 -0
  19. package/src/dev/plugins/explorer/jsenv_plugin_explorer.js +2 -2
  20. package/src/dev/plugins/toolbar/jsenv_plugin_toolbar.js +3 -1
  21. package/src/dev/start_dev_server.js +150 -34
  22. package/src/execute/execute.js +33 -6
  23. package/src/execute/run.js +19 -56
  24. package/src/execute/runtimes/browsers/from_playwright.js +207 -147
  25. package/src/execute/runtimes/node/controllable_file.mjs +26 -10
  26. package/src/execute/runtimes/node/node_execution_performance.js +67 -0
  27. package/src/execute/runtimes/node/node_process.js +280 -39
  28. package/src/jsenv_root_directory_url.js +1 -0
  29. package/src/omega/{runtime_support/default_runtime_support.js → compat/default_runtime_compat.js} +3 -5
  30. package/src/omega/{runtime_support/features_compatibility.js → compat/features_compats.js} +66 -4
  31. package/src/omega/compat/runtime_compat.js +50 -0
  32. package/src/omega/errors.js +51 -58
  33. package/src/omega/fetched_content_compliance.js +24 -0
  34. package/src/omega/file_url_converter.js +8 -50
  35. package/src/omega/kitchen.js +475 -308
  36. package/src/omega/omega_server.js +2 -3
  37. package/src/omega/server/file_service.js +57 -26
  38. package/src/omega/server/user_agent.js +4 -2
  39. package/src/omega/url_graph/url_graph_load.js +22 -7
  40. package/src/omega/url_graph/url_graph_report.js +94 -51
  41. package/src/omega/url_graph/url_info_transformations.js +26 -9
  42. package/src/omega/url_graph.js +80 -16
  43. package/src/omega/web_workers.js +42 -0
  44. package/src/{dev/plugins/autoreload → plugins/autoreload/dev_sse}/client/autoreload_preference.js +0 -0
  45. package/src/{dev/plugins/autoreload → plugins/autoreload/dev_sse}/client/event_source_client.js +19 -12
  46. package/src/{dev/plugins/autoreload → plugins/autoreload/dev_sse}/client/reload.js +0 -0
  47. package/src/{dev/plugins/autoreload → plugins/autoreload/dev_sse}/client/url_helpers.js +0 -0
  48. package/src/plugins/autoreload/dev_sse/jsenv_plugin_dev_sse_client.js +46 -0
  49. package/src/plugins/autoreload/dev_sse/jsenv_plugin_dev_sse_server.js +204 -0
  50. package/src/plugins/autoreload/jsenv_plugin_autoreload.js +27 -0
  51. package/src/plugins/autoreload/jsenv_plugin_hmr.js +35 -0
  52. package/src/plugins/bundling/css/bundle_css.js +140 -0
  53. package/src/plugins/bundling/js_classic_workers/bundle_js_classic_workers.js +13 -0
  54. package/src/plugins/bundling/js_module/bundle_js_module.js +309 -0
  55. package/src/plugins/bundling/jsenv_plugin_bundling.js +54 -0
  56. package/src/plugins/cache_control/jsenv_plugin_cache_control.js +34 -0
  57. package/src/{omega/core_plugins → plugins}/commonjs_globals/jsenv_plugin_commonjs_globals.js +54 -41
  58. package/src/plugins/file_urls/jsenv_plugin_file_urls.js +66 -0
  59. package/src/{omega/core_plugins → plugins}/filesystem_magic/jsenv_plugin_filesystem_magic.js +8 -5
  60. package/src/{omega/core_plugins → plugins}/html_supervisor/client/error_in_document.js +0 -0
  61. package/src/{omega/core_plugins → plugins}/html_supervisor/client/error_in_notification.js +0 -0
  62. package/src/plugins/html_supervisor/client/html_supervisor_installer.js +242 -0
  63. package/src/plugins/html_supervisor/client/html_supervisor_setup.js +79 -0
  64. package/src/{omega/core_plugins → plugins}/html_supervisor/client/perf_browser.js +0 -0
  65. package/src/{omega/core_plugins → plugins}/html_supervisor/client/uneval_exception.js +0 -0
  66. package/src/{omega/core_plugins → plugins}/html_supervisor/jsenv_plugin_html_supervisor.js +83 -61
  67. package/src/plugins/http_urls/jsenv_plugin_http_urls.js +12 -0
  68. package/src/{dev/plugins/autoreload → plugins/import_meta_hot}/babel_plugin_metadata_import_meta_hot.js +4 -5
  69. package/src/{dev/plugins/autoreload → plugins/import_meta_hot}/client/import_meta_hot.js +3 -1
  70. package/src/{dev/plugins/autoreload → plugins/import_meta_hot}/html_hot_dependencies.js +7 -4
  71. package/src/plugins/import_meta_hot/jsenv_plugin_import_meta_hot.js +105 -0
  72. package/src/{omega/core_plugins → plugins}/import_meta_scenarios/jsenv_plugin_import_meta_scenarios.js +33 -8
  73. package/src/plugins/import_meta_url/client/import_meta_url_browser.js +52 -0
  74. package/src/plugins/import_meta_url/client/import_meta_url_commonjs.mjs +9 -0
  75. package/src/{omega/core_plugins → plugins}/importmap/jsenv_plugin_importmap.js +39 -33
  76. package/src/plugins/inject_globals/jsenv_plugin_inject_globals.js +67 -0
  77. package/src/{omega/core_plugins → plugins}/inline/client/inline_content.js +0 -0
  78. package/src/{omega/core_plugins → plugins}/inline/jsenv_plugin_data_urls.js +18 -14
  79. package/src/{omega/core_plugins/inline/jsenv_plugin_js_and_css_inside_html.js → plugins/inline/jsenv_plugin_html_inline_content.js} +65 -44
  80. package/src/plugins/inline/jsenv_plugin_inline.js +36 -0
  81. package/src/{omega/core_plugins → plugins}/inline/jsenv_plugin_inline_query_param.js +6 -6
  82. package/src/plugins/inline/jsenv_plugin_js_inline_content.js +297 -0
  83. package/src/plugins/leading_slash/jsenv_plugin_leading_slash.js +13 -0
  84. package/src/plugins/minification/css/minify_css.js +9 -0
  85. package/src/plugins/minification/html/minify_html.js +15 -0
  86. package/src/{build/plugins/minify_js/jsenv_plugin_minify_js.js → plugins/minification/js/minify_js.js} +6 -22
  87. package/src/plugins/minification/jsenv_plugin_minification.js +78 -0
  88. package/src/plugins/minification/json/minify_json.js +8 -0
  89. package/src/plugins/node_esm_resolution/jsenv_plugin_node_esm_resolution.js +146 -0
  90. package/src/{omega → plugins}/plugin_controller.js +42 -11
  91. package/src/plugins/plugins.js +92 -0
  92. package/src/plugins/transpilation/as_js_classic/client/s.js +874 -0
  93. package/src/plugins/transpilation/as_js_classic/client/s.js.md +1 -0
  94. package/src/plugins/transpilation/as_js_classic/helpers/babel_plugin_transform_import_meta_url.js +47 -0
  95. package/src/plugins/transpilation/as_js_classic/helpers/systemjs_old.js +43 -0
  96. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic.js +199 -0
  97. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_script_type_module_as_classic.js +270 -0
  98. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_workers_type_module_as_classic.js +55 -0
  99. package/src/{omega/core_plugins → plugins/transpilation}/babel/global_this/babel_plugin_global_this_as_jsenv_import.js +0 -0
  100. package/src/{omega/core_plugins → plugins/transpilation}/babel/global_this/client/global_this.js +0 -0
  101. package/src/{omega/core_plugins → plugins/transpilation}/babel/helpers/babel_plugin_babel_helpers_as_jsenv_imports.js +0 -0
  102. package/src/{omega/core_plugins → plugins/transpilation}/babel/helpers/babel_plugin_structure.js +12 -19
  103. package/src/{omega/core_plugins → plugins/transpilation}/babel/helpers/babel_plugins_compatibility.js +0 -0
  104. package/src/{omega/core_plugins → plugins/transpilation}/babel/jsenv_plugin_babel.js +45 -27
  105. package/src/{omega/core_plugins → plugins/transpilation}/babel/new_stylesheet/babel_plugin_new_stylesheet_as_jsenv_import.js +30 -6
  106. package/src/{omega/core_plugins → plugins/transpilation}/babel/new_stylesheet/client/.eslintrc.cjs +0 -0
  107. package/src/{omega/core_plugins → plugins/transpilation}/babel/new_stylesheet/client/new_stylesheet.js +0 -0
  108. package/src/{omega/core_plugins → plugins/transpilation}/babel/regenerator_runtime/babel_plugin_regenerator_runtime_as_jsenv_import.js +0 -0
  109. package/src/{omega/core_plugins → plugins/transpilation}/babel/regenerator_runtime/client/regenerator_runtime.js +0 -0
  110. package/src/plugins/transpilation/css_parcel/jsenv_plugin_css_parcel.js +18 -0
  111. package/src/plugins/transpilation/fetch_original_url_info.js +30 -0
  112. package/src/plugins/transpilation/import_assertions/jsenv_plugin_import_assertions.js +181 -0
  113. package/src/plugins/transpilation/jsenv_plugin_top_level_await.js +80 -0
  114. package/src/plugins/transpilation/jsenv_plugin_transpilation.js +44 -0
  115. package/src/plugins/url_analysis/css/css_urls.js +49 -0
  116. package/src/plugins/url_analysis/html/html_urls.js +269 -0
  117. package/src/plugins/url_analysis/js/js_urls.js +67 -0
  118. package/src/plugins/url_analysis/jsenv_plugin_url_analysis.js +18 -0
  119. package/src/plugins/url_analysis/webmanifest/webmanifest_urls.js +17 -0
  120. package/src/{omega/core_plugins → plugins}/url_resolution/jsenv_plugin_url_resolution.js +12 -5
  121. package/src/plugins/url_version/jsenv_plugin_url_version.js +28 -0
  122. package/src/test/execute_plan.js +38 -19
  123. package/src/test/execute_test_plan.js +25 -8
  124. package/src/test/logs_file_execution.js +10 -12
  125. package/src/build/plugins/bundle_js_module/jsenv_plugin_bundle_js_module.js +0 -225
  126. package/src/build/plugins/minify_html/jsenv_plugin_minify_html.js +0 -30
  127. package/src/dev/plugins/autoreload/client/event_source_connection.js +0 -195
  128. package/src/dev/plugins/autoreload/jsenv_plugin_autoreload.js +0 -374
  129. package/src/dev/plugins/autoreload/sse_service.js +0 -149
  130. package/src/execute/runtimes/node/controlled_process.js +0 -316
  131. package/src/omega/core_plugins/file_urls/jsenv_plugin_file_urls.js +0 -67
  132. package/src/omega/core_plugins/html_supervisor/client/html_supervisor_installer.js +0 -168
  133. package/src/omega/core_plugins/html_supervisor/client/html_supervisor_setup.js +0 -77
  134. package/src/omega/core_plugins/import_assertions/helpers/babel_plugin_metadata_import_assertions.js +0 -98
  135. package/src/omega/core_plugins/import_assertions/helpers/json_module.js +0 -12
  136. package/src/omega/core_plugins/import_assertions/helpers/text_module.js +0 -6
  137. package/src/omega/core_plugins/import_assertions/jsenv_plugin_import_assertions.js +0 -211
  138. package/src/omega/core_plugins/inline/jsenv_plugin_inline.js +0 -13
  139. package/src/omega/core_plugins/inline/jsenv_plugin_new_inline_content.js +0 -210
  140. package/src/omega/core_plugins/leading_slash/jsenv_plugin_leading_slash.js +0 -12
  141. package/src/omega/core_plugins/node_esm_resolution/jsenv_plugin_node_esm_resolution.js +0 -77
  142. package/src/omega/core_plugins/url_version/jsenv_plugin_url_version.js +0 -50
  143. package/src/omega/core_plugins.js +0 -39
  144. package/src/omega/runtime_support/runtime_support.js +0 -20
  145. package/src/omega/url_graph/url_graph_sort.js +0 -29
  146. package/src/omega/url_mentions/css_url_mentions.js +0 -63
  147. package/src/omega/url_mentions/html_url_mentions.js +0 -185
  148. package/src/omega/url_mentions/js_module_url_mentions.js +0 -91
  149. package/src/omega/url_mentions/parse_url_mentions.js +0 -37
  150. package/src/omega/url_mentions/worker_classic_url_mentions.js +0 -37
@@ -8,14 +8,13 @@ import {
8
8
  } from "@jsenv/server"
9
9
  import { convertFileSystemErrorToResponseProperties } from "@jsenv/server/src/internal/convertFileSystemErrorToResponseProperties.js"
10
10
  import { createCallbackListNotifiedOnce } from "@jsenv/abort"
11
- import { loggerToLogLevel } from "@jsenv/logger"
12
11
 
13
12
  import { createFileService } from "./server/file_service.js"
14
13
 
15
14
  export const startOmegaServer = async ({
16
15
  signal,
17
16
  handleSIGINT,
18
- logger,
17
+ logLevel,
19
18
  protocol = "http",
20
19
  http2 = protocol === "https",
21
20
  privateKey,
@@ -48,7 +47,7 @@ export const startOmegaServer = async ({
48
47
  stopOnSIGINT: handleSIGINT,
49
48
  stopOnInternalError: false,
50
49
  keepProcessAlive,
51
- logLevel: loggerToLogLevel(logger),
50
+ logLevel,
52
51
  startLog: false,
53
52
 
54
53
  protocol,
@@ -20,6 +20,11 @@ export const createFileService = ({
20
20
  urlGraph,
21
21
  scenario,
22
22
  }
23
+ const augmentResponseContext = {
24
+ rootDirectoryUrl,
25
+ urlGraph,
26
+ scenario,
27
+ }
23
28
 
24
29
  const getResponse = async (request) => {
25
30
  // serve file inside ".jsenv" directory
@@ -39,43 +44,78 @@ export const createFileService = ({
39
44
  if (responseFromPlugin) {
40
45
  return responseFromPlugin
41
46
  }
42
- const { runtimeName, runtimeVersion } = parseUserAgentHeader(
43
- request.headers["user-agent"],
44
- )
45
- const runtimeSupport = {
46
- [runtimeName]: runtimeVersion,
47
- }
48
- const reference = kitchen.createReference({
47
+ const [reference, urlInfo] = kitchen.prepareEntryPoint({
49
48
  parentUrl: inferParentFromRequest(request, rootDirectoryUrl),
50
49
  type: "entry_point",
51
50
  specifier: request.ressource,
52
51
  })
53
- const requestedUrlInfo = kitchen.resolveReference(reference)
52
+ const ifNoneMatch = request.headers["if-none-match"]
53
+ if (ifNoneMatch && urlInfo.contentEtag === ifNoneMatch) {
54
+ return {
55
+ status: 304,
56
+ headers: {
57
+ "cache-control": `private,max-age=0,must-revalidate`,
58
+ },
59
+ }
60
+ }
54
61
  const referenceFromGraph = urlGraph.inferReference(
55
62
  reference.url,
56
63
  reference.parentUrl,
57
64
  )
58
65
  try {
66
+ // urlInfo objects are reused, they must be "reset" before cooking them again
67
+ if (
68
+ urlInfo.contentEtag &&
69
+ !urlInfo.isInline &&
70
+ urlInfo.type !== "sourcemap"
71
+ ) {
72
+ urlInfo.sourcemap = null
73
+ urlInfo.sourcemapReference = null
74
+ urlInfo.content = null
75
+ urlInfo.originalContent = null
76
+ urlInfo.type = null
77
+ urlInfo.subtype = null
78
+ urlInfo.timing = {}
79
+ }
80
+ const { runtimeName, runtimeVersion } = parseUserAgentHeader(
81
+ request.headers["user-agent"],
82
+ )
59
83
  await kitchen.cook({
60
84
  reference: referenceFromGraph || reference,
61
- urlInfo: requestedUrlInfo,
62
- outDirectoryUrl: `${rootDirectoryUrl}.jsenv/${scenario}/${runtimeName}@${runtimeVersion}/`,
63
- runtimeSupport,
85
+ urlInfo,
86
+ outDirectoryUrl:
87
+ scenario === "dev"
88
+ ? `${rootDirectoryUrl}.jsenv/${runtimeName}@${runtimeVersion}/`
89
+ : `${rootDirectoryUrl}.jsenv/${scenario}/${runtimeName}@${runtimeVersion}/`,
90
+ clientRuntimeCompat: {
91
+ [runtimeName]: runtimeVersion,
92
+ },
64
93
  })
65
- const { response, contentType, content } = requestedUrlInfo
94
+ let { response, contentType, content, contentEtag } = urlInfo
66
95
  if (response) {
67
96
  return response
68
97
  }
69
- return {
98
+ response = {
70
99
  url: reference.url,
71
100
  status: 200,
72
101
  headers: {
73
102
  "content-type": contentType,
74
103
  "content-length": Buffer.byteLength(content),
75
104
  "cache-control": `private,max-age=0,must-revalidate`,
105
+ "eTag": contentEtag,
76
106
  },
77
107
  body: content,
108
+ timing: urlInfo.timing,
78
109
  }
110
+ kitchen.pluginController.callHooks(
111
+ "augmentResponse",
112
+ { reference, urlInfo },
113
+ augmentResponseContext,
114
+ (returnValue) => {
115
+ response = composeTwoResponses(response, returnValue)
116
+ },
117
+ )
118
+ return response
79
119
  } catch (e) {
80
120
  const code = e.code
81
121
  if (code === "PARSE_ERROR") {
@@ -86,11 +126,11 @@ export const createFileService = ({
86
126
  statusText: e.reason,
87
127
  statusMessage: e.message,
88
128
  headers: {
89
- "content-type": requestedUrlInfo.contentType,
90
- "content-length": Buffer.byteLength(requestedUrlInfo.content),
129
+ "content-type": urlInfo.contentType,
130
+ "content-length": Buffer.byteLength(urlInfo.content),
91
131
  "cache-control": "no-store",
92
132
  },
93
- body: requestedUrlInfo.content,
133
+ body: urlInfo.content,
94
134
  }
95
135
  }
96
136
  if (code === "EISDIR") {
@@ -127,16 +167,7 @@ export const createFileService = ({
127
167
  }
128
168
  return async (request) => {
129
169
  let response = await getResponse(request)
130
- if (response.url) {
131
- kitchen.pluginController.callHooks(
132
- "augmentResponse",
133
- response,
134
- {},
135
- (returnValue) => {
136
- response = composeTwoResponses(response, returnValue)
137
- },
138
- )
139
- }
170
+
140
171
  return response
141
172
  }
142
173
  }
@@ -1,8 +1,10 @@
1
1
  import { createRequire } from "node:module"
2
2
 
3
+ import { memoizeByFirstArgument } from "@jsenv/utils/memoize/memoize_by_first_argument.js"
4
+
3
5
  const require = createRequire(import.meta.url)
4
6
 
5
- export const parseUserAgentHeader = (userAgent) => {
7
+ export const parseUserAgentHeader = memoizeByFirstArgument((userAgent) => {
6
8
  if (userAgent.includes("node-fetch/")) {
7
9
  // it's not really node and conceptually we can't assume the node version
8
10
  // but good enough for now
@@ -19,4 +21,4 @@ export const parseUserAgentHeader = (userAgent) => {
19
21
  runtimeVersion:
20
22
  family === "Other" ? "unknown" : `${major}.${minor}${patch}`,
21
23
  }
22
- }
24
+ })
@@ -1,27 +1,29 @@
1
1
  import { ensureEmptyDirectory } from "@jsenv/filesystem"
2
2
 
3
3
  export const loadUrlGraph = async ({
4
+ operation,
4
5
  urlGraph,
5
6
  kitchen,
6
7
  startLoading,
7
8
  outDirectoryUrl,
8
- runtimeSupport,
9
+ clientRuntimeCompat,
9
10
  }) => {
10
11
  if (outDirectoryUrl) {
11
12
  await ensureEmptyDirectory(outDirectoryUrl)
12
13
  }
13
14
  const promises = []
15
+ const promiseMap = new Map()
14
16
  const cook = ({ urlInfo, ...rest }) => {
15
- const promiseFromData = urlInfo.data.promise
17
+ const promiseFromData = promiseMap.get(urlInfo)
16
18
  if (promiseFromData) return promiseFromData
17
19
  const promise = _cook({
18
20
  urlInfo,
19
21
  outDirectoryUrl,
20
- runtimeSupport,
22
+ clientRuntimeCompat,
21
23
  ...rest,
22
24
  })
23
25
  promises.push(promise)
24
- urlInfo.data.promise = promise
26
+ promiseMap.set(urlInfo, promise)
25
27
  return promise
26
28
  }
27
29
  const _cook = async ({ urlInfo, ...rest }) => {
@@ -32,21 +34,32 @@ export const loadUrlGraph = async ({
32
34
  })
33
35
  const { references } = urlInfo
34
36
  references.forEach((reference) => {
37
+ // we don't cook ressource hints
38
+ // because they might refer to ressource that will be modified during build
39
+ // It also means something else have to reference that url in order to cook it
40
+ // so that the preload is deleted by "resync_ressource_hints.js" otherwise
41
+ if (reference.isRessourceHint) {
42
+ return
43
+ }
44
+ // we use reference.generatedUrl to mimic what a browser would do:
45
+ // do a fetch to the specifier as found in the file
46
+ const referencedUrlInfo = urlGraph.reuseOrCreateUrlInfo(
47
+ reference.generatedUrl,
48
+ )
35
49
  cook({
36
50
  reference,
37
- urlInfo: urlGraph.getUrlInfo(reference.url),
51
+ urlInfo: referencedUrlInfo,
38
52
  })
39
53
  })
40
54
  }
41
55
  startLoading(
42
56
  ({ trace, parentUrl = kitchen.rootDirectoryUrl, type, specifier }) => {
43
- const entryReference = kitchen.createReference({
57
+ const [entryReference, entryUrlInfo] = kitchen.prepareEntryPoint({
44
58
  trace,
45
59
  parentUrl,
46
60
  type,
47
61
  specifier,
48
62
  })
49
- const entryUrlInfo = kitchen.resolveReference(entryReference)
50
63
  entryUrlInfo.data.isEntryPoint = true
51
64
  cook({
52
65
  reference: entryReference,
@@ -57,6 +70,7 @@ export const loadUrlGraph = async ({
57
70
  )
58
71
 
59
72
  const waitAll = async () => {
73
+ operation.throwIfAborted()
60
74
  if (promises.length === 0) {
61
75
  return
62
76
  }
@@ -66,4 +80,5 @@ export const loadUrlGraph = async ({
66
80
  await waitAll()
67
81
  }
68
82
  await waitAll()
83
+ promiseMap.clear()
69
84
  }
@@ -1,37 +1,33 @@
1
- import { ANSI } from "@jsenv/log"
2
-
3
- import { byteAsFileSize } from "@jsenv/utils/logs/size_log.js"
1
+ import { ANSI, byteAsFileSize, distributePercentages } from "@jsenv/log"
4
2
 
5
3
  export const createUrlGraphSummary = (
6
4
  urlGraph,
7
5
  { title = "graph summary" } = {},
8
6
  ) => {
9
7
  const graphReport = createUrlGraphReport(urlGraph)
10
- const totalLabel = `Total`
11
8
  return `--- ${title} ---
12
9
  ${createRepartitionMessage(graphReport)}
13
- ${ANSI.color(totalLabel, ANSI.GREY)} ${
14
- graphReport.total.count
15
- } (${byteAsFileSize(graphReport.total.size)})
16
10
  --------------------`
17
11
  }
18
12
 
19
13
  const createUrlGraphReport = (urlGraph) => {
20
14
  const { urlInfos } = urlGraph
21
15
  const countGroups = {
16
+ sourcemaps: 0,
22
17
  html: 0,
23
18
  css: 0,
24
19
  js: 0,
25
- assets: 0,
26
- sourcemaps: 0,
20
+ json: 0,
21
+ other: 0,
27
22
  total: 0,
28
23
  }
29
24
  const sizeGroups = {
25
+ sourcemaps: 0,
30
26
  html: 0,
31
27
  css: 0,
32
28
  js: 0,
33
- sourcemaps: 0,
34
- assets: 0,
29
+ json: 0,
30
+ other: 0,
35
31
  total: 0,
36
32
  }
37
33
  Object.keys(urlInfos).forEach((url) => {
@@ -39,8 +35,10 @@ const createUrlGraphReport = (urlGraph) => {
39
35
  return
40
36
  }
41
37
  const urlInfo = urlInfos[url]
42
- // ignore inline files, they are already taken into account in the file where they appear
43
- if (urlInfo.isInline) {
38
+ // ignore:
39
+ // - inline files: they are already taken into account in the file where they appear
40
+ // - external files: we don't know their content
41
+ if (urlInfo.isInline || urlInfo.external) {
44
42
  return
45
43
  }
46
44
  // file loaded via import assertion are already inside the graph
@@ -49,9 +47,9 @@ const createUrlGraphReport = (urlGraph) => {
49
47
  // and only the js module remain (likely bundled)
50
48
  const urlObject = new URL(urlInfo.url)
51
49
  if (
52
- urlObject.searchParams.has("json_module") ||
53
- urlObject.searchParams.has("css_module") ||
54
- urlObject.searchParams.has("text_module")
50
+ urlObject.searchParams.has("as_json_module") ||
51
+ urlObject.searchParams.has("as_css_module") ||
52
+ urlObject.searchParams.has("as_text_module")
55
53
  ) {
56
54
  return
57
55
  }
@@ -79,17 +77,63 @@ const createUrlGraphReport = (urlGraph) => {
79
77
  sizeGroups.js += urlContentSize
80
78
  return
81
79
  }
82
- countGroups.assets++
83
- sizeGroups.assets += urlContentSize
80
+ if (category === "json") {
81
+ countGroups.json++
82
+ sizeGroups.json += urlContentSize
83
+ return
84
+ }
85
+ countGroups.other++
86
+ sizeGroups.other += urlContentSize
84
87
  return
85
88
  })
89
+
90
+ const sizesToDistribute = {}
91
+ Object.keys(sizeGroups).forEach((groupName) => {
92
+ if (groupName !== "sourcemaps" && groupName !== "total") {
93
+ sizesToDistribute[groupName] = sizeGroups[groupName]
94
+ }
95
+ })
96
+ const percentageGroups = distributePercentages(sizesToDistribute)
97
+
86
98
  return {
87
- html: { count: countGroups.html, size: sizeGroups.html },
88
- css: { count: countGroups.css, size: sizeGroups.css },
89
- js: { count: countGroups.js, size: sizeGroups.js },
90
- sourcemaps: { count: countGroups.sourcemaps, size: sizeGroups.sourcemaps },
91
- assets: { count: countGroups.assets, size: sizeGroups.assets },
92
- total: { count: countGroups.total, size: sizeGroups.total },
99
+ // sourcemaps are special, there size are ignored
100
+ // so there is no "percentage" associated
101
+ sourcemaps: {
102
+ count: countGroups.sourcemaps,
103
+ size: sizeGroups.sourcemaps,
104
+ percentage: undefined,
105
+ },
106
+
107
+ html: {
108
+ count: countGroups.html,
109
+ size: sizeGroups.html,
110
+ percentage: percentageGroups.html,
111
+ },
112
+ css: {
113
+ count: countGroups.css,
114
+ size: sizeGroups.css,
115
+ percentage: percentageGroups.css,
116
+ },
117
+ js: {
118
+ count: countGroups.js,
119
+ size: sizeGroups.js,
120
+ percentage: percentageGroups.js,
121
+ },
122
+ json: {
123
+ count: countGroups.json,
124
+ size: sizeGroups.json,
125
+ percentage: percentageGroups.json,
126
+ },
127
+ other: {
128
+ count: countGroups.other,
129
+ size: sizeGroups.other,
130
+ percentage: percentageGroups.other,
131
+ },
132
+ total: {
133
+ count: countGroups.total,
134
+ size: sizeGroups.total,
135
+ percentage: 100,
136
+ },
93
137
  }
94
138
  }
95
139
 
@@ -106,32 +150,22 @@ const determineCategory = (urlInfo) => {
106
150
  if (urlInfo.type === "js_module" || urlInfo.type === "js_classic") {
107
151
  return "js"
108
152
  }
109
- return "assets"
153
+ if (urlInfo.type === "json") {
154
+ return "json"
155
+ }
156
+ return "other"
110
157
  }
111
158
 
112
- const createRepartitionMessage = ({ html, css, js, assets }) => {
113
- const parts = []
114
- if (html.count) {
115
- parts.push(
116
- `${ANSI.color(`html:`, ANSI.GREY)} ${html.count} (${byteAsFileSize(
117
- html.size,
118
- )})`,
119
- )
120
- }
121
- if (css.count) {
159
+ const createRepartitionMessage = ({ html, css, js, json, other, total }) => {
160
+ const addPart = (name, { count, size, percentage }) => {
122
161
  parts.push(
123
- `${ANSI.color(`css:`, ANSI.GREY)} ${css.count} (${byteAsFileSize(
124
- css.size,
125
- )})`,
126
- )
127
- }
128
- if (js.count) {
129
- parts.push(
130
- `${ANSI.color(`js:`, ANSI.GREY)} ${js.count} (${byteAsFileSize(
131
- js.size,
132
- )})`,
162
+ `${ANSI.color(`${name}:`, ANSI.GREY)} ${count} (${byteAsFileSize(
163
+ size,
164
+ )} / ${percentage} %)`,
133
165
  )
134
166
  }
167
+
168
+ const parts = []
135
169
  // if (sourcemaps.count) {
136
170
  // parts.push(
137
171
  // `${ANSI.color(`sourcemaps:`, ANSI.GREY)} ${
@@ -139,13 +173,22 @@ const createRepartitionMessage = ({ html, css, js, assets }) => {
139
173
  // } (${byteAsFileSize(sourcemaps.size)})`,
140
174
  // )
141
175
  // }
142
- if (assets.count) {
143
- parts.push(
144
- `${ANSI.color(`assets:`, ANSI.GREY)} ${assets.count} (${byteAsFileSize(
145
- assets.size,
146
- )})`,
147
- )
176
+ if (html.count) {
177
+ addPart("html ", html)
178
+ }
179
+ if (css.count) {
180
+ addPart("css ", css)
181
+ }
182
+ if (js.count) {
183
+ addPart("js ", js)
184
+ }
185
+ if (json.count) {
186
+ addPart("json ", json)
187
+ }
188
+ if (other.count) {
189
+ addPart("other", other)
148
190
  }
191
+ addPart("total", total)
149
192
  return `- ${parts.join(`
150
193
  - `)}`
151
194
  }
@@ -1,3 +1,5 @@
1
+ import { bufferToEtag, urlToRelativeUrl } from "@jsenv/filesystem"
2
+
1
3
  import { composeTwoSourcemaps } from "@jsenv/utils/sourcemap/sourcemap_composition_v3.js"
2
4
  import {
3
5
  SOURCEMAP,
@@ -8,7 +10,8 @@ import {
8
10
  export const createUrlInfoTransformer = ({
9
11
  logger,
10
12
  sourcemaps,
11
- sourcemapsSources,
13
+ sourcemapsSourcesContent,
14
+ sourcemapsRelativeSources,
12
15
  urlGraph,
13
16
  injectSourcemapPlaceholder,
14
17
  foundSourcemap,
@@ -23,7 +26,12 @@ export const createUrlInfoTransformer = ({
23
26
  // for inline content (<script> insdide html)
24
27
  // chrome won't be able to fetch the file as it does not exists
25
28
  // so sourcemap must contain sources
26
- urlInfo.isInline || sourcemapsSources
29
+ sourcemapsSourcesContent ||
30
+ urlInfo.isInline ||
31
+ (sourcemap.sources &&
32
+ sourcemap.sources.some(
33
+ (source) => !source || !source.startsWith("file:"),
34
+ ))
27
35
  if (sourcemap.sources && sourcemap.sources.length > 1) {
28
36
  sourcemap.sources = sourcemap.sources.map(
29
37
  (source) => new URL(source, urlInfo.data.rawUrl || urlInfo.url).href,
@@ -78,9 +86,9 @@ export const createUrlInfoTransformer = ({
78
86
  const [sourcemapReference, sourcemapUrlInfo] = foundSourcemap({
79
87
  urlInfo,
80
88
  type,
81
- line,
82
- column,
83
89
  specifier,
90
+ specifierLine: line,
91
+ specifierColumn: column,
84
92
  })
85
93
  try {
86
94
  await context.cook({
@@ -137,20 +145,29 @@ export const createUrlInfoTransformer = ({
137
145
  const sourcemapReference = urlInfo.sourcemapReference
138
146
  const sourcemapUrlInfo = urlGraph.getUrlInfo(sourcemapReference.url)
139
147
  sourcemapUrlInfo.contentType = "application/json"
140
- sourcemapUrlInfo.content = JSON.stringify(urlInfo.sourcemap, null, " ")
148
+ const sourcemap = urlInfo.sourcemap
149
+ if (sourcemapsRelativeSources) {
150
+ sourcemap.sources = sourcemap.sources.map((source) => {
151
+ const sourceRelative = urlToRelativeUrl(source, urlInfo.url)
152
+ return sourceRelative
153
+ })
154
+ }
155
+ sourcemapUrlInfo.content = JSON.stringify(sourcemap, null, " ")
141
156
  if (sourcemaps === "inline") {
142
- sourcemapReference.generatedSpecifier = sourcemapToBase64Url(
143
- urlInfo.sourcemap,
144
- )
157
+ sourcemapReference.generatedSpecifier = sourcemapToBase64Url(sourcemap)
145
158
  }
146
159
  if (sourcemaps === "file" || sourcemaps === "inline") {
147
160
  urlInfo.content = SOURCEMAP.writeComment({
148
161
  contentType: urlInfo.contentType,
149
162
  content: urlInfo.content,
150
- specifier: sourcemapReference.generatedSpecifier,
163
+ specifier:
164
+ sourcemaps === "file" && sourcemapsRelativeSources
165
+ ? urlToRelativeUrl(sourcemapReference.url, urlInfo.url)
166
+ : sourcemapReference.generatedSpecifier,
151
167
  })
152
168
  }
153
169
  }
170
+ urlInfo.contentEtag = bufferToEtag(Buffer.from(urlInfo.content))
154
171
  }
155
172
 
156
173
  return {
@@ -1,10 +1,21 @@
1
- import { createCallbackList } from "@jsenv/abort"
2
1
  import { urlToRelativeUrl } from "@jsenv/filesystem"
3
2
 
4
- export const createUrlGraph = () => {
3
+ export const createUrlGraph = ({
4
+ clientFileChangeCallbackList,
5
+ clientFilesPruneCallbackList,
6
+ } = {}) => {
5
7
  const urlInfos = {}
6
8
  const getUrlInfo = (url) => urlInfos[url]
7
- const deleteUrlInfo = (url) => delete urlInfos[url]
9
+ const deleteUrlInfo = (url) => {
10
+ const urlInfo = urlInfos[url]
11
+ if (urlInfo) {
12
+ delete urlInfos[url]
13
+ if (urlInfo.sourcemapReference) {
14
+ deleteUrlInfo(urlInfo.sourcemapReference.url)
15
+ }
16
+ }
17
+ }
18
+
8
19
  const reuseOrCreateUrlInfo = (url) => {
9
20
  const existingUrlInfo = urlInfos[url]
10
21
  if (existingUrlInfo) return existingUrlInfo
@@ -40,13 +51,22 @@ export const createUrlGraph = () => {
40
51
  return visitDependents(urlInfo)
41
52
  }
42
53
 
43
- const prunedCallbackList = createCallbackList()
44
54
  const updateReferences = (urlInfo, references) => {
45
55
  const dependencyUrls = []
46
56
  references.forEach((reference) => {
47
- if (!dependencyUrls.includes(reference.url)) {
48
- dependencyUrls.push(reference.url)
57
+ if (reference.isRessourceHint) {
58
+ // ressource hint are a special kind of reference.
59
+ // They are a sort of weak reference to an url.
60
+ // We ignore them so that url referenced only by ressource hints
61
+ // have url.dependents.size === 0 and can be considered as not used
62
+ // It means html won't consider url referenced solely
63
+ // by <link> as dependency and it's fine
64
+ return
65
+ }
66
+ if (dependencyUrls.includes(reference.url)) {
67
+ return
49
68
  }
69
+ dependencyUrls.push(reference.url)
50
70
  })
51
71
  pruneDependencies(
52
72
  urlInfo,
@@ -82,7 +102,47 @@ export const createUrlGraph = () => {
82
102
  if (prunedUrlInfos.length === 0) {
83
103
  return
84
104
  }
85
- prunedCallbackList.notify({ prunedUrlInfos, firstUrlInfo })
105
+ prunedUrlInfos.forEach((prunedUrlInfo) => {
106
+ prunedUrlInfo.modifiedTimestamp = Date.now()
107
+ // should we delete?
108
+ // delete urlInfos[prunedUrlInfo.url]
109
+ })
110
+ if (clientFilesPruneCallbackList) {
111
+ clientFilesPruneCallbackList.forEach((callback) => {
112
+ callback({
113
+ firstUrlInfo,
114
+ prunedUrlInfos,
115
+ })
116
+ })
117
+ }
118
+ }
119
+
120
+ if (clientFileChangeCallbackList) {
121
+ const updateModifiedTimestamp = (urlInfo, modifiedTimestamp) => {
122
+ const seen = []
123
+ const iterate = (urlInfo) => {
124
+ if (seen.includes(urlInfo.url)) {
125
+ return
126
+ }
127
+ seen.push(urlInfo.url)
128
+ urlInfo.modifiedTimestamp = modifiedTimestamp
129
+ urlInfo.dependents.forEach((dependentUrl) => {
130
+ const dependentUrlInfo = urlInfos[dependentUrl]
131
+ const { hotAcceptDependencies = [] } = dependentUrlInfo.data
132
+ if (!hotAcceptDependencies.includes(urlInfo.url)) {
133
+ iterate(dependentUrlInfo)
134
+ }
135
+ })
136
+ }
137
+ iterate(urlInfo)
138
+ }
139
+ clientFileChangeCallbackList.push(({ url }) => {
140
+ const urlInfo = urlInfos[url]
141
+ if (urlInfo) {
142
+ updateModifiedTimestamp(urlInfo, Date.now())
143
+ urlInfo.contentEtag = null
144
+ }
145
+ })
86
146
  }
87
147
 
88
148
  return {
@@ -92,8 +152,6 @@ export const createUrlGraph = () => {
92
152
  deleteUrlInfo,
93
153
  inferReference,
94
154
  findDependent,
95
-
96
- prunedCallbackList,
97
155
  updateReferences,
98
156
 
99
157
  toJSON: (rootDirectoryUrl) => {
@@ -114,19 +172,25 @@ export const createUrlGraph = () => {
114
172
 
115
173
  const createUrlInfo = (url) => {
116
174
  return {
175
+ modifiedTimestamp: 0,
117
176
  data: {}, // plugins can put whatever they want here
177
+ references: [],
178
+ dependencies: new Set(),
179
+ dependents: new Set(),
180
+ type: undefined, // "html", "css", "js_classic", "js_module", "importmap", "json", "webmanifest", ...
181
+ subtype: undefined, // "worker", "service_worker", "shared_worker" for js, otherwise undefined
182
+ contentType: "", // "text/html", "text/css", "text/javascript", "application/json", ...
118
183
  url,
184
+ filename: "",
119
185
  generatedUrl: null,
120
186
  isInline: false,
121
187
  inlineUrlSite: null,
122
- contentType: "",
123
- originalContent: "",
124
- content: "",
188
+ external: false,
189
+ originalContent: undefined,
190
+ content: undefined,
191
+ contentEtag: null,
125
192
  sourcemap: null,
126
193
  sourcemapReference: null,
127
- type: "",
128
- references: [],
129
- dependencies: new Set(),
130
- dependents: new Set(),
194
+ timing: {},
131
195
  }
132
196
  }