@jsenv/core 28.1.3 → 28.2.2

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 (47) hide show
  1. package/dist/js/script_type_module_supervisor.js +8 -13
  2. package/dist/js/supervisor.js +690 -504
  3. package/dist/main.js +13332 -13179
  4. package/package.json +5 -5
  5. package/readme.md +3 -3
  6. package/src/build/build.js +980 -713
  7. package/src/build/inject_global_version_mappings.js +5 -20
  8. package/src/build/start_build_server.js +2 -2
  9. package/src/dev/start_dev_server.js +6 -3
  10. package/src/omega/compat/runtime_compat.js +9 -6
  11. package/src/omega/errors.js +3 -0
  12. package/src/omega/fetched_content_compliance.js +2 -2
  13. package/src/omega/kitchen.js +191 -146
  14. package/src/omega/server/file_service.js +104 -71
  15. package/src/omega/url_graph/url_graph_loader.js +77 -0
  16. package/src/omega/url_graph/url_info_transformations.js +12 -15
  17. package/src/omega/url_graph.js +118 -101
  18. package/src/plugins/autoreload/jsenv_plugin_autoreload_client.js +1 -0
  19. package/src/plugins/autoreload/jsenv_plugin_autoreload_server.js +34 -36
  20. package/src/plugins/autoreload/jsenv_plugin_hmr.js +3 -2
  21. package/src/plugins/bundling/js_module/{bundle_js_module.js → bundle_js_modules.js} +51 -14
  22. package/src/plugins/bundling/jsenv_plugin_bundling.js +2 -2
  23. package/src/plugins/import_meta_hot/jsenv_plugin_import_meta_hot.js +11 -0
  24. package/src/plugins/inline/jsenv_plugin_html_inline_content.js +73 -62
  25. package/src/plugins/node_esm_resolution/jsenv_plugin_node_esm_resolution.js +77 -89
  26. package/src/plugins/plugin_controller.js +26 -22
  27. package/src/plugins/server_events/jsenv_plugin_server_events_client_injection.js +1 -0
  28. package/src/plugins/supervisor/client/script_type_module_supervisor.js +7 -9
  29. package/src/plugins/supervisor/client/supervisor.js +99 -52
  30. package/src/plugins/supervisor/jsenv_plugin_supervisor.js +2 -4
  31. package/src/plugins/transpilation/as_js_classic/async-to-promises.js +16 -0
  32. package/src/plugins/transpilation/as_js_classic/convert_js_module_to_js_classic.js +85 -0
  33. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic.js +48 -190
  34. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic_conversion.js +104 -0
  35. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic_html.js +161 -240
  36. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic_library.js +92 -0
  37. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic_workers.js +19 -12
  38. package/src/plugins/transpilation/babel/jsenv_plugin_babel.js +1 -24
  39. package/src/plugins/transpilation/import_assertions/jsenv_plugin_import_assertions.js +82 -52
  40. package/src/plugins/transpilation/jsenv_plugin_transpilation.js +12 -13
  41. package/src/plugins/url_analysis/html/html_urls.js +91 -34
  42. package/src/plugins/url_analysis/js/js_urls.js +5 -4
  43. package/src/plugins/url_resolution/jsenv_plugin_url_resolution.js +1 -0
  44. package/src/test/execute_plan.js +3 -8
  45. package/src/build/inject_service_worker_urls.js +0 -78
  46. package/src/build/resync_resource_hints.js +0 -112
  47. package/src/omega/url_graph/url_graph_load.js +0 -74
@@ -1,10 +1,20 @@
1
+ /*
2
+ * when {type: "module"} cannot be used on web workers:
3
+ * - new Worker("worker.js", { type: "module" })
4
+ * transformed into
5
+ * new Worker("worker.js?as_js_classic", { type: " lassic" })
6
+ * - navigator.serviceWorker.register("service_worker.js", { type: "module" })
7
+ * transformed into
8
+ * navigator.serviceWorker.register("service_worker.js?as_js_classic", { type: "classic" })
9
+ * - new SharedWorker("shared_worker.js", { type: "module" })
10
+ * transformed into
11
+ * new SharedWorker("shared_worker.js?as_js_classic", { type: "classic" })
12
+ */
13
+
1
14
  import { injectQueryParams } from "@jsenv/urls"
