@jsenv/core 36.3.1 → 37.0.0

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 +16 -16
  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,19 +1,18 @@
1
1
  import {
2
2
  urlIsInsideOf,
3
3
  moveUrl,
4
- getCallerPosition,
5
- stringifyUrlSite,
6
4
  normalizeUrl,
7
5
  setUrlFilename,
8
6
  } from "@jsenv/urls";
9
7
  import { URL_META } from "@jsenv/url-meta";
10
- import { writeFileSync, ensureWindowsDriveLetter } from "@jsenv/filesystem";
8
+ import { ensureWindowsDriveLetter } from "@jsenv/filesystem";
11
9
  import { createLogger, createDetailedMessage, ANSI } from "@jsenv/log";
12
10
  import { CONTENT_TYPE } from "@jsenv/utils/src/content_type/content_type.js";
13
11
  import { RUNTIME_COMPAT } from "@jsenv/runtime-compat";
14
12
 
13
+ import { createUrlGraph } from "./url_graph/url_graph.js";
14
+ import { urlSpecifierEncoding } from "./url_graph/url_specifier_encoding.js";
15
15
  import { createPluginController } from "../plugins/plugin_controller.js";
16
- import { urlSpecifierEncoding } from "./url_specifier_encoding.js";
17
16
  import { createUrlInfoTransformer } from "./url_graph/url_info_transformations.js";
