@jsenv/core 36.3.1 → 37.0.1

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 (64) hide show
  1. package/dist/js/autoreload.js +6 -5
  2. package/dist/js/import_meta_hot.js +4 -4
  3. package/dist/js/server_events_client.js +422 -304
  4. package/dist/jsenv_core.js +3819 -3256
  5. package/package.json +17 -17
  6. package/src/build/build.js +342 -658
  7. package/src/build/build_urls_generator.js +8 -8
  8. package/src/build/build_versions_manager.js +495 -0
  9. package/src/build/version_mappings_injection.js +27 -16
  10. package/src/dev/file_service.js +80 -91
  11. package/src/dev/start_dev_server.js +5 -3
  12. package/src/kitchen/errors.js +16 -16
  13. package/src/kitchen/fetched_content_compliance.js +4 -8
  14. package/src/kitchen/kitchen.js +367 -939
  15. package/src/kitchen/prepend_content.js +13 -35
  16. package/src/kitchen/url_graph/references.js +713 -0
  17. package/src/kitchen/url_graph/sort_by_dependencies.js +2 -2
  18. package/src/kitchen/url_graph/url_content.js +96 -0
  19. package/src/kitchen/url_graph/url_graph.js +439 -0
  20. package/src/kitchen/url_graph/url_graph_report.js +6 -4
  21. package/src/kitchen/url_graph/url_graph_visitor.js +14 -12
  22. package/src/kitchen/url_graph/url_info_transformations.js +180 -184
  23. package/src/plugins/autoreload/client/autoreload.js +1 -0
  24. package/src/plugins/autoreload/client/reload.js +6 -6
  25. package/src/plugins/autoreload/jsenv_plugin_autoreload.js +2 -2
  26. package/src/plugins/autoreload/jsenv_plugin_autoreload_client.js +2 -2
  27. package/src/plugins/autoreload/jsenv_plugin_autoreload_server.js +84 -78
  28. package/src/plugins/autoreload/jsenv_plugin_hot_search_param.js +52 -0
  29. package/src/plugins/cache_control/jsenv_plugin_cache_control.js +1 -1
  30. package/src/plugins/commonjs_globals/jsenv_plugin_commonjs_globals.js +2 -2
  31. package/src/plugins/global_scenarios/jsenv_plugin_global_scenarios.js +3 -3
  32. package/src/plugins/import_meta_hot/client/import_meta_hot.js +4 -4
  33. package/src/plugins/import_meta_hot/jsenv_plugin_import_meta_hot.js +18 -20
  34. package/src/plugins/import_meta_scenarios/jsenv_plugin_import_meta_scenarios.js +2 -2
  35. package/src/plugins/importmap/jsenv_plugin_importmap.js +35 -37
  36. package/src/plugins/inlining/jsenv_plugin_inlining.js +1 -17
  37. package/src/plugins/inlining/jsenv_plugin_inlining_as_data_url.js +70 -50
  38. package/src/plugins/inlining/jsenv_plugin_inlining_into_html.js +72 -54
  39. package/src/plugins/plugin_controller.js +92 -27
  40. package/src/plugins/protocol_file/jsenv_plugin_protocol_file.js +18 -20
  41. package/src/plugins/reference_analysis/css/jsenv_plugin_css_reference_analysis.js +4 -5
  42. package/src/plugins/reference_analysis/data_urls/jsenv_plugin_data_urls_analysis.js +18 -16
  43. package/src/plugins/reference_analysis/directory/jsenv_plugin_directory_reference_analysis.js +13 -20
  44. package/src/plugins/reference_analysis/html/jsenv_plugin_html_reference_analysis.js +55 -72
  45. package/src/plugins/reference_analysis/js/jsenv_plugin_js_reference_analysis.js +33 -42
  46. package/src/plugins/reference_analysis/jsenv_plugin_reference_analysis.js +16 -7
  47. package/src/plugins/reference_analysis/webmanifest/jsenv_plugin_webmanifest_reference_analysis.js +4 -3
  48. package/src/plugins/resolution_node_esm/jsenv_plugin_node_esm_resolution.js +16 -6
  49. package/src/plugins/resolution_node_esm/node_esm_resolver.js +30 -24
  50. package/src/plugins/resolution_web/jsenv_plugin_web_resolution.js +8 -5
  51. package/src/plugins/ribbon/jsenv_plugin_ribbon.js +3 -3
  52. package/src/plugins/server_events/client/server_events_client.js +460 -15
  53. package/src/plugins/server_events/jsenv_plugin_server_events_client_injection.js +13 -29
  54. package/src/plugins/version_search_param/jsenv_plugin_version_search_param.js +1 -1
  55. package/src/build/version_generator.js +0 -19
  56. package/src/kitchen/url_graph/url_graph_loader.js +0 -77
  57. package/src/kitchen/url_graph.js +0 -322
  58. package/src/plugins/autoreload/jsenv_plugin_hmr.js +0 -42
  59. package/src/plugins/resolution_node_esm/url_type_from_reference.js +0 -13
  60. package/src/plugins/server_events/client/connection_manager.js +0 -170
  61. package/src/plugins/server_events/client/event_source_connection.js +0 -83
  62. package/src/plugins/server_events/client/events_manager.js +0 -75
  63. package/src/plugins/server_events/client/web_socket_connection.js +0 -81
  64. /package/src/kitchen/{url_specifier_encoding.js → url_graph/url_specifier_encoding.js} +0 -0
