@jsenv/core 27.0.0-alpha.5 → 27.0.0-alpha.50

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 (146) hide show
  1. package/dist/event_source_client.js +545 -0
  2. package/dist/event_source_client.js.map +187 -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 +22 -21
  11. package/readme.md +4 -12
  12. package/src/build/build.js +749 -437
  13. package/src/build/build_urls_generator.js +56 -22
  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 +255 -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 +136 -30
  22. package/src/execute/execute.js +31 -6
  23. package/src/execute/run.js +19 -56
  24. package/src/execute/runtimes/browsers/from_playwright.js +201 -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 +482 -292
  36. package/src/omega/omega_server.js +2 -3
  37. package/src/omega/server/file_service.js +38 -25
  38. package/src/omega/server/user_agent.js +4 -2
  39. package/src/omega/url_graph/url_graph_load.js +14 -7
  40. package/src/omega/url_graph/url_graph_report.js +17 -15
  41. package/src/omega/url_graph/url_info_transformations.js +26 -9
  42. package/src/omega/url_graph.js +91 -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 +2 -2
  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 +21 -0
  53. package/src/plugins/bundling/js_classic_workers/bundle_js_classic_workers.js +13 -0
  54. package/src/{build/plugins/bundle_js_module/jsenv_plugin_bundle_js_module.js → plugins/bundling/js_module/bundle_js_module.js} +150 -79
  55. package/src/plugins/bundling/jsenv_plugin_bundling.js +54 -0
  56. package/src/{omega/core_plugins → plugins}/commonjs_globals/jsenv_plugin_commonjs_globals.js +54 -41
  57. package/src/plugins/file_urls/jsenv_plugin_file_urls.js +66 -0
  58. package/src/{omega/core_plugins → plugins}/filesystem_magic/jsenv_plugin_filesystem_magic.js +8 -5
  59. package/src/{omega/core_plugins → plugins}/html_supervisor/client/error_in_document.js +0 -0
  60. package/src/{omega/core_plugins → plugins}/html_supervisor/client/error_in_notification.js +0 -0
  61. package/src/plugins/html_supervisor/client/html_supervisor_installer.js +242 -0
  62. package/src/plugins/html_supervisor/client/html_supervisor_setup.js +79 -0
  63. package/src/{omega/core_plugins → plugins}/html_supervisor/client/perf_browser.js +0 -0
  64. package/src/{omega/core_plugins → plugins}/html_supervisor/client/uneval_exception.js +0 -0
  65. package/src/{omega/core_plugins → plugins}/html_supervisor/jsenv_plugin_html_supervisor.js +83 -58
  66. package/src/plugins/http_urls/jsenv_plugin_http_urls.js +12 -0
  67. package/src/{dev/plugins/autoreload → plugins/import_meta_hot}/babel_plugin_metadata_import_meta_hot.js +4 -5
  68. package/src/{dev/plugins/autoreload → plugins/import_meta_hot}/client/import_meta_hot.js +3 -1
  69. package/src/{dev/plugins/autoreload → plugins/import_meta_hot}/html_hot_dependencies.js +2 -2
  70. package/src/plugins/import_meta_hot/jsenv_plugin_import_meta_hot.js +105 -0
  71. package/src/{omega/core_plugins → plugins}/import_meta_scenarios/jsenv_plugin_import_meta_scenarios.js +33 -8
  72. package/src/plugins/import_meta_url/client/import_meta_url_browser.js +52 -0
  73. package/src/plugins/import_meta_url/client/import_meta_url_commonjs.mjs +9 -0
  74. package/src/{omega/core_plugins → plugins}/importmap/jsenv_plugin_importmap.js +39 -33
  75. package/src/plugins/inject_globals/jsenv_plugin_inject_globals.js +67 -0
  76. package/src/{omega/core_plugins → plugins}/inline/client/inline_content.js +0 -0
  77. package/src/{omega/core_plugins → plugins}/inline/jsenv_plugin_data_urls.js +18 -14
  78. package/src/{omega/core_plugins/inline/jsenv_plugin_js_and_css_inside_html.js → plugins/inline/jsenv_plugin_html_inline_content.js} +65 -44
  79. package/src/plugins/inline/jsenv_plugin_inline.js +36 -0
  80. package/src/{omega/core_plugins → plugins}/inline/jsenv_plugin_inline_query_param.js +6 -6
  81. package/src/plugins/inline/jsenv_plugin_js_inline_content.js +296 -0
  82. package/src/plugins/leading_slash/jsenv_plugin_leading_slash.js +13 -0
  83. package/src/plugins/minification/css/minify_css.js +9 -0
  84. package/src/plugins/minification/html/minify_html.js +15 -0
  85. package/src/{build/plugins/minify_js/jsenv_plugin_minify_js.js → plugins/minification/js/minify_js.js} +6 -22
  86. package/src/plugins/minification/jsenv_plugin_minification.js +78 -0
  87. package/src/plugins/minification/json/minify_json.js +8 -0
  88. package/src/plugins/node_esm_resolution/jsenv_plugin_node_esm_resolution.js +146 -0
  89. package/src/{omega → plugins}/plugin_controller.js +42 -11
  90. package/src/plugins/plugins.js +91 -0
  91. package/src/plugins/transpilation/as_js_classic/client/s.js +808 -0
  92. package/src/plugins/transpilation/as_js_classic/client/s.js.md +1 -0
  93. package/src/plugins/transpilation/as_js_classic/helpers/babel_plugin_transform_import_meta_url.js +47 -0
  94. package/src/plugins/transpilation/as_js_classic/helpers/systemjs_old.js +43 -0
  95. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic.js +183 -0
  96. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_script_type_module_as_classic.js +256 -0
  97. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_workers_type_module_as_classic.js +55 -0
  98. package/src/{omega/core_plugins → plugins/transpilation}/babel/global_this/babel_plugin_global_this_as_jsenv_import.js +0 -0
  99. package/src/{omega/core_plugins → plugins/transpilation}/babel/global_this/client/global_this.js +0 -0
  100. package/src/{omega/core_plugins → plugins/transpilation}/babel/helpers/babel_plugin_babel_helpers_as_jsenv_imports.js +0 -0
  101. package/src/{omega/core_plugins → plugins/transpilation}/babel/helpers/babel_plugin_structure.js +4 -22
  102. package/src/{omega/core_plugins → plugins/transpilation}/babel/helpers/babel_plugins_compatibility.js +0 -0
  103. package/src/{omega/core_plugins → plugins/transpilation}/babel/jsenv_plugin_babel.js +37 -21
  104. package/src/{omega/core_plugins → plugins/transpilation}/babel/new_stylesheet/babel_plugin_new_stylesheet_as_jsenv_import.js +30 -6
  105. package/src/{omega/core_plugins → plugins/transpilation}/babel/new_stylesheet/client/.eslintrc.cjs +0 -0
  106. package/src/{omega/core_plugins → plugins/transpilation}/babel/new_stylesheet/client/new_stylesheet.js +0 -0
  107. package/src/{omega/core_plugins → plugins/transpilation}/babel/regenerator_runtime/babel_plugin_regenerator_runtime_as_jsenv_import.js +0 -0
  108. package/src/{omega/core_plugins → plugins/transpilation}/babel/regenerator_runtime/client/regenerator_runtime.js +0 -0
  109. package/src/plugins/transpilation/css_parcel/jsenv_plugin_css_parcel.js +18 -0
  110. package/src/plugins/transpilation/fetch_original_url_info.js +30 -0
  111. package/src/plugins/transpilation/import_assertions/jsenv_plugin_import_assertions.js +181 -0
  112. package/src/plugins/transpilation/jsenv_plugin_top_level_await.js +70 -0
  113. package/src/plugins/transpilation/jsenv_plugin_transpilation.js +44 -0
  114. package/src/plugins/url_analysis/css/css_urls.js +42 -0
  115. package/src/plugins/url_analysis/html/html_urls.js +273 -0
  116. package/src/plugins/url_analysis/js/js_urls.js +67 -0
  117. package/src/plugins/url_analysis/jsenv_plugin_url_analysis.js +18 -0
  118. package/src/plugins/url_analysis/webmanifest/webmanifest_urls.js +17 -0
  119. package/src/{omega/core_plugins → plugins}/url_resolution/jsenv_plugin_url_resolution.js +12 -5
  120. package/src/{omega/core_plugins → plugins}/url_version/jsenv_plugin_url_version.js +12 -15
  121. package/src/test/execute_plan.js +30 -18
  122. package/src/test/execute_test_plan.js +23 -8
  123. package/src/test/logs_file_execution.js +8 -7
  124. package/src/build/plugins/minify_html/jsenv_plugin_minify_html.js +0 -30
  125. package/src/dev/plugins/autoreload/client/event_source_connection.js +0 -195
  126. package/src/dev/plugins/autoreload/jsenv_plugin_autoreload.js +0 -374
  127. package/src/dev/plugins/autoreload/sse_service.js +0 -149
  128. package/src/execute/runtimes/node/controlled_process.js +0 -316
  129. package/src/omega/core_plugins/file_urls/jsenv_plugin_file_urls.js +0 -67
  130. package/src/omega/core_plugins/html_supervisor/client/html_supervisor_installer.js +0 -168
  131. package/src/omega/core_plugins/html_supervisor/client/html_supervisor_setup.js +0 -77
  132. package/src/omega/core_plugins/import_assertions/helpers/babel_plugin_metadata_import_assertions.js +0 -98
  133. package/src/omega/core_plugins/import_assertions/helpers/json_module.js +0 -12
  134. package/src/omega/core_plugins/import_assertions/helpers/text_module.js +0 -6
  135. package/src/omega/core_plugins/import_assertions/jsenv_plugin_import_assertions.js +0 -211
  136. package/src/omega/core_plugins/inline/jsenv_plugin_inline.js +0 -13
  137. package/src/omega/core_plugins/inline/jsenv_plugin_new_inline_content.js +0 -210
  138. package/src/omega/core_plugins/leading_slash/jsenv_plugin_leading_slash.js +0 -12
  139. package/src/omega/core_plugins/node_esm_resolution/jsenv_plugin_node_esm_resolution.js +0 -77
  140. package/src/omega/core_plugins.js +0 -39
  141. package/src/omega/runtime_support/runtime_support.js +0 -20
  142. package/src/omega/url_mentions/css_url_mentions.js +0 -63
  143. package/src/omega/url_mentions/html_url_mentions.js +0 -185
  144. package/src/omega/url_mentions/js_module_url_mentions.js +0 -91
  145. package/src/omega/url_mentions/parse_url_mentions.js +0 -37
  146. 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,
