@jsenv/core 28.1.1 → 28.2.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 (51) hide show
  1. package/dist/js/script_type_module_supervisor.js +8 -13
  2. package/dist/js/supervisor.js +702 -534
  3. package/dist/main.js +13275 -13164
  4. package/package.json +5 -5
  5. package/readme.md +3 -3
  6. package/src/build/build.js +960 -712
  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 +3 -2
  10. package/src/execute/run.js +1 -1
  11. package/src/execute/runtimes/browsers/from_playwright.js +1 -1
  12. package/src/omega/compat/runtime_compat.js +9 -6
  13. package/src/omega/errors.js +3 -0
  14. package/src/omega/fetched_content_compliance.js +2 -2
  15. package/src/omega/kitchen.js +189 -145
  16. package/src/omega/server/file_service.js +104 -71
  17. package/src/omega/url_graph/url_graph_loader.js +77 -0
  18. package/src/omega/url_graph/url_info_transformations.js +12 -15
  19. package/src/omega/url_graph.js +115 -101
  20. package/src/plugins/autoreload/jsenv_plugin_autoreload_client.js +1 -0
  21. package/src/plugins/autoreload/jsenv_plugin_autoreload_server.js +34 -36
  22. package/src/plugins/autoreload/jsenv_plugin_hmr.js +3 -2
  23. package/src/plugins/bundling/js_module/{bundle_js_module.js → bundle_js_modules.js} +51 -14
  24. package/src/plugins/bundling/jsenv_plugin_bundling.js +2 -2
  25. package/src/plugins/import_meta_hot/jsenv_plugin_import_meta_hot.js +11 -0
  26. package/src/plugins/inline/jsenv_plugin_html_inline_content.js +73 -62
  27. package/src/plugins/node_esm_resolution/jsenv_plugin_node_esm_resolution.js +77 -89
  28. package/src/plugins/plugin_controller.js +26 -22
  29. package/src/plugins/server_events/jsenv_plugin_server_events_client_injection.js +1 -0
  30. package/src/plugins/supervisor/client/script_type_module_supervisor.js +7 -9
  31. package/src/plugins/supervisor/client/supervisor.js +125 -96
  32. package/src/plugins/supervisor/jsenv_plugin_supervisor.js +2 -4
  33. package/src/plugins/toolbar/client/execution/toolbar_execution.js +1 -1
  34. package/src/plugins/transpilation/as_js_classic/async-to-promises.js +16 -0
  35. package/src/plugins/transpilation/as_js_classic/convert_js_module_to_js_classic.js +85 -0
  36. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic.js +48 -190
  37. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic_conversion.js +102 -0
  38. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic_html.js +161 -240
  39. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic_library.js +84 -0
  40. package/src/plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic_workers.js +19 -12
  41. package/src/plugins/transpilation/babel/jsenv_plugin_babel.js +1 -24
  42. package/src/plugins/transpilation/import_assertions/jsenv_plugin_import_assertions.js +82 -52
  43. package/src/plugins/transpilation/jsenv_plugin_transpilation.js +12 -13
  44. package/src/plugins/url_analysis/html/html_urls.js +91 -34
  45. package/src/plugins/url_analysis/js/js_urls.js +5 -4
  46. package/src/plugins/url_resolution/jsenv_plugin_url_resolution.js +1 -0
  47. package/src/test/execute_plan.js +3 -8
  48. package/src/test/execute_test_plan.js +1 -1
  49. package/src/build/inject_service_worker_urls.js +0 -78
  50. package/src/build/resync_resource_hints.js +0 -112
  51. package/src/omega/url_graph/url_graph_load.js +0 -74
@@ -1,15 +1,24 @@
1
1
  /*
2
- * Things hapenning here:
3
- * 1. load raw build files
4
- * 2. bundle files
5
- * 3. optimize files (minify mostly)
6
- * 4. urls versioning
2
+ * Build is split in 3 steps:
3
+ * 1. craft
4
+ * 2. shape
5
+ * 3. refine
6
+ *
7
+ * craft: prepare all the materials
8
+ * - resolve, fetch and transform all source files into "rawGraph"
9
+ * shape: this step can drastically change url content and their relationships
10
+ * - bundling
11
+ * - optimizations (minification)
12
+ * refine: perform minor changes on the url contents
13
+ * - cleaning html
14
+ * - url versioning
15
+ * - ressource hints
16
+ * - injecting urls into service workers
7
17
  */
8
18
 
9
19
  import {
10
20
  injectQueryParams,
11
21
  setUrlFilename,
12
- asUrlUntilPathname,
13
22
  normalizeUrl,
14
23
  asUrlWithoutSearch,
15
24
  ensurePathnameTrailingSlash,
@@ -21,13 +30,25 @@ import {
21
30
  import {
22
31
  assertAndNormalizeDirectoryUrl,
23
32
  ensureEmptyDirectory,
24
- writeFile,
33
+ writeFileSync,
25
34
  registerDirectoryLifecycle,
26
35
  } from "@jsenv/filesystem"
27
36
  import { Abort, raceProcessTeardownEvents } from "@jsenv/abort"
28
- import { createLogger, loggerToLevels, createTaskLog } from "@jsenv/log"
29
- import { generateSourcemapFileUrl } from "@jsenv/sourcemap"
30
- import { parseHtmlString, stringifyHtmlAst } from "@jsenv/ast"
37
+ import {
38
+ createLogger,
39
+ createTaskLog,
40
+ ANSI,
41
+ createDetailedMessage,
42
+ } from "@jsenv/log"
43
+ import { createMagicSource, generateSourcemapFileUrl } from "@jsenv/sourcemap"
44
+ import {
45
+ parseHtmlString,
46
+ stringifyHtmlAst,
47
+ visitHtmlNodes,
48
+ getHtmlNodeAttribute,
49
+ setHtmlNodeAttributes,
50
+ removeHtmlNode,
51
+ } from "@jsenv/ast"
31
52
 
32
53
  import { sortByDependencies } from "../omega/url_graph/sort_by_dependencies.js"
33
54
  import { createUrlGraph } from "../omega/url_graph.js"
@@ -36,16 +57,14 @@ import { jsenvPluginInline } from "../plugins/inline/jsenv_plugin_inline.js"
36
57
  import { jsenvPluginAsJsClassic } from "../plugins/transpilation/as_js_classic/jsenv_plugin_as_js_classic.js"
37
58
  import { getCorePlugins } from "../plugins/plugins.js"
38
59
  import { createKitchen } from "../omega/kitchen.js"
39
- import { loadUrlGraph } from "../omega/url_graph/url_graph_load.js"
60
+ import { createUrlGraphLoader } from "../omega/url_graph/url_graph_loader.js"
40
61
  import { createUrlGraphSummary } from "../omega/url_graph/url_graph_report.js"
41
62
  import { isWebWorkerEntryPointReference } from "../omega/web_workers.js"
42
63
 
43
64
  import { GRAPH } from "./graph_utils.js"
44
65
  import { createBuilUrlsGenerator } from "./build_urls_generator.js"
45
- import { injectGlobalVersionMapping } from "./inject_global_version_mappings.js"
66
+ import { injectVersionMappings } from "./inject_global_version_mappings.js"
46
67
  import { createVersionGenerator } from "./version_generator.js"
47
- import { injectServiceWorkerUrls } from "./inject_service_worker_urls.js"
48
- import { resyncResourceHints } from "./resync_resource_hints.js"
49
68
 
50
69
  // default runtimeCompat corresponds to
51
70
  // "we can keep <script type="module"> intact":
@@ -151,7 +170,6 @@ export const build = async ({
151
170
  const runBuild = async ({ signal, logLevel }) => {
152
171
  const logger = createLogger({ logLevel })
153
172
  const buildOperation = Abort.startOperation()
154
- const infoLogsAreDisabled = !loggerToLevels(logger).info
155
173
  buildOperation.addAbortSignal(signal)
156
174
  const entryPointKeys = Object.keys(entryPoints)
157
175
  if (entryPointKeys.length === 1) {
@@ -164,29 +182,27 @@ build ${entryPointKeys.length} entry points`)
164
182
  const useExplicitJsClassicConversion = entryPointKeys.some((key) =>
165
183
  entryPoints[key].includes("?as_js_classic"),
166
184
  )
167
-
185
+ const rawRedirections = new Map()
186
+ const bundleRedirections = new Map()
187
+ const bundleInternalRedirections = new Map()
188
+ const finalRedirections = new Map()
189
+ const versioningRedirections = new Map()
190
+ const entryUrls = []
168
191
  const rawGraph = createUrlGraph()
169
- const prebuildTask = createTaskLog("prebuild", {
170
- disabled: infoLogsAreDisabled,
171
- })
172
- const prebuildRedirections = new Map()
173
192
  const rawGraphKitchen = createKitchen({
174
193
  signal,
175
- logger,
194
+ logLevel,
176
195
  rootDirectoryUrl,
177
196
  urlGraph: rawGraph,
178
197
  scenarios: { build: true },
179
- sourcemaps,
180
- sourcemapsSourcesContent,
181
198
  runtimeCompat,
182
- writeGeneratedFiles,
183
199
  plugins: [
184
200
  ...plugins,
185
201
  {
186
202
  appliesDuring: "build",
187
203
  fetchUrlContent: (urlInfo, context) => {
188
204
  if (context.reference.original) {
189
- prebuildRedirections.set(
205
+ rawRedirections.set(
190
206
  context.reference.original.url,
191
207
  context.reference.url,
192
208
  )
@@ -211,326 +227,97 @@ build ${entryPointKeys.length} entry points`)
211
227
  transpilation: {
212
228
  ...transpilation,
213
229
  babelHelpersAsImport: !useExplicitJsClassicConversion,
214
- jsModuleAsJsClassic: false,
230
+ jsClassicFallback: false,
215
231
  },
216
232
  minification,
217
233
  bundling,
218
234
  }),
219
235
  ],
236
+ sourcemaps,
237
+ sourcemapsSourcesContent,
238
+ writeGeneratedFiles,
239
+ outDirectoryUrl: new URL(`.jsenv/build/`, rootDirectoryUrl),
220
240
  })