2
15
 
3
- export const jsenvPluginAsJsClassicWorkers = ({
4
- generateJsClassicFilename,
5
- }) => {
6
- const updateReference = (reference) => {
7
- reference.filename = generateJsClassicFilename(reference.url)
16
+ export const jsenvPluginAsJsClassicWorkers = () => {
17
+ const turnIntoJsClassicProxy = (reference) => {
8
18
  reference.mutation = (magicSource) => {
9
19
  magicSource.replace({
10
20
  start: reference.typePropertyNode.value.start,
@@ -12,10 +22,7 @@ export const jsenvPluginAsJsClassicWorkers = ({
12
22
  replacement: JSON.stringify("classic"),
13
23
  })
14
24
  }
15
- reference.expectedType = "js_classic"
16
- return injectQueryParams(reference.url, {
17
- as_js_classic: "",
18
- })
25
+ return injectQueryParams(reference.url, { as_js_classic: "" })
19
26
  }
20
27
 
21
28
  return {
@@ -30,7 +37,7 @@ export const jsenvPluginAsJsClassicWorkers = ({
30
37
  if (context.isSupportedOnCurrentClients("worker_type_module")) {
31
38
  return null
32
39
  }
33
- return updateReference(reference)
40
+ return turnIntoJsClassicProxy(reference)
34
41
  }
35
42
  if (reference.expectedSubtype === "service_worker") {
36
43
  if (
@@ -38,7 +45,7 @@ export const jsenvPluginAsJsClassicWorkers = ({
38
45
  ) {
39
46
  return null
40
47
  }
41
- return updateReference(reference)
48
+ return turnIntoJsClassicProxy(reference)
42
49
  }
43
50
  if (reference.expectedSubtype === "shared_worker") {
44
51
  if (
@@ -46,7 +53,7 @@ export const jsenvPluginAsJsClassicWorkers = ({
46
53
  ) {
47
54
  return null
48
55
  }
49
- return updateReference(reference)
56
+ return turnIntoJsClassicProxy(reference)
50
57
  }
51
58
  return null
52
59
  },
@@ -15,31 +15,9 @@ export const jsenvPluginBabel = ({
15
15
  } = {}) => {
16
16
  const transformWithBabel = async (urlInfo, context) => {
17
17
  const isJsModule = urlInfo.type === "js_module"
18
- const isWorker = urlInfo.subtype === "worker"
19
- const isServiceWorker = urlInfo.subtype === "service_worker"
20
- const isSharedWorker = urlInfo.subtype === "shared_worker"
21
- const isWorkerContext = isWorker || isServiceWorker || isSharedWorker
22
-
23
- let { clientRuntimeCompat } = context
24
- if (isWorker) {
25
- clientRuntimeCompat = RUNTIME_COMPAT.add(clientRuntimeCompat, "worker")
26
- } else if (isServiceWorker) {
27
- // when code is executed by a service worker we can assume
28
- // the execution context supports more than the default one
29
- // for instance arrow function are supported
30
- clientRuntimeCompat = RUNTIME_COMPAT.add(
31
- clientRuntimeCompat,
32
- "service_worker",
33
- )
34
- } else if (isSharedWorker) {
35
- clientRuntimeCompat = RUNTIME_COMPAT.add(
36
- clientRuntimeCompat,
37
- "shared_worker",
38
- )
39
- }
40
18
 
41
19
  const isSupported = (feature) =>
42
- RUNTIME_COMPAT.isSupported(clientRuntimeCompat, feature)
20
+ RUNTIME_COMPAT.isSupported(context.clientRuntimeCompat, feature)
43
21
  const getImportSpecifier = (clientFileUrl) => {
44
22
  const [reference] = context.referenceUtils.inject({
45
23
  type: "js_import_export",
@@ -52,7 +30,6 @@ export const jsenvPluginBabel = ({
52
30
  const babelPluginStructure = getBaseBabelPluginStructure({
53
31
  url: urlInfo.url,
54
32
  isSupported,
55
- isWorkerContext,
56
33
  isJsModule,
57
34
  getImportSpecifier,
58
35
  })
@@ -19,9 +19,22 @@ export const jsenvPluginImportAssertions = ({
19
19
  css = "auto",
20
20
  text = "auto",
21
21
  }) => {
22
- const updateReference = (reference, searchParam) => {
22
+ const transpilations = { json, css, text }
23
+ const shouldTranspileImportAssertion = (context, type) => {
24
+ const transpilation = transpilations[type]
25
+ if (transpilation === true) {
26
+ return true
27
+ }
28
+ if (transpilation === "auto") {
29
+ return !context.isSupportedOnCurrentClients(`import_type_${type}`)
30
+ }
31
+ return false
32
+ }
33
+ const markAsJsModuleProxy = (reference) => {
23
34
  reference.expectedType = "js_module"
24
35
  reference.filename = `${urlToFilename(reference.url)}.js`
36
+ }
37
+ const turnIntoJsModuleProxy = (reference, type) => {
25
38
  reference.mutation = (magicSource) => {
26
39
  magicSource.remove({
27
40
  start: reference.assertNode.start,
@@ -29,8 +42,9 @@ export const jsenvPluginImportAssertions = ({
29
42
  })
30
43
  }
31
44
  const newUrl = injectQueryParams(reference.url, {
32
- [searchParam]: "",
45
+ [`as_${type}_module`]: "",
33
46
  })
47
+ markAsJsModuleProxy(reference)
34
48
  return newUrl
35
49
  }
36
50
 
@@ -44,9 +58,9 @@ export const jsenvPluginImportAssertions = ({
44
58
  // - means rollup can bundle more js file together
45
59
  // - means url versioning can work for css inlined in js
46
60
  if (context.scenarios.build) {
47
- json = true
48
- css = true
49
- text = true
61
+ transpilations.json = true
62
+ transpilations.css = true
63
+ transpilations.text = true
50
64
  }
51
65
  },
52
66
  redirectUrl: {
@@ -54,41 +68,18 @@ export const jsenvPluginImportAssertions = ({
54
68
  if (!reference.assert) {
55
69
  return null
56
70
  }
57
- if (reference.assert.type === "json") {
58
- const shouldTranspileJsonImportAssertion =
59
- json === true
60
- ? true
61
- : json === "auto"
62
- ? !context.isSupportedOnCurrentClients("import_type_json")
63
- : false
64
- if (shouldTranspileJsonImportAssertion) {
65
- return updateReference(reference, "as_json_module")
66
- }
71
+ const { searchParams } = reference
72
+ if (
73
+ searchParams.has("as_json_module") ||
74
+ searchParams.has("as_css_module") ||
75
+ searchParams.has("as_text_module")
76
+ ) {
77
+ markAsJsModuleProxy(reference)
67
78
  return null
68
79
  }
69
- if (reference.assert.type === "css") {
70
- const shouldTranspileCssImportAssertion =
71
- css === true
72
- ? true
73
- : css === "auto"
74
- ? !context.isSupportedOnCurrentClients("import_type_css")
75
- : false
76
- if (shouldTranspileCssImportAssertion) {
77
- return updateReference(reference, "as_css_module")
78
- }
79
- return null
80
- }
81
- if (reference.assert.type === "text") {
82
- const shouldTranspileTextImportAssertion =
83
- text === true
84
- ? true
85
- : text === "auto"
86
- ? !context.isSupportedOnCurrentClients("import_type_text")
87
- : false
88
- if (shouldTranspileTextImportAssertion) {
89
- return updateReference(reference, "as_text_module")
90
- }
91
- return null
80
+ const type = reference.assert.type
81
+ if (shouldTranspileImportAssertion(context, type)) {
82
+ return turnIntoJsModuleProxy(reference, type)
92
83
  }
93
84
  return null
94
85
  },
@@ -107,16 +98,29 @@ const jsenvPluginAsModules = () => {
107
98
  name: `jsenv:as_json_module`,
108
99
  appliesDuring: "*",
109
100
  fetchUrlContent: async (urlInfo, context) => {
110
- const originalUrlInfo = await context.fetchOriginalUrlInfo({
101
+ const [jsonReference, jsonUrlInfo] = context.getWithoutSearchParam({
111
102
  urlInfo,
112
103
  context,
113
104
  searchParam: "as_json_module",
114
105
  expectedType: "json",
115
106
  })
116
- if (!originalUrlInfo) {
107
+ if (!jsonReference) {
117
108
  return null
118
109
  }
119
- const jsonText = JSON.stringify(originalUrlInfo.content.trim())
110
+ await context.fetchUrlContent(jsonUrlInfo, {
111
+ reference: jsonReference,
112
+ })
113
+ if (context.scenarios.dev) {
114
+ context.referenceUtils.found({
115
+ type: "js_import_export",
116
+ subtype: jsonReference.subtype,
117
+ specifier: jsonReference.url,
118
+ expectedType: "js_module",
119
+ })
120
+ } else if (context.scenarios.build && jsonUrlInfo.dependents.size === 0) {
121
+ context.urlGraph.deleteUrlInfo(jsonUrlInfo.url)
122
+ }
123
+ const jsonText = JSON.stringify(jsonUrlInfo.content.trim())
120
124
  return {
121
125
  // here we could `export default ${jsonText}`:
122
126
  // but js engine are optimized to recognize JSON.parse
@@ -124,8 +128,8 @@ const jsenvPluginAsModules = () => {
124
128
  content: `export default JSON.parse(${jsonText})`,
125
129
  contentType: "text/javascript",
126
130
  type: "js_module",
127
- originalUrl: originalUrlInfo.originalUrl,
128
- originalContent: originalUrlInfo.originalContent,
131
+ originalUrl: jsonUrlInfo.originalUrl,
132
+ originalContent: jsonUrlInfo.originalContent,
129
133
  }
130
134
  },
131
135
  }
@@ -134,16 +138,29 @@ const jsenvPluginAsModules = () => {
134
138
  name: `jsenv:as_css_module`,
135
139
  appliesDuring: "*",
136
140
  fetchUrlContent: async (urlInfo, context) => {
137
- const originalUrlInfo = await context.fetchOriginalUrlInfo({
141
+ const [cssReference, cssUrlInfo] = context.getWithoutSearchParam({
138
142
  urlInfo,
139
143
  context,
140
144
  searchParam: "as_css_module",
141
145
  expectedType: "css",
142
146
  })
143
- if (!originalUrlInfo) {
147
+ if (!cssReference) {
144
148
  return null
145
149
  }
146
- const cssText = JS_QUOTES.escapeSpecialChars(originalUrlInfo.content, {
150
+ await context.fetchUrlContent(cssUrlInfo, {
151
+ reference: cssReference,
152
+ })
153
+ if (context.scenarios.dev) {
154
+ context.referenceUtils.found({
155
+ type: "js_import_export",
156
+ subtype: cssReference.subtype,
157
+ specifier: cssReference.url,
158
+ expectedType: "js_module",
159
+ })
160
+ } else if (context.scenarios.build && cssUrlInfo.dependents.size === 0) {
161
+ context.urlGraph.deleteUrlInfo(cssUrlInfo.url)
162
+ }
163
+ const cssText = JS_QUOTES.escapeSpecialChars(cssUrlInfo.content, {
147
164
  // If template string is choosen and runtime do not support template literals
148
165
  // it's ok because "jsenv:new_inline_content" plugin executes after this one
149
166
  // and convert template strings into raw strings
@@ -160,8 +177,8 @@ const jsenvPluginAsModules = () => {
160
177
  export default stylesheet`,
161
178
  contentType: "text/javascript",
162
179
  type: "js_module",
163
- originalUrl: originalUrlInfo.originalUrl,
164
- originalContent: originalUrlInfo.originalContent,
180
+ originalUrl: cssUrlInfo.originalUrl,
181
+ originalContent: cssUrlInfo.originalContent,
165
182
  }
166
183
  },
167
184
  }
@@ -170,15 +187,28 @@ const jsenvPluginAsModules = () => {
170
187
  name: `jsenv:as_text_module`,
171
188
  appliesDuring: "*",
172
189
  fetchUrlContent: async (urlInfo, context) => {
173
- const originalUrlInfo = await context.fetchOriginalUrlInfo({
190
+ const [textReference, textUrlInfo] = context.getWithoutSearchParam({
174
191
  urlInfo,
175
192
  context,
176
193
  searchParam: "as_text_module",
177
194
  expectedType: "text",
178
195
  })
179
- if (!originalUrlInfo) {
196
+ if (!textReference) {
180
197
  return null
181
198
  }
199
+ await context.fetchUrlContent(textUrlInfo, {
200
+ reference: textReference,
201
+ })
202
+ if (context.scenarios.dev) {
203
+ context.referenceUtils.found({
204
+ type: "js_import_export",
205
+ subtype: textReference.subtype,
206
+ specifier: textReference.url,
207
+ expectedType: "js_module",
208
+ })
209
+ } else if (context.scenarios.build && textUrlInfo.dependents.size === 0) {
210
+ context.urlGraph.deleteUrlInfo(textUrlInfo.url)
211
+ }
182
212
  const textPlain = JS_QUOTES.escapeSpecialChars(urlInfo.content, {
183
213
  // If template string is choosen and runtime do not support template literals
184
214
  // it's ok because "jsenv:new_inline_content" plugin executes after this one
@@ -194,8 +224,8 @@ const inlineContent = new InlineContent(${textPlain}, { type: "text/plain" })
194
224
  export default inlineContent.text`,
195
225
  contentType: "text/javascript",
196
226
  type: "js_module",
197
- originalUrl: originalUrlInfo.originalUrl,
198
- originalContent: originalUrlInfo.originalContent,
227
+ originalUrl: textUrlInfo.originalUrl,
228
+ originalContent: textUrlInfo.originalContent,
199
229
  }
200
230
  },
201
231
  }
@@ -16,7 +16,12 @@ import { jsenvPluginTopLevelAwait } from "./jsenv_plugin_top_level_await.js"
16
16
  export const jsenvPluginTranspilation = ({
17
17
  importAssertions = true,
18
18
  css = true,
19
- jsModuleAsJsClassic = true,
19
+ jsClassicLibrary = true,
20
+ // build sets jsClassicFallback: false during first step of the build
21
+ // and re-enable it in the second phase (when performing the bundling)
22
+ // so that bundling is applied on js modules THEN it is converted to js classic if needed
23
+ jsClassicFallback = true,
24
+ systemJsInjection = true,
20
25
  topLevelAwait = true,
21
26
  babelHelpersAsImport = true,
22
27
  getCustomBabelPlugins,
@@ -24,11 +29,7 @@ export const jsenvPluginTranspilation = ({
24
29
  if (importAssertions === true) {
25
30
  importAssertions = {}
26
31
  }
27
- if (jsModuleAsJsClassic === true) {
28
- jsModuleAsJsClassic = {}
29
- }
30
32
  return [
31
- // import assertions we want it all the time
32
33
  ...(importAssertions
33
34
  ? [jsenvPluginImportAssertions(importAssertions)]
34
35
  : []),
@@ -38,14 +39,12 @@ export const jsenvPluginTranspilation = ({
38
39
  getCustomBabelPlugins,
39
40
  babelHelpersAsImport,
40
41
  }),
41
- // but the conversion from js_module to js_classic
42
- // we want to do it after bundling
43
- // so the build function will disable jsModuleAsJsClassic during build
44
- // and enable it manually during postbuild
45
- ...(jsModuleAsJsClassic
46
- ? [jsenvPluginAsJsClassic(jsModuleAsJsClassic)]
47
- : []),
48
- // topLevelAwait must come after js_module_as_js_classic because it's related to the module format
42
+ jsenvPluginAsJsClassic({
43
+ jsClassicLibrary,
44
+ jsClassicFallback,
45
+ systemJsInjection,
46
+ }),
47
+ // topLevelAwait must come after jsenvPluginAsJsClassic because it's related to the module format
49
48
  // so we want to wait to know the module format before transforming things related to top level await
50
49
  ...(topLevelAwait ? [jsenvPluginTopLevelAwait(topLevelAwait)] : []),
51
50
  ...(css ? [jsenvPluginCssParcel()] : []),
@@ -13,15 +13,17 @@ import {
13
13
  export const parseAndTransformHtmlUrls = async (urlInfo, context) => {
14
14
  const url = urlInfo.originalUrl
15
15
  const content = urlInfo.content
16
- const { scenarios, referenceUtils } = context
17
16
  const htmlAst = parseHtmlString(content, {
18
- storeOriginalPositions: scenarios.dev,
17
+ storeOriginalPositions: context.scenarios.dev,
19
18
  })
20
- const actions = []
21
- visitHtmlUrls({
19
+ const mentions = visitHtmlUrls({
22
20
  url,
23
21
  htmlAst,
24
- onUrl: ({
22
+ })
23
+ const actions = []
24
+ const mutations = []
25
+ mentions.forEach(
26
+ ({
25
27
  type,
26
28
  subtype,
27
29
  expectedType,
@@ -31,10 +33,10 @@ export const parseAndTransformHtmlUrls = async (urlInfo, context) => {
31
33
  originalColumn,
32
34
  node,
33
35
  attributeName,
36
+ debug,
34
37
  specifier,
35
38
  }) => {
36
39
  const { crossorigin, integrity } = readFetchMetas(node)
37
-
38
40
  const isResourceHint = [
39
41
  "preconnect",
40
42
  "dns-prefetch",
@@ -42,8 +44,9 @@ export const parseAndTransformHtmlUrls = async (urlInfo, context) => {
42
44
  "preload",
43
45
  "modulepreload",
44
46
  ].includes(subtype)
45
- const [reference] = referenceUtils.found({
47
+ const [reference] = context.referenceUtils.found({
46
48
  type,
49
+ subtype,
47
50
  expectedType,
48
51
  originalLine,
49
52
  originalColumn,
@@ -53,23 +56,26 @@ export const parseAndTransformHtmlUrls = async (urlInfo, context) => {
53
56
  isResourceHint,
54
57
  crossorigin,
55
58
  integrity,
59
+ debug,
56
60
  })
57
61
  actions.push(async () => {
62
+ await context.referenceUtils.readGeneratedSpecifier(reference)
63
+ })
64
+ mutations.push(() => {
58
65
  setHtmlNodeAttributes(node, {
59
- [attributeName]: await referenceUtils.readGeneratedSpecifier(
60
- reference,
61
- ),
66
+ [attributeName]: reference.generatedSpecifier,
62
67
  })
63
68
  })
64
69
  },
65
- })
66
- if (actions.length === 0) {
67
- return null
70
+ )
71
+ if (actions.length > 0) {
72
+ await Promise.all(actions.map((action) => action()))
68
73
  }
69
- await Promise.all(actions.map((action) => action()))
70
- return {
71
- content: stringifyHtmlAst(htmlAst),
74
+ if (mutations.length === 0) {
75
+ return null
72
76
  }
77
+ mutations.forEach((mutation) => mutation())
78
+ return stringifyHtmlAst(htmlAst)
73
79
  }
74
80
 
75
81
  const crossOriginCompatibleTagNames = ["script", "link", "img", "source"]
@@ -87,8 +93,10 @@ const readFetchMetas = (node) => {
87
93
  return meta
88
94
  }
89
95
 
90
- const visitHtmlUrls = ({ url, htmlAst, onUrl }) => {
91
- const addDependency = ({
96
+ const visitHtmlUrls = ({ url, htmlAst }) => {
97
+ const mentions = []
98
+ const finalizeCallbacks = []
99
+ const addMention = ({
92
100
  type,
93
101
  subtype,
94
102
  expectedType,
@@ -111,7 +119,8 @@ const visitHtmlUrls = ({ url, htmlAst, onUrl }) => {
111
119
  column,
112
120
  // originalLine, originalColumn
113
121
  } = position
114
- onUrl({
122
+ const debug = getHtmlNodeAttribute(node, "jsenv-debug") !== undefined
123
+ const mention = {
115
124
  type,
116
125
  subtype,
117
126
  expectedType,
@@ -121,7 +130,10 @@ const visitHtmlUrls = ({ url, htmlAst, onUrl }) => {
121
130
  specifier,
122
131
  node,
123
132
  attributeName,
124
- })
133
+ debug,
134
+ }
135
+ mentions.push(mention)
136
+ return mention
125
137
  }
126
138
  const visitAttributeAsUrlSpecifier = ({ node, attributeName, ...rest }) => {
127
139
  const value = getHtmlNodeAttribute(node, attributeName)
@@ -130,9 +142,9 @@ const visitHtmlUrls = ({ url, htmlAst, onUrl }) => {
130
142
  if (jsenvPluginOwner === "jsenv:importmap") {
131
143
  // during build the importmap is inlined
132
144
  // and shoud not be considered as a dependency anymore
133
- return
145
+ return null
134
146
  }
135
- addDependency({
147
+ return addMention({
136
148
  ...rest,
137
149
  node,
138
150
  attributeName,
@@ -142,26 +154,29 @@ const visitHtmlUrls = ({ url, htmlAst, onUrl }) => {
142
154
  ? new URL(value, url).href
143
155
  : value,
144
156
  })
145
- } else if (attributeName === "src") {
146
- visitAttributeAsUrlSpecifier({
157
+ }
158
+ if (attributeName === "src") {
159
+ return visitAttributeAsUrlSpecifier({
147
160
  ...rest,
148
161
  node,
149
162
  attributeName: "inlined-from-src",
150
163
  })
151
- } else if (attributeName === "href") {
152
- visitAttributeAsUrlSpecifier({
164
+ }
165
+ if (attributeName === "href") {
166
+ return visitAttributeAsUrlSpecifier({
153
167
  ...rest,
154
168
  node,
155
169
  attributeName: "inlined-from-href",
156
170
  })
157
171
  }
172
+ return null
158
173
  }
159
174
  const visitSrcset = ({ type, node }) => {
160
175
  const srcset = getHtmlNodeAttribute(node, "srcset")
161
176
  if (srcset) {
162
177
  const srcCandidates = parseSrcSet(srcset)
163
178
  srcCandidates.forEach((srcCandidate) => {
164
- addDependency({
179
+ addMention({
165
180
  type,
166
181
  node,
167
182
  attributeName: "srcset",
@@ -174,18 +189,20 @@ const visitHtmlUrls = ({ url, htmlAst, onUrl }) => {
174
189
  link: (node) => {
175
190
  const rel = getHtmlNodeAttribute(node, "rel")
176
191
  const type = getHtmlNodeAttribute(node, "type")
177
- visitAttributeAsUrlSpecifier({
192
+ const mention = visitAttributeAsUrlSpecifier({
178
193
  type: "link_href",
179
194
  subtype: rel,
180
195
  node,
181
196
  attributeName: "href",
197
+ // https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload#including_a_mime_type
182
198
  expectedContentType: type,
183
- expectedType: {
184
- manifest: "webmanifest",
185
- modulepreload: "js_module",
186
- stylesheet: "css",
187
- }[rel],
188
199
  })
200
+
201
+ if (mention) {
202
+ finalizeCallbacks.push(() => {
203
+ mention.expectedType = decideLinkExpectedType(mention, mentions)
204
+ })
205
+ }
189
206
  },
190
207
  // style: () => {},
191
208
  script: (node) => {
@@ -198,6 +215,7 @@ const visitHtmlUrls = ({ url, htmlAst, onUrl }) => {
198
215
  }
199
216
  visitAttributeAsUrlSpecifier({
200
217
  type: "script_src",
218
+ subtype: type,
201
219
  expectedType: type,
202
220
  node,
203
221
  attributeName: "src",
@@ -228,7 +246,7 @@ const visitHtmlUrls = ({ url, htmlAst, onUrl }) => {
228
246
  node,
229
247
  })
230
248
  },
231
- souce: (node) => {
249
+ source: (node) => {
232
250
  visitAttributeAsUrlSpecifier({
233
251
  type: "source_src",
234
252
  node,
@@ -255,4 +273,43 @@ const visitHtmlUrls = ({ url, htmlAst, onUrl }) => {
255
273
  })
256
274
  },
257
275
  })
276
+ finalizeCallbacks.forEach((finalizeCallback) => {
277
+ finalizeCallback()
278
+ })
279
+ return mentions
280
+ }
281
+
282
+ const decideLinkExpectedType = (linkMention, mentions) => {
283
+ const rel = getHtmlNodeAttribute(linkMention.node, "rel")
284
+ if (rel === "webmanifest") {
285
+ return "webmanifest"
286
+ }
287
+ if (rel === "modulepreload") {
288
+ return "js_module"
289
+ }
290
+ if (rel === "stylesheet") {
291
+ return "css"
292
+ }
293
+ if (rel === "preload") {
294
+ // https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload#what_types_of_content_can_be_preloaded
295
+ const as = getHtmlNodeAttribute(linkMention.node, "as")
296
+ if (as === "document") {
297
+ return "html"
298
+ }
299
+ if (as === "style") {
300
+ return "css"
301
+ }
302
+ if (as === "script") {
303
+ const firstScriptOnThisUrl = mentions.find(
304
+ (mentionCandidate) =>
305
+ mentionCandidate.url === linkMention.url &&
306
+ mentionCandidate.type === "script_src",
307
+ )
308
+ if (firstScriptOnThisUrl) {
309
+ return firstScriptOnThisUrl.expectedType
310
+ }
311
+ return undefined
312
+ }
313
+ }
314
+ return undefined
258
315
  }
@@ -10,7 +10,6 @@ export const parseAndTransformJsUrls = async (urlInfo, context) => {
10
10
  isJsModule: urlInfo.type === "js_module",
11
11
  isWebWorker: isWebWorkerUrlInfo(urlInfo),
12
12
  })
13
- const { rootDirectoryUrl, referenceUtils } = context
14
13
  const actions = []
15
14
  const magicSource = createMagicSource(urlInfo.content)
16
15
  jsMentions.forEach((jsMention) => {
@@ -20,7 +19,7 @@ export const parseAndTransformJsUrls = async (urlInfo, context) => {
20
19
  ) {
21
20
  urlInfo.data.usesImport = true
22
21
  }
23
- const [reference] = referenceUtils.found({
22
+ const [reference] = context.referenceUtils.found({
24
23
  type: jsMention.type,
25
24
  subtype: jsMention.subtype,
26
25
  expectedType: jsMention.expectedType,
@@ -33,7 +32,7 @@ export const parseAndTransformJsUrls = async (urlInfo, context) => {
33
32
  data: jsMention.data,
34
33
  baseUrl: {
35
34
  "StringLiteral": jsMention.baseUrl,
36
- "window.origin": rootDirectoryUrl,
35
+ "window.origin": context.rootDirectoryUrl,
37
36
  "import.meta.url": urlInfo.url,
38
37
  "context.meta.url": urlInfo.url,
39
38
  "document.currentScript.src": urlInfo.url,
@@ -43,7 +42,9 @@ export const parseAndTransformJsUrls = async (urlInfo, context) => {
43
42
  typePropertyNode: jsMention.typePropertyNode,
44
43
  })
45
44
  actions.push(async () => {
46
- const replacement = await referenceUtils.readGeneratedSpecifier(reference)
45
+ const replacement = await context.referenceUtils.readGeneratedSpecifier(
46
+ reference,
47
+ )
47
48
  magicSource.replace({
48
49
  start: jsMention.specifierStart,
49
50
  end: jsMention.specifierEnd,
@@ -28,6 +28,7 @@ export const jsenvPluginUrlResolution = () => {
28
28
  "js_url_specifier": urlResolver,
29
29
  "js_inline_content": urlResolver,
30
30
  "webmanifest_icon_src": urlResolver,
31
+ "package_json": urlResolver,
31
32
  },
32
33
  }
33
34
  }