@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
@@ -5,19 +5,19 @@ import {
5
5
  getHtmlNodeTextNode,
6
6
  htmlNodePosition,
7
7
  parseScriptNode,
8
- setHtmlNodeText,
8
+ setHtmlNodeGeneratedText,
9
9
  getHtmlNodeAttributeByName,
10
10
  } from "@jsenv/utils/html_ast/html_ast.js"
11
- import { injectQueryParams } from "@jsenv/utils/urls/url_utils.js"
12
11
  import { generateInlineContentUrl } from "@jsenv/utils/urls/inline_content_url_generator.js"
12
+ import { CONTENT_TYPE } from "@jsenv/utils/content_type/content_type.js"
13
13
 
14
- export const jsenvPluginJsAndCssInsideHtml = () => {
14
+ export const jsenvPluginHtmlInlineContent = ({ analyzeConvertedScripts }) => {
15
15
  return {
16
- name: "jsenv:js_and_css_inside_html",
16
+ name: "jsenv:html_inline_content",
17
17
  appliesDuring: "*",
18
- transform: {
19
- html: async ({ url, content }, { cook, referenceUtils }) => {
20
- const htmlAst = parseHtmlString(content)
18
+ transformUrlContent: {
19
+ html: async (urlInfo, context) => {
20
+ const htmlAst = parseHtmlString(urlInfo.content)
21
21
  const actions = []
22
22
  const handleInlineStyle = (node) => {
23
23
  if (node.nodeName !== "style") {
@@ -33,7 +33,7 @@ export const jsenvPluginJsAndCssInsideHtml = () => {
33
33
  preferOriginal: true,
34
34
  })
35
35
  const inlineStyleUrl = generateInlineContentUrl({
36
- url,
36
+ url: urlInfo.url,
37
37
  extension: ".css",
38
38
  line,
39
39
  column,
@@ -41,23 +41,27 @@ export const jsenvPluginJsAndCssInsideHtml = () => {
41
41
  columnEnd,
42
42
  })
43
43
  const [inlineStyleReference, inlineStyleUrlInfo] =
44
- referenceUtils.foundInline({
44
+ context.referenceUtils.foundInline({
45
45
  type: "link_href",
46
+ expectedType: "css",
47
+ isOriginalPosition: isOriginal,
46
48
  // we remove 1 to the line because imagine the following html:
47
49
  // <style>body { color: red; }</style>
48
50
  // -> content starts same line as <style>
49
- line: line - 1,
50
- column,
51
- isOriginal,
51
+ specifierLine: line - 1,
52
+ specifierColumn: column,
52
53
  specifier: inlineStyleUrl,
53
54
  contentType: "text/css",
54
55
  content: textNode.value,
55
56
  })
56
- await cook({
57
+ await context.cook({
57
58
  reference: inlineStyleReference,
58
59
  urlInfo: inlineStyleUrlInfo,
59
60
  })
60
- setHtmlNodeText(node, inlineStyleUrlInfo.content)
61
+ setHtmlNodeGeneratedText(node, {
62
+ generatedText: inlineStyleUrlInfo.content,
63
+ generatedBy: "jsenv:html_inline_content",
64
+ })
61
65
  })
62
66
  }
63
67
  const handleInlineScript = (node) => {
@@ -68,14 +72,19 @@ export const jsenvPluginJsAndCssInsideHtml = () => {
68
72
  if (!textNode) {
69
73
  return
70
74
  }
71
- const contentSrc = getHtmlNodeAttributeByName(node, "content-src")
72
- if (contentSrc) {
73
- // it's inline but there is a corresponding "src" somewhere
74
- // - for instance the importmap was inlined by importmap plugin
75
- // in that case the content is already cooked and can be kept as it is
76
- // - any other logic that would turn a remote script into some content
77
- // but don't want to cook the content
78
- return
75
+ // If the inline script was already handled by an other plugin, ignore it
76
+ // - we want to preserve inline scripts generated by html supervisor during dev
77
+ // - we want to avoid cooking twice a script during build
78
+ const generatedBy = getHtmlNodeAttributeByName(node, "generated-by")
79
+ if (generatedBy) {
80
+ if (generatedBy.value === "jsenv:script_type_module_as_classic") {
81
+ if (!analyzeConvertedScripts) {
82
+ return
83
+ }
84
+ }
85
+ if (generatedBy.value === "jsenv:html_supervisor") {
86
+ return
87
+ }
79
88
  }
80
89
  actions.push(async () => {
81
90
  const scriptCategory = parseScriptNode(node)
@@ -83,41 +92,57 @@ export const jsenvPluginJsAndCssInsideHtml = () => {
83
92
  htmlNodePosition.readNodePosition(node, {
84
93
  preferOriginal: true,
85
94
  })
95
+ // from MDN about [type] attribute:
96
+ // "Any other value: The embedded content is treated as a data block
97
+ // which won't be processed by the browser. Developers must use a valid MIME type
98
+ // that is not a JavaScript MIME type to denote data blocks.
99
+ // The src attribute will be ignored."
100
+ // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-type
101
+ const isJs =
102
+ scriptCategory === "classic" || scriptCategory === "module"
103
+ const isImportmap = scriptCategory === "importmap"
104
+ const contentType = isJs
105
+ ? "text/javascript"
106
+ : isImportmap
107
+ ? "application/importmap+json"
108
+ : scriptCategory
109
+
86
110
  let inlineScriptUrl = generateInlineContentUrl({
87
- url,
88
- extension: scriptCategory === "importmap" ? ".importmap" : ".js",
111
+ url: urlInfo.url,
112
+ extension: CONTENT_TYPE.asFileExtension(contentType),
89
113
  line,
90
114
  column,
91
115
  lineEnd,
92
116
  columnEnd,
93
117
  })
94
- if (scriptCategory === "classic") {
95
- inlineScriptUrl = injectQueryParams(inlineScriptUrl, {
96
- js_classic: "",
97
- })
98
- }
99
118
  const [inlineScriptReference, inlineScriptUrlInfo] =
100
- referenceUtils.foundInline({
119
+ context.referenceUtils.foundInline({
101
120
  node,
102
121
  type: "script_src",
122
+ expectedType: {
123
+ classic: "js_classic",
124
+ module: "js_module",
125
+ importmap: "importmap",
126
+ }[scriptCategory],
103
127
  // we remove 1 to the line because imagine the following html:
104
128
  // <script>console.log('ok')</script>
105
129
  // -> content starts same line as <script>
106
- line: line - 1,
107
- column,
108
- isOriginal,
130
+ specifierLine: line - 1,
131
+ specifierColumn: column,
132
+ isOriginalPosition: isOriginal,
109
133
  specifier: inlineScriptUrl,
110
- contentType:
111
- scriptCategory === "importmap"
112
- ? "application/importmap+json"
113
- : "application/javascript",
134
+ contentType,
114
135
  content: textNode.value,
115
136
  })
116
- await cook({
137
+
138
+ await context.cook({
117
139
  reference: inlineScriptReference,
118
140
  urlInfo: inlineScriptUrlInfo,
119
141
  })
120
- setHtmlNodeText(node, inlineScriptUrlInfo.content)
142
+ setHtmlNodeGeneratedText(node, {
143
+ generatedText: inlineScriptUrlInfo.content,
144
+ generatedBy: "jsenv:html_inline_content",
145
+ })
121
146
  })
122
147
  }
123
148
  visitHtmlAst(htmlAst, (node) => {
@@ -127,11 +152,7 @@ export const jsenvPluginJsAndCssInsideHtml = () => {
127
152
  if (actions.length === 0) {
128
153
  return null
129
154
  }
130
- await Promise.all(
131
- actions.map(async (action) => {
132
- await action()
133
- }),
134
- )
155
+ await Promise.all(actions.map((action) => action()))
135
156
  const htmlModified = stringifyHtmlAst(htmlAst)
136
157
  return {
137
158
  content: htmlModified,
@@ -0,0 +1,36 @@
1
+ import { jsenvPluginHtmlInlineContent } from "./jsenv_plugin_html_inline_content.js"
2
+ import { jsenvPluginJsInlineContent } from "./jsenv_plugin_js_inline_content.js"
3
+ import { jsenvPluginDataUrls } from "./jsenv_plugin_data_urls.js"
4
+ import { jsenvPluginInlineQueryParam } from "./jsenv_plugin_inline_query_param.js"
5
+
6
+ export const jsenvPluginInline = ({
7
+ fetchInlineUrls = true,
8
+ analyzeConvertedScripts = false,
9
+ allowEscapeForVersioning = false,
10
+ } = {}) => {
11
+ return [
12
+ ...(fetchInlineUrls ? [jsenvPluginInlineUrls()] : []),
13
+ jsenvPluginHtmlInlineContent({ analyzeConvertedScripts }),
14
+ jsenvPluginJsInlineContent({ allowEscapeForVersioning }),
15
+ jsenvPluginDataUrls(),
16
+ jsenvPluginInlineQueryParam(),
17
+ ]
18
+ }
19
+
20
+ const jsenvPluginInlineUrls = () => {
21
+ return {
22
+ name: "jsenv:inline_urls",
23
+ appliesDuring: "*",
24
+ fetchUrlContent: (urlInfo) => {
25
+ if (!urlInfo.isInline) {
26
+ return null
27
+ }
28
+ return {
29
+ contentType: urlInfo.contentType,
30
+ // we want to fetch the original content otherwise we might re-cook
31
+ // content already cooked
32
+ content: urlInfo.originalContent,
33
+ }
34
+ },
35
+ }
36
+ }
@@ -4,7 +4,7 @@ export const jsenvPluginInlineQueryParam = () => {
4
4
  return {
5
5
  name: "jsenv:inline_query_param",
6
6
  appliesDuring: "*",
7
- formatReferencedUrl: {
7
+ formatUrl: {
8
8
  // <link> and <script> can be inlined in the html
9
9
  // this should be done during dev and postbuild but not build
10
10
  // so that the bundled file gets inlined and not the entry point
@@ -15,17 +15,17 @@ export const jsenvPluginInlineQueryParam = () => {
15
15
  // but maybe we should rather use ?object_url
16
16
  // or people could do this:
17
17
  // import workerText from './worker.js?text'
18
- // const blob = new Blob(workerText, { type: 'application/javascript' })
18
+ // const blob = new Blob(workerText, { type: 'text/javascript' })
19
19
  // window.URL.createObjectURL(blob)
20
20
  // in any case the recommended way is to use an url
21
21
  // to benefit from shared worker and reuse worker between tabs
22
- "*": (reference, { urlGraph, cook }) => {
23
- if (!new URL(reference.url).searchParams.has("inline")) {
22
+ "*": (reference, context) => {
23
+ if (!reference.searchParams.has("inline")) {
24
24
  return null
25
25
  }
26
26
  return (async () => {
27
- const urlInfo = urlGraph.getUrlInfo(reference.url)
28
- await cook({
27
+ const urlInfo = context.urlGraph.getUrlInfo(reference.url)
28
+ await context.cook({
29
29
  reference,
30
30
  urlInfo,
31
31
  })
@@ -0,0 +1,297 @@
1
+ import { CONTENT_TYPE } from "@jsenv/utils/content_type/content_type.js"
2
+ import { createMagicSource } from "@jsenv/utils/sourcemap/magic_source.js"
3
+ import { JS_QUOTES } from "@jsenv/utils/string/js_quotes.js"
4
+ import { applyBabelPlugins } from "@jsenv/utils/js_ast/apply_babel_plugins.js"
5
+ import { generateInlineContentUrl } from "@jsenv/utils/urls/inline_content_url_generator.js"
6
+
7
+ export const jsenvPluginJsInlineContent = ({ allowEscapeForVersioning }) => {
8
+ const parseAndTransformInlineContentCalls = async (urlInfo, context) => {
9
+ const inlineContentInfos = await parseJsInlineContentInfos({
10
+ js: urlInfo.content,
11
+ url: (urlInfo.data && urlInfo.data.rawUrl) || urlInfo.url,
12
+ isJsModule: urlInfo.type === "js_module",
13
+ })
14
+ if (inlineContentInfos.length === 0) {
15
+ return null
16
+ }
17
+ const magicSource = createMagicSource(urlInfo.content)
18
+ await inlineContentInfos.reduce(async (previous, inlineContentInfo) => {
19
+ await previous
20
+ const inlineUrl = generateInlineContentUrl({
21
+ url: urlInfo.url,
22
+ extension: CONTENT_TYPE.asFileExtension(inlineContentInfo.contentType),
23
+ line: inlineContentInfo.line,
24
+ column: inlineContentInfo.column,
25
+ lineEnd: inlineContentInfo.lineEnd,
26
+ columnEnd: inlineContentInfo.columnEnd,
27
+ })
28
+ let { quote } = inlineContentInfo
29
+ if (
30
+ quote === "`" &&
31
+ !context.isSupportedOnCurrentClients("template_literals")
32
+ ) {
33
+ // if quote is "`" and template literals are not supported
34
+ // we'll use a regular string (single or double quote)
35
+ // when rendering the string
36
+ quote = JS_QUOTES.pickBest(inlineContentInfo.content)
37
+ }
38
+ const [inlineReference, inlineUrlInfo] =
39
+ context.referenceUtils.foundInline({
40
+ type: "js_inline_content",
41
+ subtype: inlineContentInfo.type, // "new_blob_first_arg", "new_inline_content_first_arg", "json_parse_first_arg"
42
+ isOriginalPosition: urlInfo.content === urlInfo.originalContent,
43
+ specifierLine: inlineContentInfo.line,
44
+ specifierColumn: inlineContentInfo.column,
45
+ specifier: inlineUrl,
46
+ contentType: inlineContentInfo.contentType,
47
+ content: inlineContentInfo.content,
48
+ })
49
+ inlineUrlInfo.jsQuote = quote
50
+ inlineReference.escape = (value) =>
51
+ JS_QUOTES.escapeSpecialChars(value.slice(1, -1), { quote })
52
+ await context.cook({
53
+ reference: inlineReference,
54
+ urlInfo: inlineUrlInfo,
55
+ })
56
+ magicSource.replace({
57
+ start: inlineContentInfo.start,
58
+ end: inlineContentInfo.end,
59
+ replacement: JS_QUOTES.escapeSpecialChars(inlineUrlInfo.content, {
60
+ quote,
61
+ allowEscapeForVersioning,
62
+ }),
63
+ })
64
+ }, Promise.resolve())
65
+ return magicSource.toContentAndSourcemap()
66
+ }
67
+
68
+ return {
69
+ name: "jsenv:js_inline_content",
70
+ appliesDuring: "*",
71
+ transformUrlContent: {
72
+ js_classic: parseAndTransformInlineContentCalls,
73
+ js_module: parseAndTransformInlineContentCalls,
74
+ },
75
+ }
76
+ }
77
+
78
+ const parseJsInlineContentInfos = async ({ js, url, isJsModule }) => {
79
+ if (
80
+ !js.includes("InlineContent") &&
81
+ !js.includes("new Blob(") &&
82
+ !js.includes("JSON.parse(")
83
+ ) {
84
+ return []
85
+ }
86
+ const { metadata } = await applyBabelPlugins({
87
+ babelPlugins: [babelPluginMetadataInlineContents],
88
+ urlInfo: {
89
+ url,
90
+ type: isJsModule ? "js_module" : "js_classic",
91
+ content: js,
92
+ },
93
+ })
94
+ return metadata.inlineContentInfos
95
+ }
96
+
97
+ const babelPluginMetadataInlineContents = () => {
98
+ return {
99
+ name: "metadata-inline-contents",
100
+ visitor: {
101
+ Program: (programPath, state) => {
102
+ const inlineContentInfos = []
103
+ const onInlineContentInfo = (inlineContentInfo) => {
104
+ inlineContentInfos.push(inlineContentInfo)
105
+ }
106
+ programPath.traverse({
107
+ NewExpression: (path) => {
108
+ if (isNewInlineContentCall(path)) {
109
+ analyzeNewInlineContentCall(path.node, {
110
+ onInlineContentInfo,
111
+ })
112
+ return
113
+ }
114
+ if (isNewBlobCall(path.node)) {
115
+ analyzeNewBlobCall(path.node, {
116
+ onInlineContentInfo,
117
+ })
118
+ return
119
+ }
120
+ },
121
+ CallExpression: (path) => {
122
+ const node = path.node
123
+ if (isJSONParseCall(node)) {
124
+ analyzeJsonParseCall(node, {
125
+ onInlineContentInfo,
126
+ })
127
+ }
128
+ },
129
+ })
130
+ state.file.metadata.inlineContentInfos = inlineContentInfos
131
+ },
132
+ },
133
+ }
134
+ }
135
+
136
+ const isNewInlineContentCall = (path) => {
137
+ const node = path.node
138
+ if (node.callee.type === "Identifier") {
139
+ // terser rename import to use a shorter name
140
+ const name = getOriginalName(path, node.callee.name)
141
+ return name === "InlineContent"
142
+ }
143
+ if (node.callee.id && node.callee.id.type === "Identifier") {
144
+ const name = getOriginalName(path, node.callee.id.name)
145
+ return name === "InlineContent"
146
+ }
147
+ return false
148
+ }
149
+ const analyzeNewInlineContentCall = (node, { onInlineContentInfo }) => {
150
+ analyzeArguments({
151
+ node,
152
+ onInlineContentInfo,
153
+ nodeHoldingContent: node.arguments[0],
154
+ type: "new_inline_content_first_arg",
155
+ })
156
+ }
157
+
158
+ const isNewBlobCall = (node) => {
159
+ return node.callee.type === "Identifier" && node.callee.name === "Blob"
160
+ }
161
+ const analyzeNewBlobCall = (node, { onInlineContentInfo }) => {
162
+ const firstArg = node.arguments[0]
163
+ if (firstArg.type !== "ArrayExpression") {
164
+ return
165
+ }
166
+ if (firstArg.elements.length !== 1) {
167
+ return
168
+ }
169
+ analyzeArguments({
170
+ node,
171
+ onInlineContentInfo,
172
+ nodeHoldingContent: firstArg.elements[0],
173
+ type: "new_blob_first_arg",
174
+ })
175
+ }
176
+
177
+ const analyzeArguments = ({
178
+ node,
179
+ onInlineContentInfo,
180
+ nodeHoldingContent,
181
+ type,
182
+ }) => {
183
+ if (node.arguments.length !== 2) {
184
+ return
185
+ }
186
+ const [, secondArg] = node.arguments
187
+ const typePropertyNode = getTypePropertyNode(secondArg)
188
+ if (!typePropertyNode) {
189
+ return
190
+ }
191
+ const typePropertyValueNode = typePropertyNode.value
192
+ if (typePropertyValueNode.type !== "StringLiteral") {
193
+ return
194
+ }
195
+ const contentType = typePropertyValueNode.value
196
+ const contentDetails = extractContentDetails(nodeHoldingContent)
197
+ if (contentDetails) {
198
+ onInlineContentInfo({
199
+ node: nodeHoldingContent,
200
+ ...getNodePosition(nodeHoldingContent),
201
+ type,
202
+ contentType,
203
+ ...contentDetails,
204
+ })
205
+ }
206
+ }
207
+ const extractContentDetails = (node) => {
208
+ if (node.type === "StringLiteral") {
209
+ return {
210
+ nodeType: "StringLiteral",
211
+ quote: node.extra.raw[0],
212
+ content: node.value,
213
+ }
214
+ }
215
+ if (node.type === "TemplateLiteral") {
216
+ const quasis = node.quasis
217
+ if (quasis.length !== 1) {
218
+ return null
219
+ }
220
+ const templateElementNode = quasis[0]
221
+ return {
222
+ nodeType: "TemplateLiteral",
223
+ quote: "`",
224
+ content: templateElementNode.value.cooked,
225
+ }
226
+ }
227
+ return null
228
+ }
229
+
230
+ const isJSONParseCall = (node) => {
231
+ const callee = node.callee
232
+ return (
233
+ callee.type === "MemberExpression" &&
234
+ callee.object.type === "Identifier" &&
235
+ callee.object.name === "JSON" &&
236
+ callee.property.type === "Identifier" &&
237
+ callee.property.name === "parse"
238
+ )
239
+ }
240
+ const analyzeJsonParseCall = (node, { onInlineContentInfo }) => {
241
+ const firstArgNode = node.arguments[0]
242
+ const contentDetails = extractContentDetails(firstArgNode)
243
+ if (contentDetails) {
244
+ onInlineContentInfo({
245
+ node: firstArgNode,
246
+ ...getNodePosition(firstArgNode),
247
+ type: "json_parse_first_arg",
248
+ contentType: "application/json",
249
+ ...contentDetails,
250
+ })
251
+ }
252
+ }
253
+
254
+ const getNodePosition = (node) => {
255
+ return {
256
+ start: node.start,
257
+ end: node.end,
258
+ line: node.loc.start.line,
259
+ column: node.loc.start.column,
260
+ lineEnd: node.loc.end.line,
261
+ columnEnd: node.loc.end.column,
262
+ }
263
+ }
264
+ const getOriginalName = (path, name) => {
265
+ const binding = path.scope.getBinding(name)
266
+ if (!binding) {
267
+ return name
268
+ }
269
+ if (binding.path.type === "ImportSpecifier") {
270
+ const importedName = binding.path.node.imported.name
271
+ if (name === importedName) {
272
+ return name
273
+ }
274
+ return getOriginalName(path, importedName)
275
+ }
276
+ if (binding.path.type === "VariableDeclarator") {
277
+ const { init } = binding.path.node
278
+ if (init && init.type === "Identifier") {
279
+ const previousName = init.name
280
+ return getOriginalName(path, previousName)
281
+ }
282
+ }
283
+ return name
284
+ }
285
+ const getTypePropertyNode = (node) => {
286
+ if (node.type !== "ObjectExpression") {
287
+ return null
288
+ }
289
+ const { properties } = node
290
+ return properties.find((property) => {
291
+ return (
292
+ property.type === "ObjectProperty" &&
293
+ property.key.type === "Identifier" &&
294
+ property.key.name === "type"
295
+ )
296
+ })
297
+ }
@@ -0,0 +1,13 @@
1
+ export const jsenvPluginLeadingSlash = () => {
2
+ return {
3
+ name: "jsenv:leading_slash",
4
+ appliesDuring: "*",
5
+ resolveUrl: (reference, context) => {
6
+ if (reference.specifier[0] !== "/") {
7
+ return null
8
+ }
9
+ return new URL(reference.specifier.slice(1), context.rootDirectoryUrl)
10
+ .href
11
+ },
12
+ }
13
+ }
@@ -0,0 +1,9 @@
1
+ import { minifyWithParcel } from "@jsenv/utils/css_ast/parcel_css.js"
2
+
3
+ export const minifyCss = ({ cssUrlInfo, context }) => {
4
+ const { code, map } = minifyWithParcel(cssUrlInfo, context)
5
+ return {
6
+ content: String(code),
7
+ sourcemap: map,
8
+ }
9
+ }
@@ -0,0 +1,15 @@
1
+ import { createRequire } from "node:module"
2
+
3
+ const require = createRequire(import.meta.url)
4
+
5
+ // https://github.com/kangax/html-minifier#options-quick-reference
6
+ export const minifyHtml = ({ htmlUrlInfo, options } = {}) => {
7
+ const { collapseWhitespace = true, removeComments = true } = options
8
+
9
+ const { minify } = require("html-minifier")
10
+ const htmlMinified = minify(htmlUrlInfo.content, {
11
+ collapseWhitespace,
12
+ removeComments,
13
+ })
14
+ return htmlMinified
15
+ }
@@ -1,28 +1,11 @@
1
1
  // https://github.com/terser-js/terser#minify-options
2
2
 
3
- export const jsenvPluginMinifyJs = () => {
4
- const applyTerser = async (urlInfo) => {
5
- return minifyJs({
6
- url: urlInfo.url,
7
- content: urlInfo.content,
8
- sourcemap: urlInfo.sourcemap,
9
- isJsModule: urlInfo.type === "js_module",
10
- })
11
- }
12
-
13
- return {
14
- name: "jsenv:minify_js",
15
- appliesDuring: {
16
- build: true,
17
- },
18
- optimize: {
19
- js_classic: applyTerser,
20
- js_module: applyTerser,
21
- },
22
- }
23
- }
3
+ export const minifyJs = async ({ jsUrlInfo, options }) => {
4
+ const url = jsUrlInfo.url
5
+ const content = jsUrlInfo.content
6
+ const sourcemap = jsUrlInfo.sourcemap
7
+ const isJsModule = jsUrlInfo.type === "js_module"
24
8
 
25
- const minifyJs = async ({ url, content, sourcemap, isJsModule }) => {
26
9
  const { minify } = await import("terser")
27
10
  const terserResult = await minify(
28
11
  {
@@ -38,6 +21,7 @@ const minifyJs = async ({ url, content, sourcemap, isJsModule }) => {
38
21
  // We need to preserve "new InlineContent()" calls to be able to recognize them
39
22
  // after minification in order to version urls inside inline content text
40
23
  keep_fnames: /InlineContent/,
24
+ ...options,
41
25
  },
42
26
  )
43
27
  return {