221
- const entryUrls = []
222
- try {
223
- await loadUrlGraph({
224
- operation: buildOperation,
225
- urlGraph: rawGraph,
226
- kitchen: rawGraphKitchen,
227
- writeGeneratedFiles,
228
- outDirectoryUrl: new URL(`.jsenv/build/`, rootDirectoryUrl),
229
- startLoading: (cookEntryFile) => {
230
- Object.keys(entryPoints).forEach((key) => {
231
- const [, entryUrlInfo] = cookEntryFile({
232
- trace: {
233
- message: `"${key}" in entryPoints parameter`,
234
- },
235
- type: "entry_point",
236
- specifier: key,
237
- })
238
- entryUrls.push(entryUrlInfo.url)
239
- entryUrlInfo.filename = entryPoints[key]
240
- // entryUrlInfo.data.entryPointKey = key
241
- })
242
- },
243
- })
244
- } catch (e) {
245
- prebuildTask.fail()
246
- throw e
247
- }
248
- prebuildTask.done()
249
241
 
250
242
  const buildUrlsGenerator = createBuilUrlsGenerator({
251
243
  buildDirectoryUrl,
252
244
  })
253
- const rawUrls = {}
254
- const bundleRedirections = {}
255
- const buildUrls = {}
256
- const bundleUrlInfos = {}
257
- const bundlers = {}
258
- rawGraphKitchen.pluginController.plugins.forEach((plugin) => {
259
- const bundle = plugin.bundle
260
- if (!bundle) {
261
- return
262
- }
263
- if (typeof bundle !== "object") {
264
- throw new Error(
265
- `bundle must be an object, found "${bundle}" on plugin named "${plugin.name}"`,
266
- )
267
- }
268
- Object.keys(bundle).forEach((type) => {
269
- const bundleFunction = bundle[type]
270
- if (!bundleFunction) {
271
- return
272
- }
273
- const bundlerForThatType = bundlers[type]
274
- if (bundlerForThatType) {
275
- // first plugin to define a bundle hook wins
276
- return
277
- }
278
- bundlers[type] = {
279
- plugin,
280
- bundleFunction: bundle[type],
281
- urlInfos: [],
282
- }
283
- })
284
- })
285
- const addToBundlerIfAny = (rawUrlInfo) => {
286
- const bundler = bundlers[rawUrlInfo.type]
287
- if (bundler) {
288
- bundler.urlInfos.push(rawUrlInfo)
289
- return
245
+ const buildToRawUrls = {}
246
+ // rename "buildDirectoryRedirections"?
247
+ const associateBuildUrlAndRawUrl = (buildUrl, rawUrl, reason) => {
248
+ if (urlIsInsideOf(rawUrl, buildDirectoryUrl)) {
249
+ throw new Error(`raw url must be inside rawGraph, got ${rawUrl}`)
290
250
  }
251
+ logger.debug(`build url generated (${reason})
252
+ ${ANSI.color(rawUrl, ANSI.GREY)} ->
253
+ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
254
+ `)
255
+ buildToRawUrls[buildUrl] = rawUrl
291
256
  }
292
- GRAPH.forEach(rawGraph, (rawUrlInfo) => {
293
- if (rawUrlInfo.isEntryPoint) {
294
- addToBundlerIfAny(rawUrlInfo)
295
- if (rawUrlInfo.type === "html") {
296
- rawUrlInfo.dependencies.forEach((dependencyUrl) => {
297
- const dependencyUrlInfo = rawGraph.getUrlInfo(dependencyUrl)
298
- if (dependencyUrlInfo.isInline) {
299
- if (dependencyUrlInfo.type === "js_module") {
300
- // bundle inline script type module deps
301
- dependencyUrlInfo.references.forEach((inlineScriptRef) => {
302
- if (inlineScriptRef.type === "js_import_export") {
303
- const inlineUrlInfo = rawGraph.getUrlInfo(
304
- inlineScriptRef.url,
305
- )
306
- addToBundlerIfAny(inlineUrlInfo)
307
- }
308
- })
309
- }
310
- // inline content cannot be bundled
311
- return
312
- }
313
- addToBundlerIfAny(dependencyUrlInfo)
314
- })
315
- rawUrlInfo.references.forEach((reference) => {
316
- if (
317
- reference.isResourceHint &&
318
- reference.expectedType === "js_module"
319
- ) {
320
- const referencedUrlInfo = rawGraph.getUrlInfo(reference.url)
321
- if (
322
- referencedUrlInfo &&
323
- // something else than the resource hint is using this url
324
- referencedUrlInfo.dependents.size > 0
325
- ) {
326
- addToBundlerIfAny(referencedUrlInfo)
327
- }
328
- }
329
- })
330
- return
331
- }
332
- }
333
- // File referenced with new URL('./file.js', import.meta.url)
334
- // are entry points that can be bundled
335
- // For instance we will bundle service worker/workers detected like this
336
- if (rawUrlInfo.type === "js_module") {
337
- rawUrlInfo.references.forEach((reference) => {
338
- if (reference.type === "js_url_specifier") {
339
- const urlInfo = rawGraph.getUrlInfo(reference.url)
340
- addToBundlerIfAny(urlInfo)
341
- }
342
- })
343
- }
344
- })
345
- const bundleInternalRedirections = {}
346
- await Object.keys(bundlers).reduce(async (previous, type) => {
347
- await previous
348
- const bundler = bundlers[type]
349
- const urlInfosToBundle = bundler.urlInfos
350
- if (urlInfosToBundle.length === 0) {
351
- return
352
- }
353
- const bundleTask = createTaskLog(`bundle "${type}"`, {
354
- disabled: infoLogsAreDisabled,
355
- })
356
- try {
357
- const bundlerGeneratedUrlInfos =
358
- await rawGraphKitchen.pluginController.callAsyncHook(
359
- {
360
- plugin: bundler.plugin,
361
- hookName: "bundle",
362
- value: bundler.bundleFunction,
363
- },
364
- urlInfosToBundle,
365
- {
366
- ...rawGraphKitchen.kitchenContext,
367
- buildDirectoryUrl,
368
- },
369
- )
370
- Object.keys(bundlerGeneratedUrlInfos).forEach((url) => {
371
- const rawUrlInfo = rawGraph.getUrlInfo(url)
372
- const bundlerGeneratedUrlInfo = bundlerGeneratedUrlInfos[url]
373
- const bundleUrlInfo = {
374
- type,
375
- subtype: rawUrlInfo ? rawUrlInfo.subtype : undefined,
376
- isEntryPoint: rawUrlInfo ? rawUrlInfo.isEntryPoint : undefined,
377
- filename: rawUrlInfo ? rawUrlInfo.filename : undefined,
378
- originalUrl: rawUrlInfo ? rawUrlInfo.originalUrl : undefined,
379
- originalContent: rawUrlInfo
380
- ? rawUrlInfo.originalContent
381
- : undefined,
382
- ...bundlerGeneratedUrlInfo,
383
- data: {
384
- ...(rawUrlInfo ? rawUrlInfo.data : {}),
385
- ...bundlerGeneratedUrlInfo.data,
386
- fromBundle: true,
387
- },
388
- }
389
- const buildUrl = buildUrlsGenerator.generate(url, {
390
- urlInfo: bundleUrlInfo,
391
- })
392
- bundleRedirections[url] = buildUrl
393
- rawUrls[buildUrl] = url
394
- bundleUrlInfos[buildUrl] = bundleUrlInfo
395
- if (buildUrl.includes("?")) {
396
- bundleUrlInfos[asUrlWithoutSearch(buildUrl)] = bundleUrlInfo
397
- }
398
- if (bundlerGeneratedUrlInfo.data.bundleRelativeUrl) {
399
- const urlForBundler = new URL(
400
- bundlerGeneratedUrlInfo.data.bundleRelativeUrl,
401
- buildDirectoryUrl,
402
- ).href
403
- if (urlForBundler !== buildUrl) {
404
- bundleInternalRedirections[urlForBundler] = buildUrl
405
- }
406
- }
407
- })
408
- } catch (e) {
409
- bundleTask.fail()
410
- throw e
411
- }
412
- bundleTask.done()
413
- }, Promise.resolve())
414
-
257
+ const buildUrls = new Map()
258
+ const bundleUrlInfos = {}
259
+ const bundlers = {}
260
+ const finalGraph = createUrlGraph()
415
261
  const urlAnalysisPlugin = jsenvPluginUrlAnalysis({
416
262
  rootDirectoryUrl,
417
263
  ...urlAnalysis,
418
264
  })
419
- const postBuildRedirections = {}
420
- const finalGraph = createUrlGraph()
421
265
  const finalGraphKitchen = createKitchen({
422
- logger,
266
+ logLevel,
423
267
  rootDirectoryUrl,
424
268
  urlGraph: finalGraph,
425
269
  scenarios: { build: true },
426
- sourcemaps,
427
- sourcemapsSourcesContent,
428
- sourcemapsRelativeSources: !versioning,
429
270
  runtimeCompat,
430
- writeGeneratedFiles,
431
271
  plugins: [
432
272
  urlAnalysisPlugin,
433
- jsenvPluginAsJsClassic({ systemJsInjection: true }),
434
- jsenvPluginInline({ fetchInlineUrls: false }),
273
+ jsenvPluginAsJsClassic({
274
+ jsClassicLibrary: false,
275
+ jsClassicFallback: true,
276
+ systemJsInjection: true,
277
+ }),
278
+ jsenvPluginInline({
279
+ fetchInlineUrls: false,
280
+ }),
435
281
  {
436
- name: "jsenv:postbuild",
282
+ name: "jsenv:build",
437
283
  appliesDuring: "build",
438
284
  resolveUrl: (reference) => {
439
- const performInternalRedirections = (url) => {
440
- const prebuildRedirection = prebuildRedirections.get(url)
441
- if (prebuildRedirection) {
442
- logger.debug(
443
- `\nprebuild redirection\n${url} ->\n${prebuildRedirection}\n`,
444
- )
445
- url = prebuildRedirection
446
- }
447
- const bundleRedirection = bundleRedirections[url]
448
- if (bundleRedirection) {
449
- logger.debug(
450
- `\nbundler redirection\n${url} ->\n${bundleRedirection}\n`,
451
- )
452
- url = bundleRedirection
285
+ const getUrl = () => {
286
+ if (reference.type === "filesystem") {
287
+ const parentRawUrl = buildToRawUrls[reference.parentUrl]
288
+ const baseUrl = ensurePathnameTrailingSlash(parentRawUrl)
289
+ return new URL(reference.specifier, baseUrl).href
453
290
  }
454
- const bundleInternalRedirection = bundleInternalRedirections[url]
455
- if (bundleInternalRedirection) {
456
- logger.debug(
457
- `\nbundler internal redirection\n${url} ->\n${bundleInternalRedirection}\n`,
458
- )
459
- url = bundleInternalRedirection
291
+ if (reference.specifier[0] === "/") {
292
+ return new URL(reference.specifier.slice(1), buildDirectoryUrl)
293
+ .href
460
294
  }
461
- return url
462
- }
463
-
464
- if (reference.type === "filesystem") {
465
- const parentRawUrl = rawUrls[reference.parentUrl]
466
- const baseUrl = ensurePathnameTrailingSlash(parentRawUrl)
467
- return performInternalRedirections(
468
- new URL(reference.specifier, baseUrl).href,
469
- )
470
- }
471
- if (reference.specifier[0] === "/") {
472
- return performInternalRedirections(
473
- new URL(reference.specifier.slice(1), buildDirectoryUrl).href,
474
- )
475
- }
476
- return performInternalRedirections(
477
- new URL(
295
+ return new URL(
478
296
  reference.specifier,
479
297
  reference.baseUrl || reference.parentUrl,
480
- ).href,
481
- )
298
+ ).href
299
+ }
300
+ let url = getUrl()
301
+ // url = rawRedirections.get(url) || url
302
+ url = bundleRedirections.get(url) || url
303
+ url = bundleInternalRedirections.get(url) || url
304
+ return url
482
305
  },
483
306
  // redirecting urls into the build directory
484
307
  redirectUrl: (reference) => {
485
308
  if (!reference.url.startsWith("file:")) {
486
309
  return null
487
310
  }
311
+ // referenced by resource hint
312
+ // -> keep it untouched, it will be handled by "resync_resource_hints"
313
+ if (reference.isResourceHint) {
314
+ return reference.original ? reference.original.url : null
315
+ }
488
316
  // already a build url
489
- const rawUrl = rawUrls[reference.url]
317
+ const rawUrl = buildToRawUrls[reference.url]
490
318
  if (rawUrl) {
491
319
  return reference.url
492
320
  }
493
- // from "js_module_as_js_classic":
494
- // - injecting "?as_js_classic" for the first time
495
- // - injecting "?as_js_classic" because the parentUrl has it
496
- if (reference.original) {
497
- const referenceOriginalUrl = reference.original.url
498
- let originalBuildUrl
499
- if (urlIsInsideOf(referenceOriginalUrl, buildDirectoryUrl)) {
500
- originalBuildUrl = referenceOriginalUrl
501
- } else {
502
- originalBuildUrl = Object.keys(rawUrls).find(
503
- (key) => rawUrls[key] === referenceOriginalUrl,
504
- )
505
- }
506
- let rawUrl
507
- if (urlIsInsideOf(reference.url, buildDirectoryUrl)) {
508
- const originalBuildUrl =
509
- postBuildRedirections[referenceOriginalUrl]
510
- rawUrl = originalBuildUrl
511
- ? rawUrls[originalBuildUrl]
512
- : reference.url
513
- } else {
514
- rawUrl = reference.url
515
- }
516
- // the url info do not exists yet (it will be created after this "normalize" hook)
517
- // And the content will be generated when url is cooked by url graph loader.
518
- // Here we just want to reserve an url for that file
519
- const buildUrl = buildUrlsGenerator.generate(rawUrl, {
520
- urlInfo: {
521
- data: reference.data,
522
- isEntryPoint:
523
- reference.isEntryPoint ||
524
- isWebWorkerEntryPointReference(reference),
525
- type: reference.expectedType,
526
- subtype: reference.expectedSubtype,
527
- filename: reference.filename,
528
- },
529
- })
530
- postBuildRedirections[originalBuildUrl] = buildUrl
531
- rawUrls[buildUrl] = rawUrl
532
- return buildUrl
533
- }
534
321
  if (reference.isInline) {
535
322
  const rawUrlInfo = GRAPH.find(rawGraph, (rawUrlInfo) => {
536
323
  if (!rawUrlInfo.isInline) {
@@ -555,7 +342,51 @@ build ${entryPointKeys.length} entry points`)
555
342
  urlInfo: rawUrlInfo,
556
343
  parentUrlInfo,
557
344
  })
558
- rawUrls[buildUrl] = rawUrlInfo.url
345
+ associateBuildUrlAndRawUrl(
346
+ buildUrl,
347
+ rawUrlInfo.url,
348
+ "inline content",
349
+ )
350
+ return buildUrl
351
+ }
352
+ // from "js_module_as_js_classic":
353
+ // - injecting "?as_js_classic" for the first time
354
+ // - injecting "?as_js_classic" because the parentUrl has it
355
+ if (reference.original) {
356
+ const urlBeforeRedirect = reference.original.url
357
+ const urlAfterRedirect = reference.url
358
+ const isEntryPoint =
359
+ reference.isEntryPoint ||
360
+ isWebWorkerEntryPointReference(reference)
361
+ // the url info do not exists yet (it will be created after this "redirectUrl" hook)
362
+ // And the content will be generated when url is cooked by url graph loader.
363
+ // Here we just want to reserve an url for that file
364
+ const urlInfo = {
365
+ data: reference.data,
366
+ isEntryPoint,
367
+ type: reference.expectedType,
368
+ subtype: reference.expectedSubtype,
369
+ filename: reference.filename,
370
+ }
371
+ if (urlIsInsideOf(urlBeforeRedirect, buildDirectoryUrl)) {
372
+ // the redirection happened on a build url, happens due to:
373
+ // 1. bundling
374
+ const buildUrl = buildUrlsGenerator.generate(urlAfterRedirect, {
375
+ urlInfo,
376
+ })
377
+ finalRedirections.set(urlBeforeRedirect, buildUrl)
378
+ return buildUrl
379
+ }
380
+ const rawUrl = urlAfterRedirect
381
+ const buildUrl = buildUrlsGenerator.generate(rawUrl, {
382
+ urlInfo,
383
+ })
384
+ finalRedirections.set(urlBeforeRedirect, buildUrl)
385
+ associateBuildUrlAndRawUrl(
386
+ buildUrl,
387
+ rawUrl,
388
+ "redirected during postbuild",
389
+ )
559
390
  return buildUrl
560
391
  }
561
392
  // from "js_module_as_js_classic":
@@ -567,20 +398,34 @@ build ${entryPointKeys.length} entry points`)
567
398
  type: "js_classic",
568
399
  },
569
400
  })
570
- rawUrls[buildUrl] = reference.url
401
+ associateBuildUrlAndRawUrl(
402
+ buildUrl,
403
+ reference.url,
404
+ "injected during postbuild",
405
+ )
406
+ finalRedirections.set(buildUrl, buildUrl)
571
407
  return buildUrl
572
408
  }
573
409
  const rawUrlInfo = rawGraph.getUrlInfo(reference.url)
574
410
  const parentUrlInfo = finalGraph.getUrlInfo(reference.parentUrl)
575
411
  // files from root directory but not given to rollup nor postcss
576
412
  if (rawUrlInfo) {
577
- const buildUrl = buildUrlsGenerator.generate(reference.url, {
578
- urlInfo: rawUrlInfo,
579
- parentUrlInfo,
580
- })
581
- rawUrls[buildUrl] = rawUrlInfo.url
413
+ const referencedUrlObject = new URL(reference.url)
414
+ referencedUrlObject.searchParams.delete("as_js_classic_library")
415
+ const buildUrl = buildUrlsGenerator.generate(
416
+ referencedUrlObject.href,
417
+ {
418
+ urlInfo: rawUrlInfo,
419
+ parentUrlInfo,
420
+ },
421
+ )
422
+ associateBuildUrlAndRawUrl(buildUrl, rawUrlInfo.url, "raw file")
582
423
  if (buildUrl.includes("?")) {
583
- rawUrls[asUrlWithoutSearch(buildUrl)] = rawUrlInfo.url
424
+ associateBuildUrlAndRawUrl(
425
+ asUrlWithoutSearch(buildUrl),
426
+ rawUrlInfo.url,
427
+ "raw file",
428
+ )
584
429
  }
585
430
  return buildUrl
586
431
  }
@@ -606,21 +451,26 @@ build ${entryPointKeys.length} entry points`)
606
451
  }
607
452
  return null
608
453
  }
454
+ if (reference.isResourceHint) {
455
+ return null
456
+ }
609
457
  if (!urlIsInsideOf(reference.generatedUrl, buildDirectoryUrl)) {
610
458
  throw new Error(
611
459
  `urls should be inside build directory at this stage, found "${reference.url}"`,
612
460
  )
613
461
  }
614
- if (reference.isResourceHint) {
615
- // return the raw url, we will resync at the end
616
- return rawUrls[reference.url]
617
- }
618
- // remove eventual search params and hash
619
- const urlUntilPathname = asUrlUntilPathname(reference.generatedUrl)
462
+ const generatedUrlObject = new URL(reference.generatedUrl)
463
+ generatedUrlObject.searchParams.delete("as_js_classic")
464
+ generatedUrlObject.searchParams.delete("as_js_classic_library")
465
+ generatedUrlObject.searchParams.delete("as_json_module")
466
+ generatedUrlObject.searchParams.delete("as_css_module")
467
+ generatedUrlObject.searchParams.delete("as_text_module")
468
+ generatedUrlObject.hash = ""
469
+ const generatedUrl = generatedUrlObject.href
620
470
  let specifier
621
471
  if (baseUrl === "./") {
622
472
  const relativeUrl = urlToRelativeUrl(
623
- urlUntilPathname,
473
+ generatedUrl,
624
474
  reference.parentUrl === rootDirectoryUrl
625
475
  ? buildDirectoryUrl
626
476
  : reference.parentUrl,
@@ -632,26 +482,32 @@ build ${entryPointKeys.length} entry points`)
632
482
  // if a file is in the same directory we could prefer the relative notation
633
483
  // but to keep things simple let's keep the "absolutely relative" to baseUrl for now
634
484
  specifier = `${baseUrl}${urlToRelativeUrl(
635
- urlUntilPathname,
485
+ generatedUrl,
636
486
  buildDirectoryUrl,
637
487
  )}`
638
488
  }
639
- buildUrls[specifier] = reference.generatedUrl
489
+ buildUrls.set(specifier, reference.generatedUrl)
640
490
  return specifier
641
491
  },
642
492
  fetchUrlContent: async (finalUrlInfo, context) => {
643
493
  const fromBundleOrRawGraph = (url) => {
644
494
  const bundleUrlInfo = bundleUrlInfos[url]
645
495
  if (bundleUrlInfo) {
646
- logger.debug(`fetching from bundle ${url}`)
496
+ // logger.debug(`fetching from bundle ${url}`)
647
497
  return bundleUrlInfo
648
498
  }
649
- const rawUrl = rawUrls[url] || url
499
+ const rawUrl = buildToRawUrls[url] || url
650
500
  const rawUrlInfo = rawGraph.getUrlInfo(rawUrl)
651
501
  if (!rawUrlInfo) {
652
- throw new Error(`Cannot find url`)
502
+ throw new Error(
503
+ createDetailedMessage(`Cannot find url`, {
504
+ url,
505
+ "raw urls": Object.values(buildToRawUrls),
506
+ "build urls": Object.keys(buildToRawUrls),
507
+ }),
508
+ )
653
509
  }
654
- logger.debug(`fetching from raw graph ${url}`)
510
+ // logger.debug(`fetching from raw graph ${url}`)
655
511
  if (rawUrlInfo.isInline) {
656
512
  // Inline content, such as <script> inside html, is transformed during the previous phase.
657
513
  // If we read the inline content it would be considered as the original content.
@@ -667,24 +523,28 @@ build ${entryPointKeys.length} entry points`)
667
523
  }
668
524
  return rawUrlInfo
669
525
  }
526
+ const { reference } = context
670
527
  // reference injected during "postbuild":
671
528
  // - happens for "as_js_classic" injecting "s.js"
672
- if (context.reference.injected) {
529
+ if (reference.injected) {
673
530
  const [ref, rawUrlInfo] = rawGraphKitchen.injectReference({
674
- type: context.reference.type,
675
- expectedType: context.reference.expectedType,
676
- expectedSubtype: context.reference.expectedSubtype,
677
- parentUrl: rawUrls[context.reference.parentUrl],
678
- specifier: context.reference.specifier,
531
+ type: reference.type,
532
+ expectedType: reference.expectedType,
533
+ expectedSubtype: reference.expectedSubtype,
534
+ parentUrl: buildToRawUrls[reference.parentUrl],
535
+ specifier: reference.specifier,
679
536
  injected: true,
680
537
  })
681
538
  await rawGraphKitchen.cook(rawUrlInfo, { reference: ref })
682
539
  return rawUrlInfo
683
540
  }
541
+ if (reference.isInline) {
542
+ return fromBundleOrRawGraph(reference.url)
543
+ }
684
544
  // reference updated during "postbuild":
685
545
  // - happens for "as_js_classic"
686
- if (context.reference.original) {
687
- return fromBundleOrRawGraph(context.reference.original.url)
546
+ if (reference.original) {
547
+ return fromBundleOrRawGraph(reference.original.url)
688
548
  }
689
549
  return fromBundleOrRawGraph(finalUrlInfo.url)
690
550
  },
@@ -707,115 +567,750 @@ build ${entryPointKeys.length} entry points`)
707
567
  },
708
568
  },
709
569
  ],
570
+ sourcemaps,
571
+ sourcemapsSourcesContent,
572
+ sourcemapsRelativeSources: !versioning,
573
+ writeGeneratedFiles,
574
+ outDirectoryUrl: new URL(".jsenv/postbuild/", rootDirectoryUrl),
710
575
  })
711
- const buildTask = createTaskLog("build", { disabled: infoLogsAreDisabled })
712
- const postBuildEntryUrls = []
713
- try {
714
- await loadUrlGraph({
715
- operation: buildOperation,
716
- urlGraph: finalGraph,
717
- kitchen: finalGraphKitchen,
718
- outDirectoryUrl: new URL(".jsenv/postbuild/", rootDirectoryUrl),
719
- writeGeneratedFiles,
720
- skipResourceHint: true,
721
- startLoading: (cookEntryFile) => {
722
- entryUrls.forEach((entryUrl) => {
723
- const [, postBuildEntryUrlInfo] = cookEntryFile({
724
- trace: { message: `entryPoint` },
576
+ const finalEntryUrls = []
577
+
578
+ craft: {
579
+ const loadTask = createTaskLog("load", {
580
+ disabled: logger.levels.debug || !logger.levels.info,
581
+ })
582
+ try {
583
+ if (writeGeneratedFiles) {
584
+ await ensureEmptyDirectory(new URL(`.jsenv/build/`, rootDirectoryUrl))
585
+ }
586
+ const rawUrlGraphLoader = createUrlGraphLoader(
587
+ rawGraphKitchen.kitchenContext,
588
+ )
589
+ Object.keys(entryPoints).forEach((key) => {
590
+ const [entryReference, entryUrlInfo] =
591
+ rawGraphKitchen.kitchenContext.prepareEntryPoint({
592
+ trace: { message: `"${key}" in entryPoints parameter` },
593
+ parentUrl: rootDirectoryUrl,
725
594
  type: "entry_point",
726
- specifier: entryUrl,
595
+ specifier: key,
727
596
  })
728
- postBuildEntryUrls.push(postBuildEntryUrlInfo.url)
729
- })
730
- },
731
- })
732
- } catch (e) {
733
- buildTask.fail()
734
- throw e
597
+ entryUrls.push(entryUrlInfo.url)
598
+ entryUrlInfo.filename = entryPoints[key]
599
+ rawUrlGraphLoader.load(entryUrlInfo, { reference: entryReference })
600
+ })
601
+ await rawUrlGraphLoader.getAllLoadDonePromise(buildOperation)
602
+ } catch (e) {
603
+ loadTask.fail()
604
+ throw e
605
+ }
606
+ loadTask.done()
735
607
  }
736
- buildTask.done()
737
608
 
738
- logger.debug(
739
- `graph urls pre-versioning:
740
- ${Array.from(finalGraph.urlInfoMap.keys()).join("\n")}`,
741
- )
742
- if (versioning) {
743
- await applyUrlVersioning({
744
- buildOperation,
745
- logger,
746
- infoLogsAreDisabled,
747
- buildDirectoryUrl,
748
- rawUrls,
749
- buildUrls,
750
- baseUrl,
751
- postBuildEntryUrls,
752
- sourcemaps,
753
- sourcemapsSourcesContent,
754
- runtimeCompat,
755
- writeGeneratedFiles,
756
- rawGraph,
757
- urlAnalysisPlugin,
758
- finalGraph,
759
- finalGraphKitchen,
760
- lineBreakNormalization,
761
- versioningMethod,
762
- })
763
- }
764
- GRAPH.forEach(finalGraph, (urlInfo) => {
765
- if (!urlInfo.shouldHandle) {
766
- return
609
+ shape: {
610
+ bundle: {
611
+ rawGraphKitchen.pluginController.plugins.forEach((plugin) => {
612
+ const bundle = plugin.bundle
613
+ if (!bundle) {
614
+ return
615
+ }
616
+ if (typeof bundle !== "object") {
617
+ throw new Error(
618
+ `bundle must be an object, found "${bundle}" on plugin named "${plugin.name}"`,
619
+ )
620
+ }
621
+ Object.keys(bundle).forEach((type) => {
622
+ const bundleFunction = bundle[type]
623
+ if (!bundleFunction) {
624
+ return
625
+ }
626
+ const bundlerForThatType = bundlers[type]
627
+ if (bundlerForThatType) {
628
+ // first plugin to define a bundle hook wins
629
+ return
630
+ }
631
+ bundlers[type] = {
632
+ plugin,
633
+ bundleFunction: bundle[type],
634
+ urlInfos: [],
635
+ }
636
+ })
637
+ })
638
+ const addToBundlerIfAny = (rawUrlInfo) => {
639
+ const bundler = bundlers[rawUrlInfo.type]
640
+ if (bundler) {
641
+ bundler.urlInfos.push(rawUrlInfo)
642
+ return
643
+ }
644
+ }
645
+ GRAPH.forEach(rawGraph, (rawUrlInfo) => {
646
+ if (rawUrlInfo.isEntryPoint) {
647
+ addToBundlerIfAny(rawUrlInfo)
648
+ if (rawUrlInfo.type === "html") {
649
+ rawUrlInfo.dependencies.forEach((dependencyUrl) => {
650
+ const dependencyUrlInfo = rawGraph.getUrlInfo(dependencyUrl)
651
+ if (dependencyUrlInfo.isInline) {
652
+ if (dependencyUrlInfo.type === "js_module") {
653
+ // bundle inline script type module deps
654
+ dependencyUrlInfo.references.forEach((inlineScriptRef) => {
655
+ if (inlineScriptRef.type === "js_import_export") {
656
+ const inlineUrlInfo = rawGraph.getUrlInfo(
657
+ inlineScriptRef.url,
658
+ )
659
+ addToBundlerIfAny(inlineUrlInfo)
660
+ }
661
+ })
662
+ }
663
+ // inline content cannot be bundled
664
+ return
665
+ }
666
+ addToBundlerIfAny(dependencyUrlInfo)
667
+ })
668
+ rawUrlInfo.references.forEach((reference) => {
669
+ if (
670
+ reference.isResourceHint &&
671
+ reference.expectedType === "js_module"
672
+ ) {
673
+ const referencedUrlInfo = rawGraph.getUrlInfo(reference.url)
674
+ if (
675
+ referencedUrlInfo &&
676
+ // something else than the resource hint is using this url
677
+ referencedUrlInfo.dependents.size > 0
678
+ ) {
679
+ addToBundlerIfAny(referencedUrlInfo)
680
+ }
681
+ }
682
+ })
683
+ return
684
+ }
685
+ }
686
+ // File referenced with new URL('./file.js', import.meta.url)
687
+ // are entry points that can be bundled
688
+ // For instance we will bundle service worker/workers detected like this
689
+ if (rawUrlInfo.type === "js_module") {
690
+ rawUrlInfo.references.forEach((reference) => {
691
+ if (reference.type === "js_url_specifier") {
692
+ const urlInfo = rawGraph.getUrlInfo(reference.url)
693
+ addToBundlerIfAny(urlInfo)
694
+ }
695
+ })
696
+ }
697
+ })
698
+ await Object.keys(bundlers).reduce(async (previous, type) => {
699
+ await previous
700
+ const bundler = bundlers[type]
701
+ const urlInfosToBundle = bundler.urlInfos
702
+ if (urlInfosToBundle.length === 0) {
703
+ return
704
+ }
705
+ const bundleTask = createTaskLog(`bundle "${type}"`, {
706
+ disabled: logger.levels.debug || !logger.levels.info,
707
+ })
708
+ try {
709
+ const bundlerGeneratedUrlInfos =
710
+ await rawGraphKitchen.pluginController.callAsyncHook(
711
+ {
712
+ plugin: bundler.plugin,
713
+ hookName: "bundle",
714
+ value: bundler.bundleFunction,
715
+ },
716
+ urlInfosToBundle,
717
+ {
718
+ ...rawGraphKitchen.kitchenContext,
719
+ buildDirectoryUrl,
720
+ },
721
+ )
722
+ Object.keys(bundlerGeneratedUrlInfos).forEach((url) => {
723
+ const rawUrlInfo = rawGraph.getUrlInfo(url)
724
+ const bundlerGeneratedUrlInfo = bundlerGeneratedUrlInfos[url]
725
+ const bundleUrlInfo = {
726
+ type,
727
+ subtype: rawUrlInfo ? rawUrlInfo.subtype : undefined,
728
+ isEntryPoint: rawUrlInfo ? rawUrlInfo.isEntryPoint : undefined,
729
+ filename: rawUrlInfo ? rawUrlInfo.filename : undefined,
730
+ originalUrl: rawUrlInfo ? rawUrlInfo.originalUrl : undefined,
731
+ originalContent: rawUrlInfo
732
+ ? rawUrlInfo.originalContent
733
+ : undefined,
734
+ ...bundlerGeneratedUrlInfo,
735
+ data: {
736
+ ...(rawUrlInfo ? rawUrlInfo.data : {}),
737
+ ...bundlerGeneratedUrlInfo.data,
738
+ fromBundle: true,
739
+ },
740
+ }
741
+ if (bundlerGeneratedUrlInfo.sourceUrls) {
742
+ bundlerGeneratedUrlInfo.sourceUrls.forEach((sourceUrl) => {
743
+ const rawUrlInfo = rawGraph.getUrlInfo(sourceUrl)
744
+ if (rawUrlInfo) {
745
+ rawUrlInfo.data.bundled = true
746
+ }
747
+ })
748
+ }
749
+ const buildUrl = buildUrlsGenerator.generate(url, {
750
+ urlInfo: bundleUrlInfo,
751
+ })
752
+ bundleRedirections.set(url, buildUrl)
753
+ if (urlIsInsideOf(url, buildDirectoryUrl)) {
754
+ // chunk generated by rollup to share code
755
+ } else {
756
+ associateBuildUrlAndRawUrl(buildUrl, url, "bundle")
757
+ }
758
+ bundleUrlInfos[buildUrl] = bundleUrlInfo
759
+ if (buildUrl.includes("?")) {
760
+ bundleUrlInfos[asUrlWithoutSearch(buildUrl)] = bundleUrlInfo
761
+ }
762
+ if (bundlerGeneratedUrlInfo.data.bundleRelativeUrl) {
763
+ const urlForBundler = new URL(
764
+ bundlerGeneratedUrlInfo.data.bundleRelativeUrl,
765
+ buildDirectoryUrl,
766
+ ).href
767
+ if (urlForBundler !== buildUrl) {
768
+ bundleInternalRedirections.set(urlForBundler, buildUrl)
769
+ }
770
+ }
771
+ })
772
+ } catch (e) {
773
+ bundleTask.fail()
774
+ throw e
775
+ }
776
+ bundleTask.done()
777
+ }, Promise.resolve())
767
778
  }
768
- if (!urlInfo.url.startsWith("file:")) {
769
- return
779
+ reload_in_build_directory: {
780
+ const buildTask = createTaskLog("build", {
781
+ disabled: logger.levels.debug || !logger.levels.info,
782
+ })
783
+ try {
784
+ if (writeGeneratedFiles) {
785
+ await ensureEmptyDirectory(
786
+ new URL(`.jsenv/postbuild/`, rootDirectoryUrl),
787
+ )
788
+ }
789
+ const finalUrlGraphLoader = createUrlGraphLoader(
790
+ finalGraphKitchen.kitchenContext,
791
+ )
792
+ entryUrls.forEach((entryUrl) => {
793
+ const [finalEntryReference, finalEntryUrlInfo] =
794
+ finalGraphKitchen.kitchenContext.prepareEntryPoint({
795
+ trace: { message: `entryPoint` },
796
+ parentUrl: rootDirectoryUrl,
797
+ type: "entry_point",
798
+ specifier: entryUrl,
799
+ })
800
+ finalEntryUrls.push(finalEntryUrlInfo.url)
801
+ finalUrlGraphLoader.load(finalEntryUrlInfo, {
802
+ reference: finalEntryReference,
803
+ })
804
+ })
805
+ await finalUrlGraphLoader.getAllLoadDonePromise(buildOperation)
806
+ } catch (e) {
807
+ buildTask.fail()
808
+ throw e
809
+ }
810
+ buildTask.done()
811
+ }
812
+ }
813
+
814
+ refine: {
815
+ inject_version_in_urls: {
816
+ if (!versioning) {
817
+ break inject_version_in_urls
818
+ }
819
+ const versioningTask = createTaskLog("inject version in urls", {
820
+ disabled: logger.levels.debug || !logger.levels.info,
821
+ })
822
+ try {
823
+ const urlsSorted = sortByDependencies(finalGraph.toObject())
824
+ urlsSorted.forEach((url) => {
825
+ if (url.startsWith("data:")) {
826
+ return
827
+ }
828
+ const urlInfo = finalGraph.getUrlInfo(url)
829
+ if (urlInfo.type === "sourcemap") {
830
+ return
831
+ }
832
+ // ignore:
833
+ // - inline files:
834
+ // they are already taken into account in the file where they appear
835
+ // - ignored files:
836
+ // we don't know their content
837
+ // - unused files without reference
838
+ // File updated such as style.css -> style.css.js or file.js->file.nomodule.js
839
+ // Are used at some point just to be discarded later because they need to be converted
840
+ // There is no need to version them and we could not because the file have been ignored
841
+ // so their content is unknown
842
+ if (urlInfo.isInline) {
843
+ return
844
+ }
845
+ if (!urlInfo.shouldHandle) {
846
+ return
847
+ }
848
+ if (!urlInfo.isEntryPoint && urlInfo.dependents.size === 0) {
849
+ return
850
+ }
851
+ const urlContent =
852
+ urlInfo.type === "html"
853
+ ? stringifyHtmlAst(
854
+ parseHtmlString(urlInfo.content, {
855
+ storeOriginalPositions: false,
856
+ }),
857
+ { cleanupJsenvAttributes: true },
858
+ )
859
+ : urlInfo.content
860
+ const versionGenerator = createVersionGenerator()
861
+ versionGenerator.augmentWithContent({
862
+ content: urlContent,
863
+ contentType: urlInfo.contentType,
864
+ lineBreakNormalization,
865
+ })
866
+ urlInfo.dependencies.forEach((dependencyUrl) => {
867
+ // this dependency is inline
868
+ if (dependencyUrl.startsWith("data:")) {
869
+ return
870
+ }
871
+ const dependencyUrlInfo = finalGraph.getUrlInfo(dependencyUrl)
872
+ if (
873
+ // this content is part of the file, no need to take into account twice
874
+ dependencyUrlInfo.isInline ||
875
+ // this dependency content is not known
876
+ !dependencyUrlInfo.shouldHandle
877
+ ) {
878
+ return
879
+ }
880
+ if (dependencyUrlInfo.data.version) {
881
+ versionGenerator.augmentWithDependencyVersion(
882
+ dependencyUrlInfo.data.version,
883
+ )
884
+ } else {
885
+ // because all dependencies are know, if the dependency has no version
886
+ // it means there is a circular dependency between this file
887
+ // and it's dependency
888
+ // in that case we'll use the dependency content
889
+ versionGenerator.augmentWithContent({
890
+ content: dependencyUrlInfo.content,
891
+ contentType: dependencyUrlInfo.contentType,
892
+ lineBreakNormalization,
893
+ })
894
+ }
895
+ })
896
+ urlInfo.data.version = versionGenerator.generate()
897
+
898
+ const buildUrlObject = new URL(urlInfo.url)
899
+ // remove ?as_js_classic as
900
+ // this information is already hold into ".nomodule"
901
+ buildUrlObject.searchParams.delete("as_js_classic")
902
+ buildUrlObject.searchParams.delete("as_js_classic_library")
903
+ buildUrlObject.searchParams.delete("as_json_module")
904
+ buildUrlObject.searchParams.delete("as_css_module")
905
+ buildUrlObject.searchParams.delete("as_text_module")
906
+ const buildUrl = buildUrlObject.href
907
+ finalRedirections.set(urlInfo.url, buildUrl)
908
+ urlInfo.data.versionedUrl = normalizeUrl(
909
+ injectVersionIntoBuildUrl({
910
+ buildUrl,
911
+ version: urlInfo.data.version,
912
+ versioningMethod,
913
+ }),
914
+ )
915
+ })
916
+ const versionMappings = {}
917
+ const usedVersionMappings = new Set()
918
+ const versioningKitchen = createKitchen({
919
+ logLevel: logger.level,
920
+ rootDirectoryUrl: buildDirectoryUrl,
921
+ urlGraph: finalGraph,
922
+ scenarios: { build: true },
923
+ runtimeCompat,
924
+ plugins: [
925
+ urlAnalysisPlugin,
926
+ jsenvPluginInline({
927
+ fetchInlineUrls: false,
928
+ analyzeConvertedScripts: true, // to be able to version their urls
929
+ allowEscapeForVersioning: true,
930
+ }),
931
+ {
932
+ name: "jsenv:versioning",
933
+ appliesDuring: "build",
934
+ resolveUrl: (reference) => {
935
+ const buildUrl = buildUrls.get(reference.specifier)
936
+ if (buildUrl) {
937
+ return buildUrl
938
+ }
939
+ const urlObject = new URL(
940
+ reference.specifier,
941
+ reference.baseUrl || reference.parentUrl,
942
+ )
943
+ const url = urlObject.href
944
+ // during versioning we revisit the deps
945
+ // but the code used to enforce trailing slash on directories
946
+ // is not applied because "jsenv:file_url_resolution" is not used
947
+ // so here we search if the url with a trailing slash exists
948
+ if (
949
+ reference.type === "filesystem" &&
950
+ !urlObject.pathname.endsWith("/")
951
+ ) {
952
+ const urlWithTrailingSlash = `${url}/`
953
+ const specifier = findKey(buildUrls, urlWithTrailingSlash)
954
+ if (specifier) {
955
+ return urlWithTrailingSlash
956
+ }
957
+ }
958
+ return url
959
+ },
960
+ formatUrl: (reference) => {
961
+ if (!reference.shouldHandle) {
962
+ if (reference.generatedUrl.startsWith("ignore:")) {
963
+ return reference.generatedUrl.slice("ignore:".length)
964
+ }
965
+ return null
966
+ }
967
+ if (reference.isInline || reference.url.startsWith("data:")) {
968
+ return null
969
+ }
970
+ if (reference.isResourceHint) {
971
+ return null
972
+ }
973
+ // specifier comes from "normalize" hook done a bit earlier in this file
974
+ // we want to get back their build url to access their infos
975
+ const referencedUrlInfo = finalGraph.getUrlInfo(reference.url)
976
+ if (!canUseVersionedUrl(referencedUrlInfo)) {
977
+ return reference.specifier
978
+ }
979
+ if (!referencedUrlInfo.shouldHandle) {
980
+ return null
981
+ }
982
+ const versionedUrl = referencedUrlInfo.data.versionedUrl
983
+ if (!versionedUrl) {
984
+ // happens for sourcemap
985
+ return `${baseUrl}${urlToRelativeUrl(
986
+ referencedUrlInfo.url,
987
+ buildDirectoryUrl,
988
+ )}`
989
+ }
990
+ const versionedSpecifier = `${baseUrl}${urlToRelativeUrl(
991
+ versionedUrl,
992
+ buildDirectoryUrl,
993
+ )}`
994
+ versionMappings[reference.specifier] = versionedSpecifier
995
+ versioningRedirections.set(reference.url, versionedUrl)
996
+ buildUrls.set(versionedSpecifier, versionedUrl)
997
+
998
+ const parentUrlInfo = finalGraph.getUrlInfo(
999
+ reference.parentUrl,
1000
+ )
1001
+ if (parentUrlInfo.jsQuote) {
1002
+ // the url is inline inside js quotes
1003
+ usedVersionMappings.add(reference.specifier)
1004
+ return () =>
1005
+ `${parentUrlInfo.jsQuote}+__v__(${JSON.stringify(
1006
+ reference.specifier,
1007
+ )})+${parentUrlInfo.jsQuote}`
1008
+ }
1009
+ if (
1010
+ reference.type === "js_url_specifier" ||
1011
+ reference.subtype === "import_dynamic"
1012
+ ) {
1013
+ usedVersionMappings.add(reference.specifier)
1014
+ return () => `__v__(${JSON.stringify(reference.specifier)})`
1015
+ }
1016
+ return versionedSpecifier
1017
+ },
1018
+ fetchUrlContent: (versionedUrlInfo) => {
1019
+ if (versionedUrlInfo.isInline) {
1020
+ const rawUrlInfo = rawGraph.getUrlInfo(
1021
+ buildToRawUrls[versionedUrlInfo.url],
1022
+ )
1023
+ const finalUrlInfo = finalGraph.getUrlInfo(
1024
+ versionedUrlInfo.url,
1025
+ )
1026
+ return {
1027
+ content: versionedUrlInfo.content,
1028
+ contentType: versionedUrlInfo.contentType,
1029
+ originalContent: rawUrlInfo
1030
+ ? rawUrlInfo.originalContent
1031
+ : undefined,
1032
+ sourcemap: finalUrlInfo
1033
+ ? finalUrlInfo.sourcemap
1034
+ : undefined,
1035
+ }
1036
+ }
1037
+ return versionedUrlInfo
1038
+ },
1039
+ },
1040
+ ],
1041
+ sourcemaps,
1042
+ sourcemapsSourcesContent,
1043
+ sourcemapsRelativeSources: true,
1044
+ writeGeneratedFiles,
1045
+ outDirectoryUrl: new URL(
1046
+ ".jsenv/postbuild/",
1047
+ finalGraphKitchen.rootDirectoryUrl,
1048
+ ),
1049
+ })
1050
+ const versioningUrlGraphLoader = createUrlGraphLoader(
1051
+ versioningKitchen.kitchenContext,
1052
+ )
1053
+ finalEntryUrls.forEach((finalEntryUrl) => {
1054
+ const [finalEntryReference, finalEntryUrlInfo] =
1055
+ finalGraphKitchen.kitchenContext.prepareEntryPoint({
1056
+ trace: { message: `entryPoint` },
1057
+ parentUrl: buildDirectoryUrl,
1058
+ type: "entry_point",
1059
+ specifier: finalEntryUrl,
1060
+ })
1061
+ versioningUrlGraphLoader.load(finalEntryUrlInfo, {
1062
+ reference: finalEntryReference,
1063
+ })
1064
+ })
1065
+ await versioningUrlGraphLoader.getAllLoadDonePromise(buildOperation)
1066
+ if (usedVersionMappings.size) {
1067
+ const versionMappingsNeeded = {}
1068
+ usedVersionMappings.forEach((specifier) => {
1069
+ versionMappingsNeeded[specifier] = versionMappings[specifier]
1070
+ })
1071
+ const actions = []
1072
+ GRAPH.forEach(finalGraph, (urlInfo) => {
1073
+ if (urlInfo.isEntryPoint) {
1074
+ actions.push(async () => {
1075
+ await injectVersionMappings({
1076
+ urlInfo,
1077
+ kitchen: finalGraphKitchen,
1078
+ versionMappings: versionMappingsNeeded,
1079
+ })
1080
+ })
1081
+ }
1082
+ })
1083
+ await Promise.all(actions.map((action) => action()))
1084
+ }
1085
+ } catch (e) {
1086
+ versioningTask.fail()
1087
+ throw e
1088
+ }
1089
+ versioningTask.done()
770
1090
  }
771
- if (urlInfo.type === "html") {
772
- const htmlAst = parseHtmlString(urlInfo.content, {
773
- storeOriginalPositions: false,
1091
+ cleanup_jsenv_attributes_from_html: {
1092
+ GRAPH.forEach(finalGraph, (urlInfo) => {
1093
+ if (!urlInfo.shouldHandle) {
1094
+ return
1095
+ }
1096
+ if (!urlInfo.url.startsWith("file:")) {
1097
+ return
1098
+ }
1099
+ if (urlInfo.type === "html") {
1100
+ const htmlAst = parseHtmlString(urlInfo.content, {
1101
+ storeOriginalPositions: false,
1102
+ })
1103
+ urlInfo.content = stringifyHtmlAst(htmlAst, {
1104
+ cleanupJsenvAttributes: true,
1105
+ })
1106
+ }
774
1107
  })
775
- urlInfo.content = stringifyHtmlAst(htmlAst, {
776
- removeOriginalPositionAttributes: true,
1108
+ }
1109
+ /*
1110
+ * Update <link rel="preload"> and friends after build (once we know everything)
1111
+ * - Used to remove resource hint targeting an url that is no longer used:
1112
+ * - Happens because of import assertions transpilation (file is inlined into JS)
1113
+ */
1114
+ resync_resource_hints: {
1115
+ const actions = []
1116
+ GRAPH.forEach(finalGraph, (urlInfo) => {
1117
+ if (urlInfo.type !== "html") {
1118
+ return
1119
+ }
1120
+ actions.push(async () => {
1121
+ const htmlAst = parseHtmlString(urlInfo.content, {
1122
+ storeOriginalPositions: false,
1123
+ })
1124
+ const mutations = []
1125
+ visitHtmlNodes(htmlAst, {
1126
+ link: (node) => {
1127
+ const href = getHtmlNodeAttribute(node, "href")
1128
+ if (href === undefined || href.startsWith("data:")) {
1129
+ return
1130
+ }
1131
+ const rel = getHtmlNodeAttribute(node, "rel")
1132
+ const isResourceHint = [
1133
+ "preconnect",
1134
+ "dns-prefetch",
1135
+ "prefetch",
1136
+ "preload",
1137
+ "modulepreload",
1138
+ ].includes(rel)
1139
+ if (!isResourceHint) {
1140
+ return
1141
+ }
1142
+ const onBuildUrl = (buildUrl) => {
1143
+ const buildUrlInfo = buildUrl
1144
+ ? finalGraph.getUrlInfo(buildUrl)
1145
+ : null
1146
+ if (!buildUrlInfo) {
1147
+ logger.warn(
1148
+ `remove resource hint because cannot find "${href}" in the graph`,
1149
+ )
1150
+ mutations.push(() => {
1151
+ removeHtmlNode(node)
1152
+ })
1153
+ return
1154
+ }
1155
+ if (buildUrlInfo.dependents.size === 0) {
1156
+ logger.info(
1157
+ `remove resource hint because "${href}" not used anymore`,
1158
+ )
1159
+ mutations.push(() => {
1160
+ removeHtmlNode(node)
1161
+ })
1162
+ return
1163
+ }
1164
+ const buildUrlFormatted =
1165
+ versioningRedirections.get(buildUrlInfo.url) ||
1166
+ buildUrlInfo.url
1167
+ const buildSpecifierBeforeRedirect = findKey(
1168
+ buildUrls,
1169
+ buildUrlFormatted,
1170
+ )
1171
+ mutations.push(() => {
1172
+ setHtmlNodeAttributes(node, {
1173
+ href: buildSpecifierBeforeRedirect,
1174
+ crossorigin: undefined,
1175
+ })
1176
+ })
1177
+ }
1178
+ if (href.startsWith("file:")) {
1179
+ let url = href
1180
+ url = rawRedirections.get(url) || url
1181
+ const rawUrlInfo = rawGraph.getUrlInfo(url)
1182
+ if (rawUrlInfo && rawUrlInfo.data.bundled) {
1183
+ logger.info(
1184
+ `remove resource hint on "${href}" because it was bundled`,
1185
+ )
1186
+ mutations.push(() => {
1187
+ removeHtmlNode(node)
1188
+ })
1189
+ } else {
1190
+ url = bundleRedirections.get(url) || url
1191
+ url = bundleInternalRedirections.get(url) || url
1192
+ url = finalRedirections.get(url) || url
1193
+ onBuildUrl(url)
1194
+ }
1195
+ } else {
1196
+ onBuildUrl(null)
1197
+ }
1198
+ },
1199
+ })
1200
+ if (mutations.length > 0) {
1201
+ mutations.forEach((mutation) => mutation())
1202
+ await finalGraphKitchen.urlInfoTransformer.applyFinalTransformations(
1203
+ urlInfo,
1204
+ {
1205
+ content: stringifyHtmlAst(htmlAst),
1206
+ },
1207
+ )
1208
+ }
1209
+ })
777
1210
  })
1211
+ await Promise.all(
1212
+ actions.map((resourceHintAction) => resourceHintAction()),
1213
+ )
1214
+ buildOperation.throwIfAborted()
778
1215
  }
779
- const version = urlInfo.data.version
780
- const useVersionedUrl = version && canUseVersionedUrl(urlInfo, finalGraph)
781
- const buildUrl = useVersionedUrl ? urlInfo.data.versionedUrl : urlInfo.url
782
- const buildUrlSpecifier = Object.keys(buildUrls).find(
783
- (key) => buildUrls[key] === buildUrl,
784
- )
785
- urlInfo.data.buildUrl = buildUrl
786
- urlInfo.data.buildUrlIsVersioned = useVersionedUrl
787
- urlInfo.data.buildUrlSpecifier = buildUrlSpecifier
788
- })
789
- await resyncResourceHints({
790
- logger,
791
- finalGraphKitchen,
792
- finalGraph,
793
- rawUrls,
794
- postBuildRedirections,
795
- })
796
- buildOperation.throwIfAborted()
797
- const cleanupActions = []
798
- GRAPH.forEach(finalGraph, (urlInfo) => {
799
- // nothing uses this url anymore
800
- // - versioning update inline content
801
- // - file converted for import assertion of js_classic conversion
802
- if (
803
- !urlInfo.isEntryPoint &&
804
- urlInfo.type !== "sourcemap" &&
805
- urlInfo.dependents.size === 0
806
- ) {
807
- cleanupActions.push(() => {
808
- finalGraph.deleteUrlInfo(urlInfo.url)
1216
+ delete_unused_urls: {
1217
+ const actions = []
1218
+ GRAPH.forEach(finalGraph, (urlInfo) => {
1219
+ // nothing uses this url anymore
1220
+ // - versioning update inline content
1221
+ // - file converted for import assertion or js_classic conversion
1222
+ if (
1223
+ !urlInfo.isEntryPoint &&
1224
+ urlInfo.type !== "sourcemap" &&
1225
+ urlInfo.dependents.size === 0 &&
1226
+ !urlInfo.injected // injected during postbuild
1227
+ ) {
1228
+ actions.push(() => {
1229
+ finalGraph.deleteUrlInfo(urlInfo.url)
1230
+ })
1231
+ }
809
1232
  })
1233
+ actions.forEach((action) => action())
810
1234
  }
811
- })
812
- cleanupActions.forEach((cleanupAction) => cleanupAction())
813
- await injectServiceWorkerUrls({
814
- finalGraphKitchen,
815
- finalGraph,
816
- lineBreakNormalization,
817
- })
818
- buildOperation.throwIfAborted()
1235
+ inject_urls_in_service_workers: {
1236
+ const serviceWorkerEntryUrlInfos = GRAPH.filter(
1237
+ finalGraph,
1238
+ (finalUrlInfo) => {
1239
+ return (
1240
+ finalUrlInfo.subtype === "service_worker" &&
1241
+ finalUrlInfo.isEntryPoint
1242
+ )
1243
+ },
1244
+ )
1245
+ if (serviceWorkerEntryUrlInfos.length > 0) {
1246
+ const serviceWorkerUrls = {}
1247
+ GRAPH.forEach(finalGraph, (urlInfo) => {
1248
+ if (urlInfo.isInline || !urlInfo.shouldHandle) {
1249
+ return
1250
+ }
1251
+ if (!urlInfo.url.startsWith("file:")) {
1252
+ return
1253
+ }
1254
+ const versionedUrl = urlInfo.data.versionedUrl
1255
+ if (!versionedUrl) {
1256
+ // when url is not versioned we compute a "version" for that url anyway
1257
+ // so that service worker source still changes and navigator
1258
+ // detect there is a change
1259
+ const versionGenerator = createVersionGenerator()
1260
+ versionGenerator.augmentWithContent({
1261
+ content: urlInfo.content,
1262
+ contentType: urlInfo.contentType,
1263
+ lineBreakNormalization,
1264
+ })
1265
+ const version = versionGenerator.generate()
1266
+ urlInfo.data.version = version
1267
+ const specifier = findKey(buildUrls, urlInfo.url)
1268
+ serviceWorkerUrls[specifier] = { versioned: false, version }
1269
+ return
1270
+ }
1271
+ if (!canUseVersionedUrl(urlInfo)) {
1272
+ const specifier = findKey(buildUrls, urlInfo.url)
1273
+ serviceWorkerUrls[specifier] = {
1274
+ versioned: false,
1275
+ version: urlInfo.data.version,
1276
+ }
1277
+ return
1278
+ }
1279
+ const versionedSpecifier = findKey(buildUrls, versionedUrl)
1280
+ serviceWorkerUrls[versionedSpecifier] = { versioned: true }
1281
+ })
1282
+ serviceWorkerEntryUrlInfos.forEach((serviceWorkerEntryUrlInfo) => {
1283
+ const magicSource = createMagicSource(
1284
+ serviceWorkerEntryUrlInfo.content,
1285
+ )
1286
+ const urlsWithoutSelf = {
1287
+ ...serviceWorkerUrls,
1288
+ }
1289
+ const serviceWorkerSpecifier = findKey(
1290
+ buildUrls,
1291
+ serviceWorkerEntryUrlInfo.url,
1292
+ )
1293
+ delete urlsWithoutSelf[serviceWorkerSpecifier]
1294
+ magicSource.prepend(
1295
+ `\nself.serviceWorkerUrls = ${JSON.stringify(
1296
+ urlsWithoutSelf,
1297
+ null,
1298
+ " ",
1299
+ )};\n`,
1300
+ )
1301
+ const { content, sourcemap } = magicSource.toContentAndSourcemap()
1302
+ finalGraphKitchen.urlInfoTransformer.applyFinalTransformations(
1303
+ serviceWorkerEntryUrlInfo,
1304
+ {
1305
+ content,
1306
+ sourcemap,
1307
+ },
1308
+ )
1309
+ })
1310
+ }
1311
+ buildOperation.throwIfAborted()
1312
+ }
1313
+ }
819
1314
 
820
1315
  const buildManifest = {}
821
1316
  const buildFileContents = {}
@@ -830,19 +1325,32 @@ ${Array.from(finalGraph.urlInfoMap.keys()).join("\n")}`,
830
1325
  if (urlInfo.type === "directory") {
831
1326
  return
832
1327
  }
833
- const buildRelativeUrl = urlToRelativeUrl(
834
- urlInfo.data.buildUrl,
835
- buildDirectoryUrl,
836
- )
837
1328
  if (urlInfo.isInline) {
838
- buildInlineContents[buildRelativeUrl] = urlInfo.content
839
- } else {
840
- buildFileContents[buildRelativeUrl] = urlInfo.content
841
- const buildRelativeUrlWithoutVersioning = urlToRelativeUrl(
1329
+ const buildRelativeUrl = urlToRelativeUrl(
842
1330
  urlInfo.url,
843
1331
  buildDirectoryUrl,
844
1332
  )
845
- buildManifest[buildRelativeUrlWithoutVersioning] = buildRelativeUrl
1333
+ buildInlineContents[buildRelativeUrl] = urlInfo.content
1334
+ } else {
1335
+ const versionedUrl = urlInfo.data.versionedUrl
1336
+ if (versionedUrl && canUseVersionedUrl(urlInfo)) {
1337
+ const buildRelativeUrl = urlToRelativeUrl(
1338
+ urlInfo.url,
1339
+ buildDirectoryUrl,
1340
+ )
1341
+ const versionedBuildRelativeUrl = urlToRelativeUrl(
1342
+ versionedUrl,
1343
+ buildDirectoryUrl,
1344
+ )
1345
+ buildFileContents[versionedBuildRelativeUrl] = urlInfo.content
1346
+ buildManifest[buildRelativeUrl] = versionedBuildRelativeUrl
1347
+ } else {
1348
+ const buildRelativeUrl = urlToRelativeUrl(
1349
+ urlInfo.url,
1350
+ buildDirectoryUrl,
1351
+ )
1352
+ buildFileContents[buildRelativeUrl] = urlInfo.content
1353
+ }
846
1354
  }
847
1355
  })
848
1356
  if (writeOnFileSystem) {
@@ -850,16 +1358,14 @@ ${Array.from(finalGraph.urlInfoMap.keys()).join("\n")}`,
850
1358
  await ensureEmptyDirectory(buildDirectoryUrl)
851
1359
  }
852
1360
  const buildRelativeUrls = Object.keys(buildFileContents)
853
- await Promise.all(
854
- buildRelativeUrls.map(async (buildRelativeUrl) => {
855
- await writeFile(
856
- new URL(buildRelativeUrl, buildDirectoryUrl),
857
- buildFileContents[buildRelativeUrl],
858
- )
859
- }),
860
- )
1361
+ buildRelativeUrls.forEach((buildRelativeUrl) => {
1362
+ writeFileSync(
1363
+ new URL(buildRelativeUrl, buildDirectoryUrl),
1364
+ buildFileContents[buildRelativeUrl],
1365
+ )
1366
+ })
861
1367
  if (versioning && assetManifest && Object.keys(buildManifest).length) {
862
- await writeFile(
1368
+ writeFileSync(
863
1369
  new URL(assetManifestFileRelativeUrl, buildDirectoryUrl),
864
1370
  JSON.stringify(buildManifest, null, " "),
865
1371
  )
@@ -948,271 +1454,13 @@ ${Array.from(finalGraph.urlInfoMap.keys()).join("\n")}`,
948
1454
  return stopWatchingClientFiles
949
1455
  }
950
1456
 
951
- const applyUrlVersioning = async ({
952
- buildOperation,
953
- logger,
954
- infoLogsAreDisabled,
955
- buildDirectoryUrl,
956
- rawUrls,
957
- buildUrls,
958
- baseUrl,
959
- postBuildEntryUrls,
960
- sourcemaps,
961
- sourcemapsSourcesContent,
962
- runtimeCompat,
963
- writeGeneratedFiles,
964
- rawGraph,
965
- urlAnalysisPlugin,
966
- finalGraph,
967
- finalGraphKitchen,
968
- lineBreakNormalization,
969
- versioningMethod,
970
- }) => {
971
- const versioningTask = createTaskLog("inject version in urls", {
972
- disabled: infoLogsAreDisabled,
973
- })
974
- try {
975
- const urlsSorted = sortByDependencies(finalGraph.toObject())
976
- urlsSorted.forEach((url) => {
977
- if (url.startsWith("data:")) {
978
- return
979
- }
980
- const urlInfo = finalGraph.getUrlInfo(url)
981
- if (urlInfo.type === "sourcemap") {
982
- return
983
- }
984
- // ignore:
985
- // - inline files:
986
- // they are already taken into account in the file where they appear
987
- // - ignored files:
988
- // we don't know their content
989
- // - unused files without reference
990
- // File updated such as style.css -> style.css.js or file.js->file.nomodule.js
991
- // Are used at some point just to be discarded later because they need to be converted
992
- // There is no need to version them and we could not because the file have been ignored
993
- // so their content is unknown
994
- if (urlInfo.isInline) {
995
- return
996
- }
997
- if (!urlInfo.shouldHandle) {
998
- return
999
- }
1000
- if (!urlInfo.isEntryPoint && urlInfo.dependents.size === 0) {
1001
- return
1002
- }
1003
-
1004
- const urlContent =
1005
- urlInfo.type === "html"
1006
- ? stringifyHtmlAst(
1007
- parseHtmlString(urlInfo.content, {
1008
- storeOriginalPositions: false,
1009
- }),
1010
- { removeOriginalPositionAttributes: true },
1011
- )
1012
- : urlInfo.content
1013
- const versionGenerator = createVersionGenerator()
1014
- versionGenerator.augmentWithContent({
1015
- content: urlContent,
1016
- contentType: urlInfo.contentType,
1017
- lineBreakNormalization,
1018
- })
1019
- urlInfo.dependencies.forEach((dependencyUrl) => {
1020
- // this dependency is inline
1021
- if (dependencyUrl.startsWith("data:")) {
1022
- return
1023
- }
1024
- const dependencyUrlInfo = finalGraph.getUrlInfo(dependencyUrl)
1025
- if (
1026
- // this content is part of the file, no need to take into account twice
1027
- dependencyUrlInfo.isInline ||
1028
- // this dependency content is not known
1029
- !dependencyUrlInfo.shouldHandle
1030
- ) {
1031
- return
1032
- }
1033
- if (dependencyUrlInfo.data.version) {
1034
- versionGenerator.augmentWithDependencyVersion(
1035
- dependencyUrlInfo.data.version,
1036
- )
1037
- } else {
1038
- // because all dependencies are know, if the dependency has no version
1039
- // it means there is a circular dependency between this file
1040
- // and it's dependency
1041
- // in that case we'll use the dependency content
1042
- versionGenerator.augmentWithContent({
1043
- content: dependencyUrlInfo.content,
1044
- contentType: dependencyUrlInfo.contentType,
1045
- lineBreakNormalization,
1046
- })
1047
- }
1048
- })
1049
- urlInfo.data.version = versionGenerator.generate()
1050
-
1051
- urlInfo.data.versionedUrl = normalizeUrl(
1052
- injectVersionIntoBuildUrl({
1053
- buildUrl: urlInfo.url,
1054
- version: urlInfo.data.version,
1055
- versioningMethod,
1056
- }),
1057
- )
1058
- })
1059
- const versionMappings = {}
1060
- const usedVersionMappings = []
1061
- const versioningKitchen = createKitchen({
1062
- logger,
1063
- rootDirectoryUrl: buildDirectoryUrl,
1064
- urlGraph: finalGraph,
1065
- scenarios: { build: true },
1066
- sourcemaps,
1067
- sourcemapsSourcesContent,
1068
- sourcemapsRelativeSources: true,
1069
- runtimeCompat,
1070
- writeGeneratedFiles,
1071
- plugins: [
1072
- urlAnalysisPlugin,
1073
- jsenvPluginInline({
1074
- fetchInlineUrls: false,
1075
- analyzeConvertedScripts: true, // to be able to version their urls
1076
- allowEscapeForVersioning: true,
1077
- }),
1078
- {
1079
- name: "jsenv:versioning",
1080
- appliesDuring: "build",
1081
- resolveUrl: (reference) => {
1082
- const buildUrl = buildUrls[reference.specifier]
1083
- if (buildUrl) {
1084
- return buildUrl
1085
- }
1086
- const urlObject = new URL(
1087
- reference.specifier,
1088
- reference.baseUrl || reference.parentUrl,
1089
- )
1090
- const url = urlObject.href
1091
- // during versioning we revisit the deps
1092
- // but the code used to enforce trailing slash on directories
1093
- // is not applied because "jsenv:file_url_resolution" is not used
1094
- // so here we search if the url with a trailing slash exists
1095
- if (
1096
- reference.type === "filesystem" &&
1097
- !urlObject.pathname.endsWith("/")
1098
- ) {
1099
- const urlWithTrailingSlash = `${url}/`
1100
- const specifier = Object.keys(buildUrls).find(
1101
- (key) => buildUrls[key] === urlWithTrailingSlash,
1102
- )
1103
- if (specifier) {
1104
- return urlWithTrailingSlash
1105
- }
1106
- }
1107
- return url
1108
- },
1109
- formatUrl: (reference) => {
1110
- if (!reference.shouldHandle) {
1111
- if (reference.generatedUrl.startsWith("ignore:")) {
1112
- return reference.generatedUrl.slice("ignore:".length)
1113
- }
1114
- return null
1115
- }
1116
- if (reference.isInline || reference.url.startsWith("data:")) {
1117
- return null
1118
- }
1119
- if (reference.isResourceHint) {
1120
- return null
1121
- }
1122
- // specifier comes from "normalize" hook done a bit earlier in this file
1123
- // we want to get back their build url to access their infos
1124
- const referencedUrlInfo = finalGraph.getUrlInfo(reference.url)
1125
- if (!canUseVersionedUrl(referencedUrlInfo)) {
1126
- return reference.specifier
1127
- }
1128
- if (!referencedUrlInfo.shouldHandle) {
1129
- return null
1130
- }
1131
- const versionedUrl = referencedUrlInfo.data.versionedUrl
1132
- if (!versionedUrl) {
1133
- // happens for sourcemap
1134
- return `${baseUrl}${urlToRelativeUrl(
1135
- referencedUrlInfo.url,
1136
- buildDirectoryUrl,
1137
- )}`
1138
- }
1139
- const versionedSpecifier = `${baseUrl}${urlToRelativeUrl(
1140
- versionedUrl,
1141
- buildDirectoryUrl,
1142
- )}`
1143
- versionMappings[reference.specifier] = versionedSpecifier
1144
- buildUrls[versionedSpecifier] = versionedUrl
1145
-
1146
- const parentUrlInfo = finalGraph.getUrlInfo(reference.parentUrl)
1147
- if (parentUrlInfo.jsQuote) {
1148
- // the url is inline inside js quotes
1149
- usedVersionMappings.push(reference.specifier)
1150
- return () =>
1151
- `${parentUrlInfo.jsQuote}+__v__(${JSON.stringify(
1152
- reference.specifier,
1153
- )})+${parentUrlInfo.jsQuote}`
1154
- }
1155
- if (
1156
- reference.type === "js_url_specifier" ||
1157
- reference.subtype === "import_dynamic"
1158
- ) {
1159
- usedVersionMappings.push(reference.specifier)
1160
- return () => `__v__(${JSON.stringify(reference.specifier)})`
1161
- }
1162
- return versionedSpecifier
1163
- },
1164
- fetchUrlContent: (versionedUrlInfo) => {
1165
- if (versionedUrlInfo.isInline) {
1166
- const rawUrlInfo = rawGraph.getUrlInfo(
1167
- rawUrls[versionedUrlInfo.url],
1168
- )
1169
- const finalUrlInfo = finalGraph.getUrlInfo(versionedUrlInfo.url)
1170
- return {
1171
- content: versionedUrlInfo.content,
1172
- contentType: versionedUrlInfo.contentType,
1173
- originalContent: rawUrlInfo
1174
- ? rawUrlInfo.originalContent
1175
- : undefined,
1176
- sourcemap: finalUrlInfo ? finalUrlInfo.sourcemap : undefined,
1177
- }
1178
- }
1179
- return versionedUrlInfo
1180
- },
1181
- },
1182
- ],
1183
- })
1184
- await loadUrlGraph({
1185
- operation: buildOperation,
1186
- urlGraph: finalGraph,
1187
- kitchen: versioningKitchen,
1188
- skipResourceHint: true,
1189
- writeGeneratedFiles,
1190
- startLoading: (cookEntryFile) => {
1191
- postBuildEntryUrls.forEach((postBuildEntryUrl) => {
1192
- cookEntryFile({
1193
- trace: `entryPoint`,
1194
- type: "entry_point",
1195
- specifier: postBuildEntryUrl,
1196
- })
1197
- })
1198
- },
1199
- })
1200
- if (usedVersionMappings.length) {
1201
- const versionMappingsNeeded = {}
1202
- usedVersionMappings.forEach((specifier) => {
1203
- versionMappingsNeeded[specifier] = versionMappings[specifier]
1204
- })
1205
- await injectGlobalVersionMapping({
1206
- finalGraphKitchen,
1207
- finalGraph,
1208
- versionMappings: versionMappingsNeeded,
1209
- })
1457
+ const findKey = (map, value) => {
1458
+ for (const [keyCandidate, valueCandidate] of map) {
1459
+ if (valueCandidate === value) {
1460
+ return keyCandidate
1210
1461
  }
1211
- } catch (e) {
1212
- versioningTask.fail()
1213
- throw e
1214
1462
  }
1215
- versioningTask.done()
1463
+ return undefined
1216
1464
  }
1217
1465
 
1218
1466
  const injectVersionIntoBuildUrl = ({ buildUrl, version, versioningMethod }) => {