@@ -1,58 +1,78 @@
1
- import { DATA_URL } from "@jsenv/urls";
1
+ import { DATA_URL, injectQueryParamsIntoSpecifier } from "@jsenv/urls";
2
2
 
3
3
  export const jsenvPluginInliningAsDataUrl = () => {
4
4
  return {
5
5
  name: "jsenv:inlining_as_data_url",
6
6
  appliesDuring: "*",
7
- formatReference: {
8
- // if the referenced url is a worker we could use
9
- // https://www.oreilly.com/library/view/web-workers/9781449322120/ch04.html
10
- // but maybe we should rather use ?object_url
11
- // or people could do this:
12
- // import workerText from './worker.js?text'
13
- // const blob = new Blob(workerText, { type: 'text/javascript' })
14
- // window.URL.createObjectURL(blob)
15
- // in any case the recommended way is to use an url
16
- // to benefit from shared worker and reuse worker between tabs
17
- "*": (reference, context) => {
18
- if (
19
- !reference.original ||
20
- !reference.original.searchParams.has("inline")
21
- ) {
22
- return null;
23
- }
24
- // <link rel="stylesheet"> and <script> can be inlined in the html
25
- if (
26
- reference.type === "link_href" &&
27
- reference.subtype === "stylesheet"
28
- ) {
29
- return null;
30
- }
31
- if (reference.type === "script") {
32
- return null;
33
- }
34
- return (async () => {
35
- const urlInfo = context.urlGraph.getUrlInfo(reference.url);
36
- await context.cook(urlInfo, { reference });
37
- const contentAsBase64 = Buffer.from(urlInfo.content).toString(
38
- "base64",
39
- );
40
- let specifier = DATA_URL.stringify({
41
- mediaType: urlInfo.contentType,
42
- base64Flag: true,
43
- data: contentAsBase64,
44
- });
45
- context.referenceUtils.becomesInline(reference, {
46
- line: reference.line,
47
- column: reference.column,
48
- isOriginal: reference.isOriginal,
49
- specifier,
50
- content: contentAsBase64,
51
- contentType: urlInfo.contentType,
52
- });
53
- return specifier;
54
- })();
55
- },
7
+ // if the referenced url is a worker we could use
8
+ // https://www.oreilly.com/library/view/web-workers/9781449322120/ch04.html
9
+ // but maybe we should rather use ?object_url
10
+ // or people could do this:
11
+ // import workerText from './worker.js?text'
12
+ // const blob = new Blob(workerText, { type: 'text/javascript' })
13
+ // window.URL.createObjectURL(blob)
14
+ // in any case the recommended way is to use an url
15
+ // to benefit from shared worker and reuse worker between tabs
16
+ formatReference: (reference) => {
17
+ if (!reference.searchParams.has("inline")) {
18
+ return null;
19
+ }
20
+ if (reference.type === "sourcemap_comment") {
21
+ return null;
22
+ }
23
+ // <link rel="stylesheet"> and <script> can be inlined in the html
24
+ if (
25
+ reference.type === "link_href" &&
26
+ reference.subtype === "stylesheet"
27
+ ) {
28
+ return null;
29
+ }
30
+ if (
31
+ reference.original &&
32
+ reference.original.type === "link_href" &&
33
+ reference.original.subtype === "stylesheet"
34
+ ) {
35
+ return null;
36
+ }
37
+ if (reference.type === "script") {
38
+ return null;
39
+ }
40
+ const specifierWithBase64Param = injectQueryParamsIntoSpecifier(
41
+ reference.specifier,
42
+ { as_base_64: "", inline: undefined },
43
+ );
44
+ const referenceInlined = reference.inline({
45
+ line: reference.line,
46
+ column: reference.column,
47
+ isOriginal: reference.isOriginal,
48
+ specifier: specifierWithBase64Param,
49
+ });
50
+ const urlInfoInlined = referenceInlined.urlInfo;
51
+ return (async () => {
52
+ await urlInfoInlined.cook();
53
+ const base64Url = DATA_URL.stringify({
54
+ mediaType: urlInfoInlined.contentType,
55
+ base64Flag: true,
56
+ data: urlInfoInlined.content,
57
+ });
58
+ return base64Url;
59
+ })();
60
+ },
61
+ fetchUrlContent: async (urlInfo) => {
62
+ const withoutBase64ParamUrlInfo =
63
+ urlInfo.getWithoutSearchParam("as_base_64");
64
+ if (!withoutBase64ParamUrlInfo) {
65
+ return null;
66
+ }
67
+ await withoutBase64ParamUrlInfo.cook();
68
+ const contentAsBase64 = Buffer.from(
69
+ withoutBase64ParamUrlInfo.content,
70
+ ).toString("base64");
71
+ return {
72
+ originalContent: withoutBase64ParamUrlInfo.originalContent,
73
+ content: contentAsBase64,
74
+ contentType: withoutBase64ParamUrlInfo.contentType,
75
+ };
56
76
  },
57
77
  };
58
78
  };
@@ -9,46 +9,57 @@ import {
9
9
  setHtmlNodeText,
10
10
  getHtmlNodePosition,
11
11
  } from "@jsenv/ast";
12
+ import { generateInlineContentUrl, urlToExtension } from "@jsenv/urls";
12
13
 
13
14
  export const jsenvPluginInliningIntoHtml = () => {
14
15
  return {
15
16
  name: "jsenv:inlining_into_html",
16
17
  appliesDuring: "*",
17
18
  transformUrlContent: {
18
- html: async (urlInfo, context) => {
19
+ html: async (urlInfo) => {
19
20
  const htmlAst = parseHtmlString(urlInfo.content);
20
21
  const mutations = [];
21
22
  const actions = [];
22
23
 
23
24
  const onStyleSheet = (linkNode, { href }) => {
24
- const linkReference = context.referenceUtils.find(
25
- (ref) =>
26
- ref.generatedSpecifier === href &&
27
- ref.type === "link_href" &&
28
- ref.subtype === "stylesheet",
29
- );
30
- if (
31
- !linkReference.original ||
32
- !linkReference.original.searchParams.has("inline")
33
- ) {
25
+ let linkReference = null;
26
+ for (const referenceToOther of urlInfo.referenceToOthersSet) {
27
+ if (
28
+ referenceToOther.generatedSpecifier === href &&
29
+ referenceToOther.type === "link_href" &&
30
+ referenceToOther.subtype === "stylesheet"
31
+ ) {
32
+ linkReference = referenceToOther;
33
+ break;
34
+ }
35
+ }
36
+ if (!linkReference.searchParams.has("inline")) {
34
37
  return;
35
38
  }
36
- const linkUrlInfo = context.urlGraph.getUrlInfo(linkReference.url);
37
- actions.push(async () => {
38
- await context.cook(linkUrlInfo, {
39
- reference: linkReference,
40
- });
41
- const { line, column, isOriginal } = getHtmlNodePosition(linkNode, {
39
+ const { line, column, lineEnd, columnEnd, isOriginal } =
40
+ getHtmlNodePosition(linkNode, {
42
41
  preferOriginal: true,
43
42
  });
44
- context.referenceUtils.becomesInline(linkReference, {
45
- line: line - 1,
46
- column,
47
- isOriginal,
48
- specifier: linkReference.generatedSpecifier,
49
- content: linkUrlInfo.content,
50
- contentType: linkUrlInfo.contentType,
51
- });
43
+ const linkInlineUrl = generateInlineContentUrl({
44
+ url: linkReference.url,
45
+ extension: urlToExtension(linkReference.url),
46
+ line,
47
+ column,
48
+ lineEnd,
49
+ columnEnd,
50
+ });
51
+ const linkReferenceInlined = linkReference.inline({
52
+ line: line - 1,
53
+ column,
54
+ isOriginal,
55
+ specifier: linkInlineUrl,
56
+ type: "style",
57
+ expectedType: linkReference.expectedType,
58
+ });
59
+ const linkUrlInfoInlined = linkReferenceInlined.urlInfo;
60
+
61
+ actions.push(async () => {
62
+ await linkUrlInfoInlined.cook();
52
63
  mutations.push(() => {
53
64
  setHtmlNodeAttributes(linkNode, {
54
65
  "inlined-from-href": href,
@@ -62,43 +73,50 @@ export const jsenvPluginInliningIntoHtml = () => {
62
73
  });
63
74
  linkNode.nodeName = "style";
64
75
  linkNode.tagName = "style";
65
- setHtmlNodeText(linkNode, linkUrlInfo.content, {
76
+ setHtmlNodeText(linkNode, linkUrlInfoInlined.content, {
66
77
  indentation: "auto",
67
78
  });
68
79
  });
69
80
  });
70
81
  };
71
82
  const onScriptWithSrc = (scriptNode, { src }) => {
72
- const scriptReference = context.referenceUtils.find(
73
- (ref) => ref.generatedSpecifier === src && ref.type === "script",
74
- );
75
- if (
76
- !scriptReference.original ||
77
- !scriptReference.original.searchParams.has("inline")
78
- ) {
83
+ let scriptReference;
84
+ for (const dependencyReference of urlInfo.referenceToOthersSet) {
85
+ if (
86
+ dependencyReference.generatedSpecifier === src &&
87
+ dependencyReference.type === "script"
88
+ ) {
89
+ scriptReference = dependencyReference;
90
+ break;
91
+ }
92
+ }
93
+ if (!scriptReference.searchParams.has("inline")) {
79
94
  return;
80
95
  }
81
- const scriptUrlInfo = context.urlGraph.getUrlInfo(
82
- scriptReference.url,
83
- );
84
- actions.push(async () => {
85
- await context.cook(scriptUrlInfo, {
86
- reference: scriptReference,
87
- });
88
- const { line, column, isOriginal } = getHtmlNodePosition(
89
- scriptNode,
90
- {
91
- preferOriginal: true,
92
- },
93
- );
94
- context.referenceUtils.becomesInline(scriptReference, {
95
- line: line - 1,
96
- column,
97
- isOriginal,
98
- specifier: scriptReference.generatedSpecifier,
99
- content: scriptUrlInfo.content,
100
- contentType: scriptUrlInfo.contentType,
96
+ const { line, column, lineEnd, columnEnd, isOriginal } =
97
+ getHtmlNodePosition(scriptNode, {
98
+ preferOriginal: true,
101
99
  });
100
+ const scriptInlineUrl = generateInlineContentUrl({
101
+ url: scriptReference.url,
102
+ extension: urlToExtension(scriptReference.url),
103
+ line,
104
+ column,
105
+ lineEnd,
106
+ columnEnd,
107
+ });
108
+ const scriptReferenceInlined = scriptReference.inline({
109
+ line: line - 1,
110
+ column,
111
+ isOriginal,
112
+ specifier: scriptInlineUrl,
113
+ type: scriptReference.type,
114
+ subtype: scriptReference.subtype,
115
+ expectedType: scriptReference.expectedType,
116
+ });
117
+ const scriptUrlInfoInlined = scriptReferenceInlined.urlInfo;
118
+ actions.push(async () => {
119
+ await scriptUrlInfoInlined.cook();
102
120
  mutations.push(() => {
103
121
  setHtmlNodeAttributes(scriptNode, {
104
122
  "inlined-from-src": src,
@@ -107,7 +125,7 @@ export const jsenvPluginInliningIntoHtml = () => {
107
125
  "integrity": undefined,
108
126
  "jsenv-inlined-by": "jsenv:inlining_into_html",
109
127
  });
110
- setHtmlNodeText(scriptNode, scriptUrlInfo.content, {
128
+ setHtmlNodeText(scriptNode, scriptUrlInfoInlined.content, {
111
129
  indentation: "auto",
112
130
  });
113
131
  });
@@ -1,4 +1,10 @@
1
1
  import { performance } from "node:perf_hooks";
2
+ import {
3
+ parseHtmlString,
4
+ stringifyHtmlAst,
5
+ injectHtmlNodeAsEarlyAsPossible,
6
+ createHtmlNode,
7
+ } from "@jsenv/ast";
2
8
 
3
9
  const HOOK_NAMES = [
4
10
  "init",
@@ -134,7 +140,7 @@ export const createPluginController = (kitchenContext) => {
134
140
  let lastPluginUsed = null;
135
141
  let currentPlugin = null;
136
142
  let currentHookName = null;
137
- const callHook = (hook, info, context) => {
143
+ const callHook = (hook, info) => {
138
144
  const hookFn = getHookFunction(hook, info);
139
145
  if (!hookFn) {
140
146
  return null;
@@ -146,21 +152,17 @@ export const createPluginController = (kitchenContext) => {
146
152
  lastPluginUsed = hook.plugin;
147
153
  currentPlugin = hook.plugin;
148
154
  currentHookName = hook.name;
149
- let valueReturned = hookFn(info, context);
155
+ let valueReturned = hookFn(info);
150
156
  currentPlugin = null;
151
157
  currentHookName = null;
152
158
  if (info.timing) {
153
159
  info.timing[`${hook.name}-${hook.plugin.name.replace("jsenv:", "")}`] =
154
160
  performance.now() - startTimestamp;
155
161
  }
156
- valueReturned = assertAndNormalizeReturnValue(
157
- hook.name,
158
- valueReturned,
159
- info,
160
- );
162
+ valueReturned = assertAndNormalizeReturnValue(hook, valueReturned, info);
161
163
  return valueReturned;
162
164
  };
163
- const callAsyncHook = async (hook, info, context) => {
165
+ const callAsyncHook = async (hook, info) => {
164
166
  const hookFn = getHookFunction(hook, info);
165
167
  if (!hookFn) {
166
168
  return null;
@@ -173,38 +175,37 @@ export const createPluginController = (kitchenContext) => {
173
175
  lastPluginUsed = hook.plugin;
174
176
  currentPlugin = hook.plugin;
175
177
  currentHookName = hook.name;
176
- let valueReturned = await hookFn(info, context);
178
+ let valueReturned = await hookFn(info);
177
179
  currentPlugin = null;
178
180
  currentHookName = null;
179
181
  if (info.timing) {
180
182
  info.timing[`${hook.name}-${hook.plugin.name.replace("jsenv:", "")}`] =
181
183
  performance.now() - startTimestamp;
182
184
  }
183
- valueReturned = assertAndNormalizeReturnValue(
184
- hook.name,
185
- valueReturned,
186
- info,
187
- );
185
+ valueReturned = assertAndNormalizeReturnValue(hook, valueReturned, info);
188
186
  return valueReturned;
189
187
  };
190
188
 
191
- const callHooks = (hookName, info, context, callback) => {
189
+ const callHooks = (hookName, info, callback) => {
192
190
  const hooks = hookGroups[hookName];
193
191
  if (hooks) {
192
+ const setHookParams = (firstArg = info) => {
193
+ info = firstArg;
194
+ };
194
195
  for (const hook of hooks) {
195
- const returnValue = callHook(hook, info, context);
196
+ const returnValue = callHook(hook, info);
196
197
  if (returnValue && callback) {
197
- callback(returnValue, hook.plugin);
198
+ callback(returnValue, hook.plugin, setHookParams);
198
199
  }
199
200
  }
200
201
  }
201
202
  };
202
- const callAsyncHooks = async (hookName, info, context, callback) => {
203
+ const callAsyncHooks = async (hookName, info, callback) => {
203
204
  const hooks = hookGroups[hookName];
204
205
  if (hooks) {
205
206
  await hooks.reduce(async (previous, hook) => {
206
207
  await previous;
207
- const returnValue = await callAsyncHook(hook, info, context);
208
+ const returnValue = await callAsyncHook(hook, info);
208
209
  if (returnValue && callback) {
209
210
  await callback(returnValue, hook.plugin);
210
211
  }
@@ -212,11 +213,11 @@ export const createPluginController = (kitchenContext) => {
212
213
  }
213
214
  };
214
215
 
215
- const callHooksUntil = (hookName, info, context) => {
216
+ const callHooksUntil = (hookName, info) => {
216
217
  const hooks = hookGroups[hookName];
217
218
  if (hooks) {
218
219
  for (const hook of hooks) {
219
- const returnValue = callHook(hook, info, context);
220
+ const returnValue = callHook(hook, info);
220
221
  if (returnValue) {
221
222
  return returnValue;
222
223
  }
@@ -224,7 +225,7 @@ export const createPluginController = (kitchenContext) => {
224
225
  }
225
226
  return null;
226
227
  };
227
- const callAsyncHooksUntil = (hookName, info, context) => {
228
+ const callAsyncHooksUntil = (hookName, info) => {
228
229
  const hooks = hookGroups[hookName];
229
230
  if (!hooks) {
230
231
  return null;
@@ -238,7 +239,7 @@ export const createPluginController = (kitchenContext) => {
238
239
  return resolve();
239
240
  }
240
241
  const hook = hooks[index];
241
- const returnValue = callAsyncHook(hook, info, context);
242
+ const returnValue = callAsyncHook(hook, info);
242
243
  return Promise.resolve(returnValue).then((output) => {
243
244
  if (output) {
244
245
  return resolve(output);
@@ -285,16 +286,20 @@ const getHookFunction = (
285
286
  return hookValue;
286
287
  };
287
288
 
288
- const assertAndNormalizeReturnValue = (hookName, returnValue, info) => {
289
+ const assertAndNormalizeReturnValue = (hook, returnValue, info) => {
289
290
  // all hooks are allowed to return null/undefined as a signal of "I don't do anything"
290
291
  if (returnValue === null || returnValue === undefined) {
291
292
  return returnValue;
292
293
  }
293
294
  for (const returnValueAssertion of returnValueAssertions) {
294
- if (!returnValueAssertion.appliesTo.includes(hookName)) {
295
+ if (!returnValueAssertion.appliesTo.includes(hook.name)) {
295
296
  continue;
296
297
  }
297
- const assertionResult = returnValueAssertion.assertion(returnValue, info);
298
+ const assertionResult = returnValueAssertion.assertion(
299
+ returnValue,
300
+ info,
301
+ hook,
302
+ );
298
303
  if (assertionResult !== undefined) {
299
304
  // normalization
300
305
  returnValue = assertionResult;
@@ -328,7 +333,7 @@ const returnValueAssertions = [
328
333
  "finalizeUrlContent",
329
334
  "optimizeUrlContent",
330
335
  ],
331
- assertion: (valueReturned, urlInfo) => {
336
+ assertion: (valueReturned, urlInfo, hook) => {
332
337
  if (typeof valueReturned === "string" || Buffer.isBuffer(valueReturned)) {
333
338
  return { content: valueReturned };
334
339
  }
@@ -337,6 +342,12 @@ const returnValueAssertions = [
337
342
  if (urlInfo.url.startsWith("ignore:")) {
338
343
  return undefined;
339
344
  }
345
+ if (urlInfo.type === "html") {
346
+ const { scriptInjections } = valueReturned;
347
+ if (scriptInjections) {
348
+ return applyScriptInjections(urlInfo, scriptInjections, hook);
349
+ }
350
+ }
340
351
  if (typeof content !== "string" && !Buffer.isBuffer(content) && !body) {
341
352
  throw new Error(
342
353
  `Unexpected "content" returned by plugin: it must be a string or a buffer; got ${content}`,
@@ -350,3 +361,57 @@ const returnValueAssertions = [
350
361
  },
351
362
  },
352
363
  ];
364
+
365
+ const applyScriptInjections = (htmlUrlInfo, scriptInjections, hook) => {
366
+ const htmlAst = parseHtmlString(htmlUrlInfo.content);
367
+
368
+ scriptInjections.reverse().forEach((scriptInjection) => {
369
+ const { setup } = scriptInjection;
370
+ if (setup) {
371
+ const setupGlobalName = setup.name;
372
+ const setupParamSource = stringifyParams(setup.param, " ");
373
+ const inlineJs = `${setupGlobalName}({${setupParamSource}})`;
374
+ injectHtmlNodeAsEarlyAsPossible(
375
+ htmlAst,
376
+ createHtmlNode({
377
+ tagName: "script",
378
+ textContent: inlineJs,
379
+ }),
380
+ hook.plugin.name,
381
+ );
382
+ }
383
+ const scriptReference = htmlUrlInfo.dependencies.inject({
384
+ type: "script",
385
+ subtype: scriptInjection.type === "module" ? "js_module" : "js_classic",
386
+ expectedType:
387
+ scriptInjection.type === "module" ? "js_module" : "js_classic",
388
+ specifier: scriptInjection.src,
389
+ });
390
+ injectHtmlNodeAsEarlyAsPossible(
391
+ htmlAst,
392
+ createHtmlNode({
393
+ tagName: "script",
394
+ ...(scriptInjection.type === "module" ? { type: "module" } : {}),
395
+ src: scriptReference.generatedSpecifier,
396
+ }),
397
+ hook.plugin.name,
398
+ );
399
+ });
400
+ const htmlModified = stringifyHtmlAst(htmlAst);
401
+ return {
402
+ content: htmlModified,
403
+ };
404
+ };
405
+
406
+ const stringifyParams = (params, prefix = "") => {
407
+ const source = JSON.stringify(params, null, prefix);
408
+ if (prefix.length) {
409
+ // remove leading "{\n"
410
+ // remove leading prefix
411
+ // remove trailing "\n}"
412
+ return source.slice(2 + prefix.length, -2);
413
+ }
414
+ // remove leading "{"
415
+ // remove trailing "}"
416
+ return source.slice(1, -1);
417
+ };
@@ -84,7 +84,7 @@ export const jsenvPluginProtocolFile = ({
84
84
  magicDirectoryIndex,
85
85
  magicExtensions: getExtensionsToTry(
86
86
  magicExtensions,
87
- reference.parentUrl,
87
+ reference.ownerUrlInfo.url,
88
88
  ),
89
89
  });
90
90
  if (filesystemResolution.stat) {
@@ -117,13 +117,12 @@ export const jsenvPluginProtocolFile = ({
117
117
  name: "jsenv:fs_resolution",
118
118
  appliesDuring: "*",
119
119
  resolveReference: {
120
- filesystem: (reference, context) => {
121
- const { parentUrl } = reference;
122
- const parentUrlInfo = context.urlGraph.getUrlInfo(parentUrl);
120
+ filesystem: (reference) => {
121
+ const ownerUrlInfo = reference.ownerUrlInfo;
123
122
  const baseUrl =
124
- parentUrlInfo && parentUrlInfo.type === "directory"
125
- ? ensurePathnameTrailingSlash(parentUrl)
126
- : parentUrl;
123
+ ownerUrlInfo && ownerUrlInfo.type === "directory"
124
+ ? ensurePathnameTrailingSlash(ownerUrlInfo.url)
125
+ : ownerUrlInfo.url;
127
126
  return new URL(reference.specifier, baseUrl).href;
128
127
  },
129
128
  },
@@ -141,14 +140,15 @@ export const jsenvPluginProtocolFile = ({
141
140
  }
142
141
  return null;
143
142
  },
144
- formatReference: (reference, context) => {
143
+ formatReference: (reference) => {
145
144
  if (!reference.generatedUrl.startsWith("file:")) {
146
145
  return null;
147
146
  }
148
- if (urlIsInsideOf(reference.generatedUrl, context.rootDirectoryUrl)) {
147
+ const { rootDirectoryUrl } = reference.ownerUrlInfo.context;
148
+ if (urlIsInsideOf(reference.generatedUrl, rootDirectoryUrl)) {
149
149
  return `/${urlToRelativeUrl(
150
150
  reference.generatedUrl,
151
- context.rootDirectoryUrl,
151
+ rootDirectoryUrl,
152
152
  )}`;
153
153
  }
154
154
  return `/@fs/${reference.generatedUrl.slice("file:///".length)}`;
@@ -157,19 +157,16 @@ export const jsenvPluginProtocolFile = ({
157
157
  {
158
158
  name: "jsenv:file_url_fetching",
159
159
  appliesDuring: "*",
160
- fetchUrlContent: (urlInfo, context) => {
160
+ fetchUrlContent: (urlInfo) => {
161
161
  if (!urlInfo.url.startsWith("file:")) {
162
162
  return null;
163
163
  }
164
164
  const urlObject = new URL(urlInfo.url);
165
- if (context.reference.leadsToADirectory) {
165
+ if (urlInfo.firstReference.leadsToADirectory) {
166
166
  const directoryEntries = readdirSync(urlObject);
167
167
  let filename;
168
- if (context.reference.type === "filesystem") {
169
- const parentUrlInfo = context.urlGraph.getUrlInfo(
170
- context.reference.parentUrl,
171
- );
172
- filename = `${parentUrlInfo.filename}${context.reference.specifier}/`;
168
+ if (urlInfo.firstReference.type === "filesystem") {
169
+ filename = `${urlInfo.firstReference.ownerUrlInfo.filename}${urlInfo.firstReference.specifier}/`;
173
170
  } else {
174
171
  filename = `${urlToFilename(urlInfo.url)}/`;
175
172
  }
@@ -182,10 +179,11 @@ export const jsenvPluginProtocolFile = ({
182
179
  }
183
180
  const fileBuffer = readFileSync(urlObject);
184
181
  const contentType = CONTENT_TYPE.fromUrlExtension(urlInfo.url);
182
+ const content = CONTENT_TYPE.isTextual(contentType)
183
+ ? String(fileBuffer)
184
+ : fileBuffer;
185
185
  return {
186
- content: CONTENT_TYPE.isTextual(contentType)
187
- ? String(fileBuffer)
188
- : fileBuffer,
186
+ content,
189
187
  contentType,
190
188
  };
191
189
  },
@@ -15,7 +15,7 @@ export const jsenvPluginCssReferenceAnalysis = () => {
15
15
  };
16
16
  };
17
17
 
18
- const parseAndTransformCssUrls = async (urlInfo, context) => {
18
+ const parseAndTransformCssUrls = async (urlInfo) => {
19
19
  const cssUrls = await parseCssUrls({
20
20
  css: urlInfo.content,
21
21
  url: urlInfo.originalUrl,
@@ -23,7 +23,7 @@ const parseAndTransformCssUrls = async (urlInfo, context) => {
23
23
  const actions = [];
24
24
  const magicSource = createMagicSource(urlInfo.content);
25
25
  for (const cssUrl of cssUrls) {
26
- const [reference] = context.referenceUtils.found({
26
+ const reference = urlInfo.dependencies.found({
27
27
  type: cssUrl.type,
28
28
  specifier: cssUrl.specifier,
29
29
  specifierStart: cssUrl.start,
@@ -32,9 +32,8 @@ const parseAndTransformCssUrls = async (urlInfo, context) => {
32
32
  specifierColumn: cssUrl.column,
33
33
  });
34
34
  actions.push(async () => {
35
- const replacement = await context.referenceUtils.readGeneratedSpecifier(
36
- reference,
37
- );
35
+ await reference.readGeneratedSpecifier();
36
+ const replacement = reference.generatedSpecifier;
38
37
  magicSource.replace({
39
38
  start: cssUrl.start,
40
39
  end: cssUrl.end,