@@ -39,43 +39,65 @@ export const createFileService = ({
39
39
  if (responseFromPlugin) {
40
40
  return responseFromPlugin
41
41
  }
42
- const { runtimeName, runtimeVersion } = parseUserAgentHeader(
43
- request.headers["user-agent"],
44
- )
45
- const runtimeSupport = {
46
- [runtimeName]: runtimeVersion,
47
- }
48
- const reference = kitchen.createReference({
42
+ const [reference, urlInfo] = kitchen.prepareEntryPoint({
49
43
  parentUrl: inferParentFromRequest(request, rootDirectoryUrl),
50
44
  type: "entry_point",
51
45
  specifier: request.ressource,
52
46
  })
53
- const requestedUrlInfo = kitchen.resolveReference(reference)
47
+ const ifNoneMatch = request.headers["if-none-match"]
48
+ if (ifNoneMatch && urlInfo.contentEtag === ifNoneMatch) {
49
+ return {
50
+ status: 304,
51
+ headers: {
52
+ "cache-control": `private,max-age=0,must-revalidate`,
53
+ },
54
+ }
55
+ }
54
56
  const referenceFromGraph = urlGraph.inferReference(
55
57
  reference.url,
56
58
  reference.parentUrl,
57
59
  )
58
60
  try {
61
+ // urlInfo objects are reused, they must be "reset" before cooking then again
62
+ if (!urlInfo.isInline && !urlInfo.type === "sourcemap") {
63
+ urlGraph.resetUrlInfo(urlInfo)
64
+ }
65
+ const { runtimeName, runtimeVersion } = parseUserAgentHeader(
66
+ request.headers["user-agent"],
67
+ )
59
68
  await kitchen.cook({
60
69
  reference: referenceFromGraph || reference,
61
- urlInfo: requestedUrlInfo,
70
+ urlInfo,
62
71
  outDirectoryUrl: `${rootDirectoryUrl}.jsenv/${scenario}/${runtimeName}@${runtimeVersion}/`,
63
- runtimeSupport,
72
+ clientRuntimeCompat: {
73
+ [runtimeName]: runtimeVersion,
74
+ },
64
75
  })
65
- const { response, contentType, content } = requestedUrlInfo
76
+ let { response, contentType, content, contentEtag } = urlInfo
66
77
  if (response) {
67
78
  return response
68
79
  }
69
- return {
80
+ response = {
70
81
  url: reference.url,
71
82
  status: 200,
72
83
  headers: {
73
84
  "content-type": contentType,
74
85
  "content-length": Buffer.byteLength(content),
75
86
  "cache-control": `private,max-age=0,must-revalidate`,
87
+ "eTag": contentEtag,
76
88
  },
77
89
  body: content,
90
+ timing: urlInfo.timing,
78
91
  }
92
+ kitchen.pluginController.callHooks(
93
+ "augmentResponse",
94
+ { reference, urlInfo },
95
+ {},
96
+ (returnValue) => {
97
+ response = composeTwoResponses(response, returnValue)
98
+ },
99
+ )
100
+ return response
79
101
  } catch (e) {
80
102
  const code = e.code
81
103
  if (code === "PARSE_ERROR") {
@@ -86,11 +108,11 @@ export const createFileService = ({
86
108
  statusText: e.reason,
87
109
  statusMessage: e.message,
88
110
  headers: {
89
- "content-type": requestedUrlInfo.contentType,
90
- "content-length": Buffer.byteLength(requestedUrlInfo.content),
111
+ "content-type": urlInfo.contentType,
112
+ "content-length": Buffer.byteLength(urlInfo.content),
91
113
  "cache-control": "no-store",
92
114
  },
93
- body: requestedUrlInfo.content,
115
+ body: urlInfo.content,
94
116
  }
95
117
  }
96
118
  if (code === "EISDIR") {
@@ -127,16 +149,7 @@ export const createFileService = ({
127
149
  }
128
150
  return async (request) => {
129
151
  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
- }
152
+
140
153
  return response
141
154
  }
142
155
  }
@@ -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
+ })
@@ -5,23 +5,24 @@ export const loadUrlGraph = async ({
5
5
  kitchen,
6
6
  startLoading,
7
7
  outDirectoryUrl,
8
- runtimeSupport,
8
+ clientRuntimeCompat,
9
9
  }) => {
10
10
  if (outDirectoryUrl) {
11
11
  await ensureEmptyDirectory(outDirectoryUrl)
12
12
  }
13
13
  const promises = []
14
+ const promiseMap = new Map()
14
15
  const cook = ({ urlInfo, ...rest }) => {
15
- const promiseFromData = urlInfo.data.promise
16
+ const promiseFromData = promiseMap.get(urlInfo)
16
17
  if (promiseFromData) return promiseFromData
17
18
  const promise = _cook({
18
19
  urlInfo,
19
20
  outDirectoryUrl,
20
- runtimeSupport,
21
+ clientRuntimeCompat,
21
22
  ...rest,
22
23
  })
23
24
  promises.push(promise)
24
- urlInfo.data.promise = promise
25
+ promiseMap.set(urlInfo, promise)
25
26
  return promise
26
27
  }
27
28
  const _cook = async ({ urlInfo, ...rest }) => {
@@ -32,26 +33,31 @@ export const loadUrlGraph = async ({
32
33
  })
33
34
  const { references } = urlInfo
34
35
  references.forEach((reference) => {
36
+ // we use reference.generatedUrl to mimic what a browser would do:
37
+ // do a fetch to the specifier as found in the file
38
+ const referencedUrlInfo = urlGraph.reuseOrCreateUrlInfo(
39
+ reference.generatedUrl,
40
+ )
35
41
  cook({
36
42
  reference,
37
- urlInfo: urlGraph.getUrlInfo(reference.url),
43
+ urlInfo: referencedUrlInfo,
38
44
  })
39
45
  })
40
46
  }
41
47
  startLoading(
42
48
  ({ trace, parentUrl = kitchen.rootDirectoryUrl, type, specifier }) => {
43
- const entryReference = kitchen.createReference({
49
+ const [entryReference, entryUrlInfo] = kitchen.prepareEntryPoint({
44
50
  trace,
45
51
  parentUrl,
46
52
  type,
47
53
  specifier,
48
54
  })
49
- const entryUrlInfo = kitchen.resolveReference(entryReference)
50
55
  entryUrlInfo.data.isEntryPoint = true
51
56
  cook({
52
57
  reference: entryReference,
53
58
  urlInfo: entryUrlInfo,
54
59
  })
60
+ return [entryReference, entryUrlInfo]
55
61
  },
56
62
  )
57
63
 
@@ -65,4 +71,5 @@ export const loadUrlGraph = async ({
65
71
  await waitAll()
66
72
  }
67
73
  await waitAll()
74
+ promiseMap.clear()
68
75
  }
@@ -22,7 +22,7 @@ const createUrlGraphReport = (urlGraph) => {
22
22
  html: 0,
23
23
  css: 0,
24
24
  js: 0,
25
- assets: 0,
25
+ other: 0,
26
26
  sourcemaps: 0,
27
27
  total: 0,
28
28
  }
@@ -31,7 +31,7 @@ const createUrlGraphReport = (urlGraph) => {
31
31
  css: 0,
32
32
  js: 0,
33
33
  sourcemaps: 0,
34
- assets: 0,
34
+ other: 0,
35
35
  total: 0,
36
36
  }
37
37
  Object.keys(urlInfos).forEach((url) => {
@@ -39,8 +39,10 @@ const createUrlGraphReport = (urlGraph) => {
39
39
  return
40
40
  }
41
41
  const urlInfo = urlInfos[url]
42
- // ignore inline files, they are already taken into account in the file where they appear
43
- if (urlInfo.isInline) {
42
+ // ignore:
43
+ // - inline files: they are already taken into account in the file where they appear
44
+ // - external files: we don't know their content
45
+ if (urlInfo.isInline || urlInfo.external) {
44
46
  return
45
47
  }
46
48
  // file loaded via import assertion are already inside the graph
@@ -49,9 +51,9 @@ const createUrlGraphReport = (urlGraph) => {
49
51
  // and only the js module remain (likely bundled)
50
52
  const urlObject = new URL(urlInfo.url)
51
53
  if (
52
- urlObject.searchParams.has("json_module") ||
53
- urlObject.searchParams.has("css_module") ||
54
- urlObject.searchParams.has("text_module")
54
+ urlObject.searchParams.has("as_json_module") ||
55
+ urlObject.searchParams.has("as_css_module") ||
56
+ urlObject.searchParams.has("as_text_module")
55
57
  ) {
56
58
  return
57
59
  }
@@ -79,8 +81,8 @@ const createUrlGraphReport = (urlGraph) => {
79
81
  sizeGroups.js += urlContentSize
80
82
  return
81
83
  }
82
- countGroups.assets++
83
- sizeGroups.assets += urlContentSize
84
+ countGroups.other++
85
+ sizeGroups.other += urlContentSize
84
86
  return
85
87
  })
86
88
  return {
@@ -88,7 +90,7 @@ const createUrlGraphReport = (urlGraph) => {
88
90
  css: { count: countGroups.css, size: sizeGroups.css },
89
91
  js: { count: countGroups.js, size: sizeGroups.js },
90
92
  sourcemaps: { count: countGroups.sourcemaps, size: sizeGroups.sourcemaps },
91
- assets: { count: countGroups.assets, size: sizeGroups.assets },
93
+ other: { count: countGroups.other, size: sizeGroups.other },
92
94
  total: { count: countGroups.total, size: sizeGroups.total },
93
95
  }
94
96
  }
@@ -106,10 +108,10 @@ const determineCategory = (urlInfo) => {
106
108
  if (urlInfo.type === "js_module" || urlInfo.type === "js_classic") {
107
109
  return "js"
108
110
  }
109
- return "assets"
111
+ return "other"
110
112
  }
111
113
 
112
- const createRepartitionMessage = ({ html, css, js, assets }) => {
114
+ const createRepartitionMessage = ({ html, css, js, other }) => {
113
115
  const parts = []
114
116
  if (html.count) {
115
117
  parts.push(
@@ -139,10 +141,10 @@ const createRepartitionMessage = ({ html, css, js, assets }) => {
139
141
  // } (${byteAsFileSize(sourcemaps.size)})`,
140
142
  // )
141
143
  // }
142
- if (assets.count) {
144
+ if (other.count) {
143
145
  parts.push(
144
- `${ANSI.color(`assets:`, ANSI.GREY)} ${assets.count} (${byteAsFileSize(
145
- assets.size,
146
+ `${ANSI.color(`other:`, ANSI.GREY)} ${other.count} (${byteAsFileSize(
147
+ other.size,
146
148
  )})`,
147
149
  )
148
150
  }
@@ -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,31 @@
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
+ const resetUrlInfo = (urlInfo) => {
19
+ urlInfo.sourcemap = null
20
+ urlInfo.sourcemapReference = null
21
+ urlInfo.content = null
22
+ urlInfo.originalContent = null
23
+ urlInfo.type = null
24
+ urlInfo.subtype = null
25
+ urlInfo.data = {}
26
+ urlInfo.timing = {}
27
+ }
28
+
8
29
  const reuseOrCreateUrlInfo = (url) => {
9
30
  const existingUrlInfo = urlInfos[url]
10
31
  if (existingUrlInfo) return existingUrlInfo
@@ -40,13 +61,22 @@ export const createUrlGraph = () => {
40
61
  return visitDependents(urlInfo)
41
62
  }
42
63
 
43
- const prunedCallbackList = createCallbackList()
44
64
  const updateReferences = (urlInfo, references) => {
45
65
  const dependencyUrls = []
46
66
  references.forEach((reference) => {
47
- if (!dependencyUrls.includes(reference.url)) {
48
- dependencyUrls.push(reference.url)
67
+ if (reference.isRessourceHint) {
68
+ // ressource hint are a special kind of reference.
69
+ // They are a sort of weak reference to an url.
70
+ // We ignore them so that url referenced only by ressource hints
71
+ // have url.dependents.size === 0 and can be considered as not used
72
+ // It means html won't consider url referenced solely
73
+ // by <link> as dependency and it's fine
74
+ return
75
+ }
76
+ if (dependencyUrls.includes(reference.url)) {
77
+ return
49
78
  }
79
+ dependencyUrls.push(reference.url)
50
80
  })
51
81
  pruneDependencies(
52
82
  urlInfo,
@@ -82,7 +112,47 @@ export const createUrlGraph = () => {
82
112
  if (prunedUrlInfos.length === 0) {
83
113
  return
84
114
  }
85
- prunedCallbackList.notify({ prunedUrlInfos, firstUrlInfo })
115
+ prunedUrlInfos.forEach((prunedUrlInfo) => {
116
+ prunedUrlInfo.modifiedTimestamp = Date.now()
117
+ // should we delete?
118
+ // delete urlInfos[prunedUrlInfo.url]
119
+ })
120
+ if (clientFilesPruneCallbackList) {
121
+ clientFilesPruneCallbackList.forEach((callback) => {
122
+ callback({
123
+ firstUrlInfo,
124
+ prunedUrlInfos,
125
+ })
126
+ })
127
+ }
128
+ }
129
+
130
+ if (clientFileChangeCallbackList) {
131
+ const updateModifiedTimestamp = (urlInfo, modifiedTimestamp) => {
132
+ const seen = []
133
+ const iterate = (urlInfo) => {
134
+ if (seen.includes(urlInfo.url)) {
135
+ return
136
+ }
137
+ seen.push(urlInfo.url)
138
+ urlInfo.modifiedTimestamp = modifiedTimestamp
139
+ urlInfo.dependents.forEach((dependentUrl) => {
140
+ const dependentUrlInfo = urlInfos[dependentUrl]
141
+ const { hotAcceptDependencies = [] } = dependentUrlInfo.data
142
+ if (!hotAcceptDependencies.includes(urlInfo.url)) {
143
+ iterate(dependentUrlInfo)
144
+ }
145
+ })
146
+ }
147
+ iterate(urlInfo)
148
+ }
149
+ clientFileChangeCallbackList.push(({ url }) => {
150
+ const urlInfo = urlInfos[url]
151
+ if (urlInfo) {
152
+ updateModifiedTimestamp(urlInfo, Date.now())
153
+ urlInfo.contentEtag = null
154
+ }
155
+ })
86
156
  }
87
157
 
88
158
  return {
@@ -90,10 +160,9 @@ export const createUrlGraph = () => {
90
160
  reuseOrCreateUrlInfo,
91
161
  getUrlInfo,
92
162
  deleteUrlInfo,
163
+ resetUrlInfo,
93
164
  inferReference,
94
165
  findDependent,
95
-
96
- prunedCallbackList,
97
166
  updateReferences,
98
167
 
99
168
  toJSON: (rootDirectoryUrl) => {
@@ -114,19 +183,25 @@ export const createUrlGraph = () => {
114
183
 
115
184
  const createUrlInfo = (url) => {
116
185
  return {
186
+ modifiedTimestamp: 0,
117
187
  data: {}, // plugins can put whatever they want here
188
+ references: [],
189
+ dependencies: new Set(),
190
+ dependents: new Set(),
191
+ type: undefined, // "html", "css", "js_classic", "js_module", "importmap", "json", "webmanifest", ...
192
+ subtype: undefined, // "worker", "service_worker", "shared_worker" for js, otherwise undefined
193
+ contentType: "", // "text/html", "text/css", "text/javascript", "application/json", ...
118
194
  url,
195
+ filename: "",
119
196
  generatedUrl: null,
120
197
  isInline: false,
121
198
  inlineUrlSite: null,
122
- contentType: "",
123
- originalContent: "",
124
- content: "",
199
+ external: false,
200
+ originalContent: undefined,
201
+ content: undefined,
202
+ contentEtag: null,
125
203
  sourcemap: null,
126
204
  sourcemapReference: null,
127
- type: "",
128
- references: [],
129
- dependencies: new Set(),
130
- dependents: new Set(),
205
+ timing: {},
131
206
  }
132
207
  }
@@ -0,0 +1,42 @@
1
+ // the following apis are creating js entry points:
2
+ // - new Worker()
3
+ // - new SharedWorker()
4
+ // - navigator.serviceWorker.register()
5
+ export const isWebWorkerEntryPointReference = (reference) => {
6
+ if (reference.subtype === "new_url_first_arg") {
7
+ return ["worker", "service_worker", "shared_worker"].includes(
8
+ reference.expectedSubtype,
9
+ )
10
+ }
11
+ return [
12
+ "new_worker_first_arg",
13
+ "new_shared_worker_first_arg",
14
+ "service_worker_register_first_arg",
15
+ ].includes(reference.subtype)
16
+ }
17
+
18
+ export const isWebWorkerUrlInfo = (urlInfo) => {
19
+ return (
20
+ urlInfo.subtype === "worker" ||
21
+ urlInfo.subtype === "service_worker" ||
22
+ urlInfo.subtype === "shared_worker"
23
+ )
24
+ }
25
+
26
+ // export const isEntryPoint = (urlInfo, urlGraph) => {
27
+ // if (urlInfo.data.isEntryPoint) {
28
+ // return true
29
+ // }
30
+ // if (isWebWorker(urlInfo)) {
31
+ // // - new Worker("a.js") -> "a.js" is an entry point
32
+ // // - self.importScripts("b.js") -> "b.js" is not an entry point
33
+ // // So the following logic applies to infer if the file is a web worker entry point
34
+ // // "When a non-webworker file references a worker file, the worker file is an entry point"
35
+ // const dependents = Array.from(urlInfo.dependents)
36
+ // return dependents.some((dependentUrl) => {
37
+ // const dependentUrlInfo = urlGraph.getUrlInfo(dependentUrl)
38
+ // return !isWebWorker(dependentUrlInfo)
39
+ // })
40
+ // }
41
+ // return false
42
+ // }
@@ -1,4 +1,5 @@
1
- import { createEventSourceConnection } from "./event_source_connection.js"
1
+ import { createEventSourceConnection } from "@jsenv/utils/event_source/event_source.js"
2
+ import { urlHotMetas } from "../../../import_meta_hot/client/import_meta_hot.js"
2
3
  import {
3
4
  isAutoreloadEnabled,
4
5
  setAutoreloadPreference,
@@ -9,7 +10,6 @@ import {
9
10
  reloadDOMNodesUsingUrl,
10
11
  reloadJsImport,
11
12
  } from "./reload.js"
12
- import { urlHotMetas } from "./import_meta_hot.js"
13
13
 
14
14
  const reloadMessages = []
15
15
  const reloadMessagesSignal = { onchange: () => {} }
@@ -0,0 +1,46 @@
1
+ import { urlIsInsideOf } from "@jsenv/filesystem"
2
+
3
+ import {
4
+ parseHtmlString,
5
+ stringifyHtmlAst,
6
+ injectScriptAsEarlyAsPossible,
7
+ createHtmlNode,
8
+ } from "@jsenv/utils/html_ast/html_ast.js"
9
+ import { jsenvRootDirectoryUrl } from "@jsenv/core/src/jsenv_root_directory_url.js"
10
+
11
+ export const jsenvPluginDevSSEClient = ({ rootDirectoryUrl }) => {
12
+ const preferSourceFiles =
13
+ rootDirectoryUrl === jsenvRootDirectoryUrl ||
14
+ urlIsInsideOf(rootDirectoryUrl, jsenvRootDirectoryUrl)
15
+ const eventSourceClientFileUrl = preferSourceFiles
16
+ ? new URL("./client/event_source_client.js", import.meta.url).href
17
+ : new URL("./dist/event_source_client.js", jsenvRootDirectoryUrl).href
18
+
19
+ return {
20
+ name: "jsenv:dev_sse_client",
21
+ appliesDuring: { dev: true },
22
+ transformUrlContent: {
23
+ html: (htmlUrlInfo, context) => {
24
+ const htmlAst = parseHtmlString(htmlUrlInfo.content)
25
+ const [eventSourceClientReference] = context.referenceUtils.inject({
26
+ type: "script_src",
27
+ expectedType: "js_module",
28
+ specifier: eventSourceClientFileUrl,
29
+ })
30
+ injectScriptAsEarlyAsPossible(
31
+ htmlAst,
32
+ createHtmlNode({
33
+ "tagName": "script",
34
+ "type": "module",
35
+ "src": eventSourceClientReference.generatedSpecifier,
36
+ "injected-by": "jsenv:dev_sse_client",
37
+ }),
38
+ )
39
+ const htmlModified = stringifyHtmlAst(htmlAst)
40
+ return {
41
+ content: htmlModified,
42
+ }
43
+ },
44
+ },
45
+ }
46
+ }