18
17
  import {
19
18
  createResolveUrlError,
@@ -21,10 +20,7 @@ import {
21
20
  createTransformUrlContentError,
22
21
  createFinalizeUrlContentError,
23
22
  } from "./errors.js";
24
- import { GRAPH_VISITOR } from "./url_graph/url_graph_visitor.js";
25
23
  import { assertFetchedContentCompliance } from "./fetched_content_compliance.js";
26
- import { isWebWorkerEntryPointReference } from "./web_workers.js";
27
- import { prependContent } from "./prepend_content.js";
28
24
 
29
25
  const inlineContentClientFileUrl = new URL(
30
26
  "./client/inline_content.js",
@@ -32,6 +28,7 @@ const inlineContentClientFileUrl = new URL(
32
28
  ).href;
33
29
 
34
30
  export const createKitchen = ({
31
+ name,
35
32
  signal,
36
33
  logLevel,
37
34
 
@@ -40,47 +37,73 @@ export const createKitchen = ({
40
37
  ignore,
41
38
  ignoreProtocol = "remove",
42
39
  supportedProtocols = ["file:", "data:", "virtual:", "http:", "https:"],
43
- urlGraph,
44
40
  dev = false,
45
41
  build = false,
42
+ shape = false,
46
43
  runtimeCompat,
47
44
  // during dev/test clientRuntimeCompat is a single runtime
48
45
  // during build clientRuntimeCompat is runtimeCompat
49
46
  clientRuntimeCompat = runtimeCompat,
50
- systemJsTranspilation,
51
47
  plugins,
52
- minification,
48
+ supervisor,
53
49
  sourcemaps = dev ? "inline" : "none", // "programmatic" and "file" also allowed
54
50
  sourcemapsSourcesProtocol,
55
51
  sourcemapsSourcesContent,
56
52
  sourcemapsSourcesRelative,
57
53
  outDirectoryUrl,
54
+ baseContext = {},
58
55
  }) => {
59
- const sideEffectForwardCallbacks = [];
60
-
61
56
  const logger = createLogger({ logLevel });
62
- const kitchenContext = {
63
- signal,
64
- logger,
65
- rootDirectoryUrl,
66
- mainFilePath,
67
- urlGraph,
68
- dev,
69
- build,
70
- runtimeCompat,
71
- clientRuntimeCompat,
72
- systemJsTranspilation,
73
- isSupportedOnCurrentClients: memoizeIsSupported(clientRuntimeCompat),
74
- isSupportedOnFutureClients: memoizeIsSupported(runtimeCompat),
75
- minification,
76
- sourcemaps,
77
- outDirectoryUrl,
57
+ const kitchen = {
58
+ context: {
59
+ ...baseContext,
60
+ kitchen: null,
61
+ signal,
62
+ logger,
63
+ rootDirectoryUrl,
64
+ mainFilePath,
65
+ dev,
66
+ build,
67
+ shape,
68
+ runtimeCompat,
69
+ clientRuntimeCompat,
70
+ inlineContentClientFileUrl,
71
+ isSupportedOnCurrentClients: memoizeIsSupported(clientRuntimeCompat),
72
+ isSupportedOnFutureClients: memoizeIsSupported(runtimeCompat),
73
+ sourcemaps,
74
+ outDirectoryUrl,
75
+ },
76
+ graph: null,
77
+ pluginController: null,
78
+ urlInfoTransformer: null,
78
79
  };
80
+ const kitchenContext = kitchen.context;
81
+ kitchenContext.kitchen = kitchen;
82
+
83
+ const graph = createUrlGraph({
84
+ name,
85
+ rootDirectoryUrl,
86
+ kitchen,
87
+ });
88
+ kitchen.graph = graph;
89
+
79
90
  const pluginController = createPluginController(kitchenContext);
91
+ kitchen.pluginController = pluginController;
80
92
  plugins.forEach((pluginEntry) => {
81
93
  pluginController.pushPlugin(pluginEntry);
82
94
  });
83
95
 
96
+ const urlInfoTransformer = createUrlInfoTransformer({
97
+ logger,
98
+ sourcemaps,
99
+ sourcemapsSourcesProtocol,
100
+ sourcemapsSourcesContent,
101
+ sourcemapsSourcesRelative,
102
+ outDirectoryUrl,
103
+ supervisor,
104
+ });
105
+ kitchen.urlInfoTransformer = urlInfoTransformer;
106
+
84
107
  const isIgnoredByProtocol = (url) => {
85
108
  const { protocol } = new URL(url);
86
109
  const protocolIsSupported = supportedProtocols.some(
@@ -109,238 +132,143 @@ export const createKitchen = ({
109
132
  const isIgnored = (url) => {
110
133
  return isIgnoredByProtocol(url) || isIgnoredByParam(url);
111
134
  };
135
+ const resolveReference = (reference) => {
136
+ const setReferenceUrl = (referenceUrl) => {
137
+ // ignored urls are prefixed with "ignore:" so that reference are associated
138
+ // to a dedicated urlInfo that is ignored.
139
+ // this way it's only once a resource is referenced by reference that is not ignored
140
+ // that the resource is cooked
141
+ if (
142
+ reference.specifier[0] === "#" &&
143
+ // For Html, css and "#" refer to a resource in the page, reference must be preserved
144
+ // However for js import specifiers they have a different meaning and we want
145
+ // to resolve them (https://nodejs.org/api/packages.html#imports for instance)
146
+ reference.type !== "js_import"
147
+ ) {
148
+ referenceUrl = `ignore:${referenceUrl}`;
149
+ } else if (isIgnored(referenceUrl)) {
150
+ referenceUrl = `ignore:${referenceUrl}`;
151
+ }
112
152
 
113
- /*
114
- * - "http_request"
115
- * - "entry_point"
116
- * - "link_href"
117
- * - "style"
118
- * - "script"
119
- * - "a_href"
120
- * - "iframe_src
121
- * - "img_src"
122
- * - "img_srcset"
123
- * - "source_src"
124
- * - "source_srcset"
125
- * - "image_href"
126
- * - "use_href"
127
- * - "css_@import"
128
- * - "css_url"
129
- * - "js_import"
130
- * - "js_import_script"
131
- * - "js_url"
132
- * - "js_inline_content"
133
- * - "sourcemap_comment"
134
- * - "webmanifest_icon_src"
135
- * - "package_json"
136
- * - "side_effect_file"
137
- * */
138
- const createReference = ({
139
- data = {},
140
- node,
141
- trace,
142
- parentUrl,
143
- type,
144
- subtype,
145
- expectedContentType,
146
- expectedType,
147
- expectedSubtype,
148
- filename,
149
- integrity,
150
- crossorigin,
151
- specifier,
152
- specifierStart,
153
- specifierEnd,
154
- specifierLine,
155
- specifierColumn,
156
- baseUrl,
157
- isOriginalPosition,
158
- isEntryPoint = false,
159
- isResourceHint = false,
160
- isImplicit = false,
161
- hasVersioningEffect = false,
162
- injected = false,
163
- isInline = false,
164
- content,
165
- contentType,
166
- assert,
167
- assertNode,
168
- typePropertyNode,
169
- leadsToADirectory = false,
170
- debug = false,
171
- }) => {
172
- if (typeof specifier !== "string") {
173
- if (specifier instanceof URL) {
174
- specifier = specifier.href;
175
- } else {
176
- throw new TypeError(`"specifier" must be a string, got ${specifier}`);
153
+ if (
154
+ referenceUrl.startsWith("ignore:") &&
155
+ !reference.specifier.startsWith("ignore:")
156
+ ) {
157
+ reference.specifier = `ignore:${reference.specifier}`;
177
158
  }
178
- }
179
- const reference = {
180
- original: null,
181
- prev: null,
182
- next: null,
183
- data,
184
- node,
185
- trace,
186
- parentUrl,
187
- url: null,
188
- searchParams: null,
189
- generatedUrl: null,
190
- generatedSpecifier: null,
191
- type,
192
- subtype,
193
- expectedContentType,
194
- expectedType,
195
- expectedSubtype,
196
- filename,
197
- integrity,
198
- crossorigin,
199
- specifier,
200
- specifierStart,
201
- specifierEnd,
202
- specifierLine,
203
- specifierColumn,
204
- isOriginalPosition,
205
- baseUrl,
206
- isEntryPoint,
207
- isResourceHint,
208
- isImplicit,
209
- hasVersioningEffect,
210
- version: null,
211
- injected,
212
- timing: {},
213
- // for inline resources the reference contains the content
214
- isInline,
215
- content,
216
- contentType,
217
- escape: null,
218
- // import assertions (maybe move to data?)
219
- assert,
220
- assertNode,
221
- typePropertyNode,
222
- leadsToADirectory,
223
- mutation: null,
224
- debug,
159
+ Object.defineProperty(reference, "url", {
160
+ enumerable: true,
161
+ configurable: false,
162
+ writable: false,
163
+ value: referenceUrl,
164
+ });
165
+ reference.searchParams = new URL(referenceUrl).searchParams;
225
166
  };
226
- // Object.preventExtensions(reference) // useful to ensure all properties are declared here
227
- return reference;
228
- };
229
- const updateReference = (reference, newReference) => {
230
- reference.next = newReference;
231
- newReference.original = reference.original || reference;
232
167
 
233
- newReference.prev = reference;
234
- };
235
- const resolveReference = (reference, context = kitchenContext) => {
236
- const referenceContext = {
237
- ...context,
238
- resolveReference: (reference, context = referenceContext) =>
239
- resolveReference(reference, context),
240
- };
241
168
  try {
242
- let url = pluginController.callHooksUntil(
243
- "resolveReference",
244
- reference,
245
- referenceContext,
246
- );
247
- if (!url) {
248
- throw new Error(`NO_RESOLVE`);
249
- }
250
- if (url.includes("?debug")) {
251
- reference.debug = true;
252
- }
253
- url = normalizeUrl(url);
254
- let referencedUrlObject;
255
- let searchParams;
256
- const setReferenceUrl = (referenceUrl) => {
257
- // ignored urls are prefixed with "ignore:" so that reference are associated
258
- // to a dedicated urlInfo that is ignored.
259
- // this way it's only once a resource is referenced by reference that is not ignored
260
- // that the resource is cooked
261
- if (
262
- reference.specifier[0] === "#" &&
263
- // For Html, css and "#" refer to a resource in the page, reference must be preserved
264
- // However for js import specifiers they have a different meaning and we want
265
- // to resolve them (https://nodejs.org/api/packages.html#imports for instance)
266
- reference.type !== "js_import"
267
- ) {
268
- referenceUrl = `ignore:${referenceUrl}`;
269
- } else if (isIgnored(referenceUrl)) {
270
- referenceUrl = `ignore:${referenceUrl}`;
169
+ resolve: {
170
+ if (reference.url) {
171
+ setReferenceUrl(reference.url);
172
+ break resolve;
271
173
  }
272
-
273
- if (
274
- referenceUrl.startsWith("ignore:") &&
275
- !reference.specifier.startsWith("ignore:")
276
- ) {
277
- reference.specifier = `ignore:${reference.specifier}`;
174
+ const resolvedUrl = pluginController.callHooksUntil(
175
+ "resolveReference",
176
+ reference,
177
+ );
178
+ if (!resolvedUrl) {
179
+ throw new Error(`NO_RESOLVE`);
278
180
  }
279
-
280
- referencedUrlObject = new URL(referenceUrl);
281
- searchParams = referencedUrlObject.searchParams;
282
- reference.url = referenceUrl;
283
- reference.searchParams = searchParams;
284
- };
285
- setReferenceUrl(url);
286
-
287
- if (reference.debug) {
288
- logger.debug(`url resolved by "${
289
- pluginController.getLastPluginUsed().name
290
- }"
181
+ if (resolvedUrl.includes("?debug")) {
182
+ reference.debug = true;
183
+ }
184
+ const normalizedUrl = normalizeUrl(resolvedUrl);
185
+ setReferenceUrl(normalizedUrl);
186
+ if (reference.debug) {
187
+ logger.debug(`url resolved by "${
188
+ pluginController.getLastPluginUsed().name
189
+ }"
291
190
  ${ANSI.color(reference.specifier, ANSI.GREY)} ->
292
191
  ${ANSI.color(reference.url, ANSI.YELLOW)}
293
192
  `);
193
+ }
294
194
  }
295
- pluginController.callHooks(
296
- "redirectReference",
297
- reference,
298
- referenceContext,
299
- (returnValue, plugin) => {
300
- const normalizedReturnValue = normalizeUrl(returnValue);
301
- if (normalizedReturnValue === reference.url) {
302
- return;
303
- }
304
- if (reference.debug) {
305
- logger.debug(
306
- `url redirected by "${plugin.name}"
195
+ redirect: {
196
+ if (reference.isImplicit && reference.isWeak) {
197
+ // not needed for implicit references that are not rendered anywhere
198
+ // this condition excludes:
199
+ // - side_effect_file references injected in entry points or at the top of files
200
+ break redirect;
201
+ }
202
+ pluginController.callHooks(
203
+ "redirectReference",
204
+ reference,
205
+ (returnValue, plugin, setReference) => {
206
+ const normalizedReturnValue = normalizeUrl(returnValue);
207
+ if (normalizedReturnValue === reference.url) {
208
+ return;
209
+ }
210
+ if (reference.debug) {
211
+ logger.debug(
212
+ `url redirected by "${plugin.name}"
307
213
  ${ANSI.color(reference.url, ANSI.GREY)} ->
308
214
  ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
309
215
  `,
216
+ );
217
+ }
218
+ const referenceRedirected = reference.redirect(
219
+ normalizedReturnValue,
310
220
  );
311
- }
312
- const prevReference = { ...reference };
313
- updateReference(prevReference, reference);
314
- setReferenceUrl(normalizedReturnValue);
315
- },
316
- );
221
+ reference = referenceRedirected;
222
+ setReferenceUrl(normalizedReturnValue);
223
+ setReference(referenceRedirected);
224
+ },
225
+ );
226
+ }
317
227
  reference.generatedUrl = reference.url;
228
+ return reference;
229
+ } catch (error) {
230
+ throw createResolveUrlError({
231
+ pluginController,
232
+ reference,
233
+ error,
234
+ });
235
+ }
236
+ };
237
+ kitchenContext.resolveReference = resolveReference;
318
238
 
319
- const urlInfo = urlGraph.reuseOrCreateUrlInfo(reference.url);
320
- applyReferenceEffectsOnUrlInfo(reference, urlInfo, context);
239
+ const finalizeReference = (reference) => {
240
+ if (reference.isImplicit && reference.isWeak) {
241
+ // not needed for implicit references that are not rendered anywhere
242
+ // this condition excludes:
243
+ // - side_effect_file references injected in entry points or at the top of files
244
+ return;
245
+ }
321
246
 
247
+ transform_search_params: {
322
248
  // This hook must touch reference.generatedUrl, NOT reference.url
323
249
  // And this is because this hook inject query params used to:
324
250
  // - bypass browser cache (?v)
325
- // - convey information (?hmr)
251
+ // - convey information (?hot)
326
252
  // But do not represent an other resource, it is considered as
327
253
  // the same resource under the hood
328
254
  pluginController.callHooks(
329
255
  "transformReferenceSearchParams",
330
256
  reference,
331
- referenceContext,
332
257
  (returnValue) => {
333
258
  Object.keys(returnValue).forEach((key) => {
334
- searchParams.set(key, returnValue[key]);
259
+ reference.searchParams.set(key, returnValue[key]);
335
260
  });
261
+ const referencedUrlObject = new URL(reference.url);
262
+ const search = reference.searchParams.toString();
263
+ referencedUrlObject.search = search;
336
264
  reference.generatedUrl = normalizeUrl(referencedUrlObject.href);
337
265
  },
338
266
  );
339
-
267
+ }
268
+ format: {
340
269
  const returnValue = pluginController.callHooksUntil(
341
270
  "formatReference",
342
271
  reference,
343
- referenceContext,
344
272
  );
345
273
  if (reference.url.startsWith("ignore:")) {
346
274
  if (ignoreProtocol === "remove") {
@@ -352,92 +280,21 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
352
280
  reference.generatedSpecifier = returnValue || reference.generatedUrl;
353
281
  reference.generatedSpecifier = urlSpecifierEncoding.encode(reference);
354
282
  }
355
- return [reference, urlInfo];
356
- } catch (error) {
357
- throw createResolveUrlError({
358
- pluginController,
359
- reference,
360
- error,
361
- });
362
283
  }
363
284
  };
364
- kitchenContext.resolveReference = resolveReference;
285
+ kitchenContext.finalizeReference = finalizeReference;
365
286
 
366
- const urlInfoTransformer = createUrlInfoTransformer({
367
- logger,
368
- urlGraph,
369
- sourcemaps,
370
- sourcemapsSourcesProtocol,
371
- sourcemapsSourcesContent,
372
- sourcemapsSourcesRelative,
373
- clientRuntimeCompat,
374
- injectSourcemapPlaceholder: ({ urlInfo, specifier }) => {
375
- const [sourcemapReference, sourcemapUrlInfo] = resolveReference(
376
- createReference({
377
- trace: {
378
- message: `sourcemap comment placeholder`,
379
- url: urlInfo.url,
380
- },
381
- type: "sourcemap_comment",
382
- expectedType: "sourcemap",
383
- subtype: urlInfo.contentType === "text/javascript" ? "js" : "css",
384
- parentUrl: urlInfo.url,
385
- specifier,
386
- }),
387
- );
388
- sourcemapUrlInfo.type = "sourcemap";
389
- return [sourcemapReference, sourcemapUrlInfo];
390
- },
391
- foundSourcemap: ({
392
- urlInfo,
393
- type,
394
- specifier,
395
- specifierLine,
396
- specifierColumn,
397
- }) => {
398
- const sourcemapUrlSite = adjustUrlSite(urlInfo, {
399
- urlGraph,
400
- url: urlInfo.url,
401
- line: specifierLine,
402
- column: specifierColumn,
403
- });
404
- const [sourcemapReference, sourcemapUrlInfo] = resolveReference(
405
- createReference({
406
- trace: traceFromUrlSite(sourcemapUrlSite),
407
- type,
408
- expectedType: "sourcemap",
409
- parentUrl: urlInfo.url,
410
- specifier,
411
- specifierLine,
412
- specifierColumn,
413
- }),
414
- );
415
- if (sourcemapReference.isInline) {
416
- sourcemapUrlInfo.isInline = true;
417
- }
418
- sourcemapUrlInfo.type = "sourcemap";
419
- return [sourcemapReference, sourcemapUrlInfo];
420
- },
421
- });
422
-
423
- const fetchUrlContent = async (
424
- urlInfo,
425
- { reference, contextDuringFetch },
426
- ) => {
287
+ const fetchUrlContent = async (urlInfo) => {
427
288
  try {
428
289
  const fetchUrlContentReturnValue =
429
- await pluginController.callAsyncHooksUntil(
430
- "fetchUrlContent",
431
- urlInfo,
432
- contextDuringFetch,
433
- );
290
+ await pluginController.callAsyncHooksUntil("fetchUrlContent", urlInfo);
434
291
  if (!fetchUrlContentReturnValue) {
435
292
  logger.warn(
436
293
  createDetailedMessage(
437
294
  `no plugin has handled url during "fetchUrlContent" hook -> url will be ignored`,
438
295
  {
439
296
  "url": urlInfo.url,
440
- "url reference trace": reference.trace.message,
297
+ "url reference trace": urlInfo.firstReference.trace.message,
441
298
  },
442
299
  ),
443
300
  );
@@ -471,9 +328,14 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
471
328
  urlInfo.contentType = contentType;
472
329
  urlInfo.headers = headers;
473
330
  urlInfo.type =
474
- type || reference.expectedType || inferUrlInfoType(urlInfo);
331
+ type ||
332
+ urlInfo.firstReference.expectedType ||
333
+ inferUrlInfoType(urlInfo);
475
334
  urlInfo.subtype =
476
- subtype || reference.expectedSubtype || urlInfo.subtypeHint || "";
335
+ subtype ||
336
+ urlInfo.firstReference.expectedSubtype ||
337
+ urlInfo.subtypeHint ||
338
+ "";
477
339
  // during build urls info are reused and load returns originalUrl/originalContent
478
340
  urlInfo.originalUrl = originalUrl || urlInfo.originalUrl;
479
341
  if (data) {
@@ -482,18 +344,14 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
482
344
  if (typeof isEntryPoint === "boolean") {
483
345
  urlInfo.isEntryPoint = isEntryPoint;
484
346
  }
485
- if (filename) {
347
+ if (filename && !urlInfo.filename) {
486
348
  urlInfo.filename = filename;
487
349
  }
488
350
  assertFetchedContentCompliance({
489
- reference,
490
351
  urlInfo,
491
352
  content,
492
353
  });
493
- urlInfo.generatedUrl = determineFileUrlForOutDirectory({
494
- urlInfo,
495
- context: contextDuringFetch,
496
- });
354
+ urlInfo.generatedUrl = determineFileUrlForOutDirectory(urlInfo);
497
355
 
498
356
  // we wait here to read .contentAst and .originalContentAst
499
357
  // so that we don't trigger lazy getters
@@ -508,570 +366,210 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
508
366
  fetchUrlContentReturnValue,
509
367
  "originalContentAst",
510
368
  );
511
- await urlInfoTransformer.initTransformations(
512
- urlInfo,
513
- {
514
- content,
515
- sourcemap,
516
- originalContent,
517
- contentAst: contentAstDescriptor
518
- ? contentAstDescriptor.get
519
- ? undefined
520
- : contentAstDescriptor.value
521
- : undefined,
522
- originalContentAst: originalContentAstDescriptor
523
- ? originalContentAstDescriptor.get
524
- ? undefined
525
- : originalContentAstDescriptor.value
526
- : undefined,
527
- },
528
- contextDuringFetch,
529
- );
369
+ await urlInfoTransformer.setContent(urlInfo, content, {
370
+ sourcemap,
371
+ originalContent,
372
+ contentAst: contentAstDescriptor
373
+ ? contentAstDescriptor.get
374
+ ? undefined
375
+ : contentAstDescriptor.value
376
+ : undefined,
377
+ originalContentAst: originalContentAstDescriptor
378
+ ? originalContentAstDescriptor.get
379
+ ? undefined
380
+ : originalContentAstDescriptor.value
381
+ : undefined,
382
+ });
530
383
  } catch (error) {
531
384
  throw createFetchUrlContentError({
532
385
  pluginController,
533
386
  urlInfo,
534
- reference,
535
387
  error,
536
388
  });
537
389
  }
538
390
  };
539
391
  kitchenContext.fetchUrlContent = fetchUrlContent;
540
392
 
541
- const _cook = async (urlInfo, dishContext) => {
542
- const context = {
543
- ...kitchenContext,
544
- ...dishContext,
545
- };
546
- const { cookDuringCook = cook } = dishContext;
547
- context.cook = (urlInfo, nestedDishContext) => {
548
- return cookDuringCook(urlInfo, {
549
- ...dishContext,
550
- ...nestedDishContext,
551
- });
552
- };
553
- context.fetchUrlContent = (urlInfo, { reference }) => {
554
- return fetchUrlContent(urlInfo, {
555
- reference,
556
- contextDuringFetch: context,
393
+ const transformUrlContent = async (urlInfo) => {
394
+ try {
395
+ await pluginController.callAsyncHooks(
396
+ "transformUrlContent",
397
+ urlInfo,
398
+ (transformReturnValue) => {
399
+ urlInfoTransformer.applyTransformations(
400
+ urlInfo,
401
+ transformReturnValue,
402
+ );
403
+ },
404
+ );
405
+ } catch (error) {
406
+ const transformError = createTransformUrlContentError({
407
+ pluginController,
408
+ urlInfo,
409
+ error,
557
410
  });
558
- };
411
+ urlInfo.error = transformError;
412
+ throw transformError;
413
+ }
414
+ };
415
+ kitchenContext.transformUrlContent = transformUrlContent;
559
416
 
560
- if (!urlInfo.url.startsWith("ignore:")) {
561
- // references
562
- const references = [];
563
- const addReference = (props) => {
564
- const [reference, referencedUrlInfo] = resolveReference(
565
- createReference({
566
- parentUrl: urlInfo.url,
567
- ...props,
568
- }),
569
- context,
570
- );
571
- references.push(reference);
572
- return [reference, referencedUrlInfo];
573
- };
574
- const mutateReference = (currentReference, newReferenceParams) => {
575
- const index = references.indexOf(currentReference);
576
- if (index === -1) {
577
- throw new Error(`reference do not exists`);
578
- }
579
- const ref = createReference({
580
- ...currentReference,
581
- ...newReferenceParams,
582
- });
583
- const [newReference, newUrlInfo] = resolveReference(ref, context);
584
- updateReference(currentReference, newReference);
585
- references[index] = newReference;
586
- const currentUrlInfo = context.urlGraph.getUrlInfo(
587
- currentReference.url,
588
- );
589
- if (
590
- currentUrlInfo &&
591
- currentUrlInfo !== newUrlInfo &&
592
- !urlGraph.isUsed(currentUrlInfo)
593
- ) {
594
- context.urlGraph.deleteUrlInfo(currentReference.url);
595
- }
596
- return [newReference, newUrlInfo];
597
- };
417
+ const finalizeUrlContent = async (urlInfo) => {
418
+ try {
419
+ await urlInfo.applyContentTransformationCallbacks();
420
+ const finalizeReturnValue = await pluginController.callAsyncHooksUntil(
421
+ "finalizeUrlContent",
422
+ urlInfo,
423
+ );
424
+ urlInfoTransformer.endTransformations(urlInfo, finalizeReturnValue);
425
+ } catch (error) {
426
+ throw createFinalizeUrlContentError({
427
+ pluginController,
428
+ urlInfo,
429
+ error,
430
+ });
431
+ }
432
+ };
433
+ kitchenContext.finalizeUrlContent = finalizeUrlContent;
598
434
 
599
- const beforeFinalizeCallbacks = [];
600
- context.referenceUtils = {
601
- inlineContentClientFileUrl,
602
- _references: references,
603
- find: (predicate) => references.find(predicate),
604
- readGeneratedSpecifier,
605
- found: ({ trace, ...rest }) => {
606
- if (trace === undefined) {
607
- trace = traceFromUrlSite(
608
- adjustUrlSite(urlInfo, {
609
- urlGraph,
610
- url: urlInfo.url,
611
- line: rest.specifierLine,
612
- column: rest.specifierColumn,
613
- }),
614
- );
615
- }
616
- // console.log(trace.message)
617
- return addReference({
618
- trace,
619
- ...rest,
620
- });
621
- },
622
- foundInline: ({
623
- isOriginalPosition,
624
- specifierLine,
625
- specifierColumn,
626
- ...rest
627
- }) => {
628
- const parentUrl = isOriginalPosition
629
- ? urlInfo.url
630
- : urlInfo.generatedUrl;
631
- const parentContent = isOriginalPosition
632
- ? urlInfo.originalContent
633
- : urlInfo.content;
634
- return addReference({
635
- trace: traceFromUrlSite({
636
- url: parentUrl,
637
- content: parentContent,
638
- line: specifierLine,
639
- column: specifierColumn,
640
- }),
641
- isOriginalPosition,
642
- specifierLine,
643
- specifierColumn,
644
- isInline: true,
645
- ...rest,
646
- });
647
- },
648
- foundSideEffectFile: async ({ sideEffectFileUrl, trace, ...rest }) => {
649
- if (trace === undefined) {
650
- const { url, line, column } = getCallerPosition();
651
- trace = traceFromUrlSite({
652
- url,
653
- line,
654
- column,
655
- });
656
- }
435
+ const cookGuard = dev ? debounceCook : memoizeCook;
436
+ const cook = cookGuard(async (urlInfo, contextDuringCook) => {
437
+ if (contextDuringCook) {
438
+ Object.assign(urlInfo.context, contextDuringCook);
439
+ }
657
440
 
658
- const addRef = () =>
659
- addReference({
660
- trace,
661
- type: "side_effect_file",
662
- isImplicit: true,
663
- injected: true,
664
- specifier: sideEffectFileUrl,
665
- ...rest,
666
- });
441
+ // urlInfo objects are reused, they must be "reset" before cooking them again
442
+ if (urlInfo.error || urlInfo.content !== undefined) {
443
+ urlInfo.error = null;
444
+ urlInfo.type = null;
445
+ urlInfo.subtype = null;
446
+ urlInfo.timing = {};
447
+ urlInfoTransformer.resetContent(urlInfo);
448
+ }
667
449
 
668
- const injectAsBannerCodeBeforeFinalize = (
669
- sideEffectFileReference,
670
- sideEffectFileUrlInfo,
671
- ) => {
672
- beforeFinalizeCallbacks.push(async () => {
673
- await context.cook(sideEffectFileUrlInfo, {
674
- reference: sideEffectFileReference,
675
- });
676
- await context.referenceUtils.readGeneratedSpecifier(
677
- sideEffectFileReference,
678
- );
679
- await prependContent(
680
- urlInfoTransformer,
681
- urlInfo,
682
- sideEffectFileUrlInfo,
683
- );
684
- context.referenceUtils.becomesInline(sideEffectFileReference, {
685
- specifierLine: 0,
686
- specifierColumn: 0,
687
- specifier: sideEffectFileReference.generatedSpecifier,
688
- content: sideEffectFileUrlInfo.content,
689
- contentType: sideEffectFileUrlInfo.contentType,
690
- parentUrl: urlInfo.url,
691
- parentContent: urlInfo.content,
692
- });
693
- });
694
- return [sideEffectFileReference, sideEffectFileUrlInfo];
695
- };
450
+ if (!urlInfo.url.startsWith("ignore:")) {
451
+ try {
452
+ await urlInfo.dependencies.startCollecting(async () => {
453
+ // "fetchUrlContent" hook
454
+ await urlInfo.fetchContent();
696
455
 
697
- // When possible we inject code inside the file in the HTML
698
- // -> less duplication
456
+ // "transform" hook
457
+ await urlInfo.transformContent();
699
458
 
700
- // Case #1: Not possible to inject in other files -> inject as banner code
701
- if (!["js_classic", "js_module", "css"].includes(urlInfo.type)) {
702
- const [sideEffectFileReference, sideEffectFileUrlInfo] = addRef();
703
- return injectAsBannerCodeBeforeFinalize(
704
- sideEffectFileReference,
705
- sideEffectFileUrlInfo,
706
- );
707
- }
459
+ // "finalize" hook
460
+ await urlInfo.finalizeContent();
461
+ });
462
+ } catch (e) {
463
+ urlInfo.error = e;
464
+ if (e.code === "DIRECTORY_REFERENCE_NOT_ALLOWED") {
465
+ throw e;
466
+ }
467
+ if (urlInfo.isInline) {
468
+ // When something like <style> or <script> contains syntax error
469
+ // the HTML in itself it still valid
470
+ // keep the syntax error and continue with the HTML
471
+ const errorInfo =
472
+ e.code === "PARSE_ERROR"
473
+ ? `${e.cause.reasonCode}\n${e.traceMessage}`
474
+ : `${e.traceMessage}`;
475
+ logger.error(
476
+ `Error while handling ${urlInfo.type} declared in ${urlInfo.firstReference.trace.message}: ${errorInfo}`,
477
+ );
478
+ } else {
479
+ throw e;
480
+ }
481
+ }
482
+ }
708
483
 
709
- // Case #2: During dev
710
- // during dev cooking files is incremental
711
- // so HTML is already executed by the browser
712
- // but if we find that ref in a dependent we are good
713
- // and it's possible to find it in dependents when using
714
- // dynamic import for instance
715
- // (in that case we find the side effect file as it was injected in parent)
716
- if (context.dev) {
717
- const urlsBeforeInjection = Array.from(urlGraph.urlInfoMap.keys());
718
- const [sideEffectFileReference, sideEffectFileUrlInfo] = addRef();
719
- if (!urlsBeforeInjection.includes(sideEffectFileReference.url)) {
720
- return injectAsBannerCodeBeforeFinalize(
721
- sideEffectFileReference,
722
- sideEffectFileUrlInfo,
723
- );
724
- }
725
- const isReferencingSideEffectFile = (urlInfo) =>
726
- urlInfo.references.some(
727
- (ref) => ref.url === sideEffectFileReference.url,
728
- );
729
- const selfOrAncestorIsReferencingSideEffectFile = (
730
- dependentUrl,
731
- ) => {
732
- const dependentUrlInfo = urlGraph.getUrlInfo(dependentUrl);
733
- if (isReferencingSideEffectFile(dependentUrlInfo)) {
734
- return true;
735
- }
736
- const dependentReferencingThatFile = GRAPH_VISITOR.findDependent(
737
- urlGraph,
738
- urlInfo,
739
- (ancestorUrlInfo) =>
740
- isReferencingSideEffectFile(ancestorUrlInfo),
741
- );
742
- return Boolean(dependentReferencingThatFile);
743
- };
744
- for (const dependentUrl of urlInfo.dependents) {
745
- if (!selfOrAncestorIsReferencingSideEffectFile(dependentUrl)) {
746
- return injectAsBannerCodeBeforeFinalize(
747
- sideEffectFileReference,
748
- sideEffectFileUrlInfo,
749
- );
750
- }
484
+ // "cooked" hook
485
+ pluginController.callHooks("cooked", urlInfo, (cookedReturnValue) => {
486
+ if (typeof cookedReturnValue === "function") {
487
+ const prevCallback = graph.pruneUrlInfoCallbackRef.current;
488
+ graph.pruneUrlInfoCallbackRef.current(
489
+ (prunedUrlInfo, lastReferenceFromOther) => {
490
+ prevCallback();
491
+ if (prunedUrlInfo === urlInfo.url) {
492
+ graph.pruneUrlInfoCallbackRef.current = prevCallback;
493
+ cookedReturnValue(lastReferenceFromOther.urlInfo);
751
494
  }
752
- return [sideEffectFileReference, sideEffectFileUrlInfo];
753
- }
754
-
755
- // Case #3: During build
756
- // during build, files are not executed so it's
757
- // possible to inject reference when discovering a side effect file
758
- if (urlInfo.isEntryPoint) {
759
- const [sideEffectFileReference, sideEffectFileUrlInfo] = addRef();
760
- return injectAsBannerCodeBeforeFinalize(
761
- sideEffectFileReference,
762
- sideEffectFileUrlInfo,
763
- );
764
- }
765
- const entryPoints = urlGraph.getEntryPoints();
766
- const [sideEffectFileReference, sideEffectFileUrlInfo] = addRef();
767
- for (const entryPointUrlInfo of entryPoints) {
768
- sideEffectForwardCallbacks.push(async () => {
769
- // do not inject if already there
770
- const { dependencies } = entryPointUrlInfo;
771
- if (dependencies.has(sideEffectFileUrlInfo.url)) {
772
- return;
773
- }
774
- dependencies.add(sideEffectFileUrlInfo.url);
775
- await prependContent(
776
- urlInfoTransformer,
777
- entryPointUrlInfo,
778
- sideEffectFileUrlInfo,
779
- );
780
- await context.referenceUtils.readGeneratedSpecifier(
781
- sideEffectFileReference,
782
- );
783
- context.referenceUtils.becomesInline(sideEffectFileReference, {
784
- specifier: sideEffectFileReference.generatedSpecifier,
785
- // ideally get the correct line and column
786
- // (for js it's 0, but for html it's different)
787
- specifierLine: 0,
788
- specifierColumn: 0,
789
- content: sideEffectFileUrlInfo.content,
790
- contentType: sideEffectFileUrlInfo.contentType,
791
- parentUrl: entryPointUrlInfo.url,
792
- parentContent: entryPointUrlInfo.content,
793
- });
794
- });
795
- }
796
- return [sideEffectFileReference, sideEffectFileUrlInfo];
797
- },
798
- inject: ({ trace, ...rest }) => {
799
- if (trace === undefined) {
800
- const { url, line, column } = getCallerPosition();
801
- trace = traceFromUrlSite({
802
- url,
803
- line,
804
- column,
805
- });
806
- }
807
- return addReference({
808
- trace,
809
- injected: true,
810
- ...rest,
811
- });
812
- },
813
- becomesInline: (
814
- reference,
815
- {
816
- isOriginalPosition = reference.isOriginalPosition,
817
- specifier,
818
- specifierLine,
819
- specifierColumn,
820
- contentType,
821
- content,
822
- parentUrl = reference.parentUrl,
823
- parentContent,
824
- },
825
- ) => {
826
- const trace = traceFromUrlSite({
827
- url:
828
- parentUrl === undefined
829
- ? isOriginalPosition
830
- ? urlInfo.url
831
- : urlInfo.generatedUrl
832
- : parentUrl,
833
- content:
834
- parentContent === undefined
835
- ? isOriginalPosition
836
- ? urlInfo.originalContent
837
- : urlInfo.content
838
- : parentContent,
839
- line: specifierLine,
840
- column: specifierColumn,
841
- });
842
- return mutateReference(reference, {
843
- trace,
844
- parentUrl,
845
- isOriginalPosition,
846
- isInline: true,
847
- specifier,
848
- specifierLine,
849
- specifierColumn,
850
- contentType,
851
- content,
852
- });
853
- },
854
- becomesExternal: () => {
855
- throw new Error("not implemented yet");
856
- },
857
- };
858
-
859
- // "fetchUrlContent" hook
860
- await fetchUrlContent(urlInfo, {
861
- reference: context.reference,
862
- contextDuringFetch: context,
863
- });
864
-
865
- // "transform" hook
866
- try {
867
- await pluginController.callAsyncHooks(
868
- "transformUrlContent",
869
- urlInfo,
870
- context,
871
- (transformReturnValue) => {
872
- urlInfoTransformer.applyTransformations(
873
- urlInfo,
874
- transformReturnValue,
875
- );
876
495
  },
877
496
  );
878
- } catch (error) {
879
- urlGraph.updateReferences(urlInfo, references); // ensure reference are updated even in case of error
880
- const transformError = createTransformUrlContentError({
881
- pluginController,
882
- reference: context.reference,
883
- urlInfo,
884
- error,
885
- });
886
- urlInfo.error = transformError;
887
- throw transformError;
888
497
  }
498
+ });
499
+ });
500
+ kitchenContext.cook = cook;
889
501
 
890
- // after "transform" all references from originalContent
891
- // and the one injected by plugin are known
892
- urlGraph.updateReferences(urlInfo, references);
502
+ const lastTransformationCallbacks = [];
503
+ const addLastTransformationCallback = (callback) => {
504
+ lastTransformationCallbacks.push(callback);
505
+ };
506
+ kitchenContext.addLastTransformationCallback = addLastTransformationCallback;
893
507
 
894
- // "finalize" hook
895
- try {
896
- for (const beforeFinalizeCallback of beforeFinalizeCallbacks) {
897
- await beforeFinalizeCallback();
898
- }
899
- beforeFinalizeCallbacks.length = 0;
508
+ const cookDependencies = async (
509
+ urlInfo,
510
+ { operation, ignoreDynamicImport } = {},
511
+ ) => {
512
+ const seen = new Set();
900
513
 
901
- const finalizeReturnValue = await pluginController.callAsyncHooksUntil(
902
- "finalizeUrlContent",
903
- urlInfo,
904
- context,
905
- );
906
- urlInfoTransformer.applyTransformations(urlInfo, finalizeReturnValue);
907
- urlInfoTransformer.applyTransformationsEffects(urlInfo);
908
- } catch (error) {
909
- throw createFinalizeUrlContentError({
910
- pluginController,
911
- reference: context.reference,
912
- urlInfo,
913
- error,
914
- });
514
+ const cookSelfThenDependencies = async (urlInfo) => {
515
+ if (operation) {
516
+ operation.throwIfAborted();
915
517
  }
916
- }
518
+ if (seen.has(urlInfo)) {
519
+ return;
520
+ }
521
+ seen.add(urlInfo);
522
+ await urlInfo.cook();
523
+ await startCookingDependencies(urlInfo);
524
+ };
917
525
 
918
- // "cooked" hook
919
- pluginController.callHooks(
920
- "cooked",
921
- urlInfo,
922
- context,
923
- (cookedReturnValue) => {
924
- if (typeof cookedReturnValue === "function") {
925
- const removePrunedCallback = urlGraph.prunedCallbackList.add(
926
- ({ prunedUrlInfos, firstUrlInfo }) => {
927
- const pruned = prunedUrlInfos.find(
928
- (prunedUrlInfo) => prunedUrlInfo.url === urlInfo.url,
929
- );
930
- if (pruned) {
931
- removePrunedCallback();
932
- cookedReturnValue(firstUrlInfo);
933
- }
934
- },
935
- );
526
+ const startCookingDependencies = async (urlInfo) => {
527
+ const dependencyPromises = [];
528
+ for (const referenceToOther of urlInfo.referenceToOthersSet) {
529
+ if (referenceToOther.type === "sourcemap_comment") {
530
+ // we don't cook sourcemap reference by sourcemap comments
531
+ // because this is already done in "initTransformations"
532
+ continue;
936
533
  }
937
- },
938
- );
939
- };
940
- const cook = memoizeCook(async (urlInfo, context) => {
941
- if (!outDirectoryUrl) {
942
- await _cook(urlInfo, context);
943
- return;
944
- }
945
- // writing result inside ".jsenv" directory (debug purposes)
946
- try {
947
- await _cook(urlInfo, context);
948
- } finally {
949
- const { generatedUrl } = urlInfo;
950
- if (generatedUrl && generatedUrl.startsWith("file:")) {
951
- if (urlInfo.type === "directory") {
952
- // no need to write the directory
953
- } else if (urlInfo.content === null) {
954
- // Some error might lead to urlInfo.content to be null
955
- // (error hapenning before urlInfo.content can be set, or 404 for instance)
956
- // in that case we can't write anything
957
- } else {
958
- let contentIsInlined = urlInfo.isInline;
959
- if (
960
- contentIsInlined &&
961
- context.supervisor &&
962
- urlGraph.getUrlInfo(urlInfo.inlineUrlSite.url).type === "html"
963
- ) {
964
- contentIsInlined = false;
965
- }
966
- if (!contentIsInlined) {
967
- writeFileSync(new URL(generatedUrl), urlInfo.content);
968
- }
969
- const { sourcemapGeneratedUrl, sourcemap } = urlInfo;
970
- if (sourcemapGeneratedUrl && sourcemap) {
971
- writeFileSync(
972
- new URL(sourcemapGeneratedUrl),
973
- JSON.stringify(sourcemap, null, " "),
974
- );
975
- }
534
+ if (referenceToOther.isWeak) {
535
+ // we don't cook weak references (resource hints mostly)
536
+ // because they might refer to resource that will be modified during build
537
+ // It also means something else have to reference that url in order to cook it
538
+ // so that the preload is deleted by "resync_resource_hints.js" otherwise
539
+ continue;
540
+ }
541
+ if (referenceToOther.isImplicit) {
542
+ // implicit reference are not auto cooked
543
+ // when needed code is explicitely cooking/fetching the underlying url
544
+ continue;
976
545
  }
546
+ if (
547
+ ignoreDynamicImport &&
548
+ referenceToOther.subtype === "import_dynamic"
549
+ ) {
550
+ continue;
551
+ }
552
+ const referencedUrlInfo = referenceToOther.urlInfo;
553
+ const dependencyPromise = cookSelfThenDependencies(referencedUrlInfo);
554
+ dependencyPromises.push(dependencyPromise);
977
555
  }
978
- }
979
- });
980
- kitchenContext.cook = cook;
556
+ await Promise.all(dependencyPromises);
557
+ };
981
558
 
982
- const prepareEntryPoint = (params) => {
983
- return resolveReference(
984
- createReference({
985
- ...params,
986
- isEntryPoint: true,
559
+ await startCookingDependencies(urlInfo);
560
+ await Promise.all(
561
+ lastTransformationCallbacks.map(async (callback) => {
562
+ await callback();
987
563
  }),
988
564
  );
565
+ lastTransformationCallbacks.length = 0;
989
566
  };
990
- kitchenContext.prepareEntryPoint = prepareEntryPoint;
991
-
992
- const injectReference = (params) => {
993
- return resolveReference(createReference(params));
994
- };
995
- kitchenContext.injectReference = injectReference;
567
+ kitchenContext.cookDependencies = cookDependencies;
996
568
 
997
- const getWithoutSearchParam = ({
998
- urlInfo,
999
- reference,
1000
- context,
1001
- searchParam,
1002
- expectedType,
1003
- }) => {
1004
- const urlObject = new URL(urlInfo.url);
1005
- const { searchParams } = urlObject;
1006
- if (!searchParams.has(searchParam)) {
1007
- return [null, null];
1008
- }
1009
- searchParams.delete(searchParam);
1010
- const originalRef =
1011
- reference || context.reference.original || context.reference;
1012
- const referenceWithoutSearchParam = {
1013
- ...originalRef,
1014
- original: originalRef,
1015
- searchParams,
1016
- data: { ...originalRef.data },
1017
- expectedType,
1018
- specifier: originalRef.specifier
1019
- .replace(`?${searchParam}`, "")
1020
- .replace(`&${searchParam}`, ""),
1021
- url: normalizeUrl(urlObject.href),
1022
- generatedSpecifier: null,
1023
- generatedUrl: null,
1024
- filename: null,
1025
- };
1026
- const urlInfoWithoutSearchParam = context.urlGraph.reuseOrCreateUrlInfo(
1027
- referenceWithoutSearchParam.url,
1028
- );
1029
- if (urlInfoWithoutSearchParam.originalUrl === undefined) {
1030
- applyReferenceEffectsOnUrlInfo(
1031
- referenceWithoutSearchParam,
1032
- urlInfoWithoutSearchParam,
1033
- context,
1034
- );
1035
- }
1036
- return [referenceWithoutSearchParam, urlInfoWithoutSearchParam];
1037
- };
1038
- kitchenContext.getWithoutSearchParam = getWithoutSearchParam;
1039
-
1040
- return {
1041
- pluginController,
1042
- urlInfoTransformer,
1043
- rootDirectoryUrl,
1044
- kitchenContext,
1045
- cook,
1046
- createReference,
1047
- injectReference,
1048
- injectForwardedSideEffectFiles: async () => {
1049
- await Promise.all(
1050
- sideEffectForwardCallbacks.map(async (callback) => {
1051
- await callback();
1052
- }),
1053
- );
1054
- },
1055
- };
1056
- };
1057
-
1058
- // "formatReferencedUrl" can be async BUT this is an exception
1059
- // for most cases it will be sync. We want to favor the sync signature to keep things simpler
1060
- // The only case where it needs to be async is when
1061
- // the specifier is a `data:*` url
1062
- // in this case we'll wait for the promise returned by
1063
- // "formatReferencedUrl"
1064
- const readGeneratedSpecifier = (reference) => {
1065
- if (reference.generatedSpecifier.then) {
1066
- return reference.generatedSpecifier.then((value) => {
1067
- reference.generatedSpecifier = value;
1068
- return value;
1069
- });
1070
- }
1071
- return reference.generatedSpecifier;
569
+ return kitchen;
1072
570
  };
1073
571
 
1074
- const memoizeCook = (cook) => {
572
+ const debounceCook = (cook) => {
1075
573
  const pendingDishes = new Map();
1076
574
  return async (urlInfo, context) => {
1077
575
  const { url, modifiedTimestamp } = urlInfo;
@@ -1101,116 +599,44 @@ const memoizeCook = (cook) => {
1101
599
  };
1102
600
  };
1103
601
 
602
+ const memoizeCook = (cook) => {
603
+ const urlInfoCache = new Map();
604
+ return async (urlInfo, context) => {
605
+ const fromCache = urlInfoCache.get(urlInfo);
606
+ if (fromCache) {
607
+ await fromCache;
608
+ return;
609
+ }
610
+ let resolveCookPromise;
611
+ const promise = new Promise((resolve) => {
612
+ resolveCookPromise = resolve;
613
+ });
614
+ urlInfoCache.set(urlInfo, promise);
615
+ await cook(urlInfo, context);
616
+ resolveCookPromise();
617
+ };
618
+ };
619
+
1104
620
  const memoizeIsSupported = (runtimeCompat) => {
1105
621
  const cache = new Map();
1106
- return (feature) => {
622
+ return (feature, featureCompat) => {
1107
623
  const fromCache = cache.get(feature);
1108
624
  if (typeof fromCache === "boolean") {
1109
625
  return fromCache;
1110
626
  }
1111
- const supported = RUNTIME_COMPAT.isSupported(runtimeCompat, feature);
627
+ const supported = RUNTIME_COMPAT.isSupported(
628
+ runtimeCompat,
629
+ feature,
630
+ featureCompat,
631
+ );
1112
632
  cache.set(feature, supported);
1113
633
  return supported;
1114
634
  };
1115
635
  };
1116
636
 
1117
- const traceFromUrlSite = (urlSite) => {
1118
- return {
1119
- message: stringifyUrlSite(urlSite),
1120
- url: urlSite.url,
1121
- line: urlSite.line,
1122
- column: urlSite.column,
1123
- };
1124
- };
1125
-
1126
- const applyReferenceEffectsOnUrlInfo = (reference, urlInfo, context) => {
1127
- urlInfo.originalUrl = urlInfo.originalUrl || reference.url;
1128
-
1129
- if (reference.isEntryPoint || isWebWorkerEntryPointReference(reference)) {
1130
- urlInfo.isEntryPoint = true;
1131
- }
1132
- Object.assign(urlInfo.data, reference.data);
1133
- Object.assign(urlInfo.timing, reference.timing);
1134
- if (reference.injected) {
1135
- urlInfo.injected = true;
1136
- }
1137
- if (reference.filename && !urlInfo.filename) {
1138
- urlInfo.filename = reference.filename;
1139
- }
1140
- if (reference.isInline) {
1141
- urlInfo.isInline = true;
1142
- const parentUrlInfo = context.urlGraph.getUrlInfo(reference.parentUrl);
1143
- urlInfo.inlineUrlSite = {
1144
- url: parentUrlInfo.url,
1145
- content: reference.isOriginalPosition
1146
- ? parentUrlInfo.originalContent
1147
- : parentUrlInfo.content,
1148
- line: reference.specifierLine,
1149
- column: reference.specifierColumn,
1150
- };
1151
- urlInfo.contentType = reference.contentType;
1152
- urlInfo.originalContent = context.build
1153
- ? urlInfo.originalContent === undefined
1154
- ? reference.content
1155
- : urlInfo.originalContent
1156
- : reference.content;
1157
- urlInfo.content = reference.content;
1158
- }
1159
-
1160
- if (reference.debug) {
1161
- urlInfo.debug = true;
1162
- }
1163
- if (reference.expectedType) {
1164
- urlInfo.typeHint = reference.expectedType;
1165
- }
1166
- if (reference.expectedSubtype) {
1167
- urlInfo.subtypeHint = reference.expectedSubtype;
1168
- }
1169
- };
1170
-
1171
- const adjustUrlSite = (urlInfo, { urlGraph, url, line, column }) => {
1172
- const isOriginal = url === urlInfo.url;
1173
- const adjust = (urlSite, urlInfo) => {
1174
- if (!urlSite.isOriginal) {
1175
- return urlSite;
1176
- }
1177
- const inlineUrlSite = urlInfo.inlineUrlSite;
1178
- if (!inlineUrlSite) {
1179
- return urlSite;
1180
- }
1181
- const parentUrlInfo = urlGraph.getUrlInfo(inlineUrlSite.url);
1182
- return adjust(
1183
- {
1184
- isOriginal: true,
1185
- url: inlineUrlSite.url,
1186
- content: inlineUrlSite.content,
1187
- line:
1188
- inlineUrlSite.line === undefined
1189
- ? urlSite.line
1190
- : inlineUrlSite.line + urlSite.line,
1191
- column:
1192
- inlineUrlSite.column === undefined
1193
- ? urlSite.column
1194
- : inlineUrlSite.column + urlSite.column,
1195
- },
1196
- parentUrlInfo,
1197
- );
1198
- };
1199
- return adjust(
1200
- {
1201
- isOriginal,
1202
- url,
1203
- content: isOriginal ? urlInfo.originalContent : urlInfo.content,
1204
- line,
1205
- column,
1206
- },
1207
- urlInfo,
1208
- );
1209
- };
1210
-
1211
637
  const inferUrlInfoType = (urlInfo) => {
1212
- const { type } = urlInfo;
1213
- if (type === "sourcemap") {
638
+ const { type, typeHint } = urlInfo;
639
+ if (type === "sourcemap" || typeHint === "sourcemap") {
1214
640
  return "sourcemap";
1215
641
  }
1216
642
  const { contentType } = urlInfo;
@@ -1242,24 +668,26 @@ const inferUrlInfoType = (urlInfo) => {
1242
668
  return "other";
1243
669
  };
1244
670
 
1245
- const determineFileUrlForOutDirectory = ({ urlInfo, context }) => {
1246
- if (!context.outDirectoryUrl) {
671
+ const determineFileUrlForOutDirectory = (urlInfo) => {
672
+ if (!urlInfo.context.outDirectoryUrl) {
1247
673
  return urlInfo.url;
1248
674
  }
1249
675
  if (!urlInfo.url.startsWith("file:")) {
1250
676
  return urlInfo.url;
1251
677
  }
1252
678
  let url = urlInfo.url;
1253
- if (!urlIsInsideOf(urlInfo.url, context.rootDirectoryUrl)) {
679
+ if (!urlIsInsideOf(urlInfo.url, urlInfo.context.rootDirectoryUrl)) {
1254
680
  const fsRootUrl = ensureWindowsDriveLetter("file:///", urlInfo.url);
1255
- url = `${context.rootDirectoryUrl}@fs/${url.slice(fsRootUrl.length)}`;
681
+ url = `${urlInfo.context.rootDirectoryUrl}@fs/${url.slice(
682
+ fsRootUrl.length,
683
+ )}`;
1256
684
  }
1257
685
  if (urlInfo.filename) {
1258
686
  url = setUrlFilename(url, urlInfo.filename);
1259
687
  }
1260
688
  return moveUrl({
1261
689
  url,
1262
- from: context.rootDirectoryUrl,
1263
- to: context.outDirectoryUrl,
690
+ from: urlInfo.context.rootDirectoryUrl,
691
+ to: urlInfo.context.outDirectoryUrl,
1264
692
  });
1265
693
  };