@jsenv/core 37.0.5 → 37.1.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.
@@ -13,7 +13,7 @@ import http from "node:http";
13
13
  import { Readable, Stream, Writable } from "node:stream";
14
14
  import { Http2ServerResponse } from "node:http2";
15
15
  import { lookup } from "node:dns";
16
- import { composeTwoSourcemaps, createMagicSource, SOURCEMAP, generateSourcemapFileUrl, generateSourcemapDataUrl } from "@jsenv/sourcemap";
16
+ import { composeTwoSourcemaps, createMagicSource, generateSourcemapFileUrl, SOURCEMAP, generateSourcemapDataUrl } from "@jsenv/sourcemap";
17
17
  import { injectJsImport, visitJsAstUntil, applyBabelPlugins, parseHtmlString, visitHtmlNodes, getHtmlNodeAttribute, analyzeScriptNode, stringifyHtmlAst, setHtmlNodeAttributes, injectHtmlNodeAsEarlyAsPossible, createHtmlNode, parseJsWithAcorn, parseSrcSet, getHtmlNodeText, getHtmlNodePosition, getHtmlNodeAttributePosition, removeHtmlNodeText, setHtmlNodeText, parseCssUrls, parseJsUrls, findHtmlNode, removeHtmlNode, analyzeLinkNode, injectHtmlNode, insertHtmlNodeAfter } from "@jsenv/ast";
18
18
  import { RUNTIME_COMPAT } from "@jsenv/runtime-compat";
19
19
  import { createRequire } from "node:module";
@@ -5026,6 +5026,7 @@ const getPropertiesFromResource = ({ resource, baseUrl }) => {
5026
5026
 
5027
5027
  return {
5028
5028
  url: String(urlObject),
5029
+ searchParams: urlObject.searchParams,
5029
5030
  pathname,
5030
5031
  resource,
5031
5032
  };
@@ -10455,6 +10456,55 @@ GRAPH_VISITOR.findDependency = (urlInfo, visitor) => {
10455
10456
  return found;
10456
10457
  };
10457
10458
 
10459
+ // This function will be used in "build.js"
10460
+ // by passing rootUrlInfo as first arg
10461
+ // -> this ensure we visit only urls with strong references
10462
+ // because we start from root and ignore weak ref
10463
+ // The alternative would be to iterate on urlInfoMap
10464
+ // and call urlInfo.isUsed() but that would be more expensive
10465
+ GRAPH_VISITOR.forEachUrlInfoStronglyReferenced = (initialUrlInfo, callback) => {
10466
+ const seen = new Set();
10467
+ seen.add(initialUrlInfo);
10468
+ const iterateOnReferences = (urlInfo) => {
10469
+ for (const referenceToOther of urlInfo.referenceToOthersSet) {
10470
+ if (referenceToOther.isWeak) {
10471
+ continue;
10472
+ }
10473
+ if (referenceToOther.gotInlined()) {
10474
+ continue;
10475
+ }
10476
+ const referencedUrlInfo = referenceToOther.urlInfo;
10477
+ if (seen.has(referencedUrlInfo)) {
10478
+ continue;
10479
+ }
10480
+ seen.add(referencedUrlInfo);
10481
+ callback(referencedUrlInfo);
10482
+ iterateOnReferences(referencedUrlInfo);
10483
+ }
10484
+ };
10485
+ iterateOnReferences(initialUrlInfo);
10486
+ seen.clear();
10487
+ };
10488
+
10489
+ const createEventEmitter = () => {
10490
+ const callbackSet = new Set();
10491
+ const on = (callback) => {
10492
+ callbackSet.add(callback);
10493
+ return () => {
10494
+ callbackSet.delete(callback);
10495
+ };
10496
+ };
10497
+ const off = (callback) => {
10498
+ callbackSet.delete(callback);
10499
+ };
10500
+ const emit = (...args) => {
10501
+ callbackSet.forEach((callback) => {
10502
+ callback(...args);
10503
+ });
10504
+ };
10505
+ return { on, off, emit };
10506
+ };
10507
+
10458
10508
  const urlSpecifierEncoding = {
10459
10509
  encode: (reference) => {
10460
10510
  const { generatedSpecifier } = reference;
@@ -11116,6 +11166,10 @@ const createReference = ({
11116
11166
  return implicitReference;
11117
11167
  };
11118
11168
 
11169
+ reference.gotInlined = () => {
11170
+ return !reference.isInline && reference.next && reference.next.isInline;
11171
+ };
11172
+
11119
11173
  reference.remove = () => removeDependency(reference);
11120
11174
 
11121
11175
  // Object.preventExtensions(reference) // useful to ensure all properties are declared here
@@ -11210,7 +11264,6 @@ const canAddOrRemoveReference = (reference) => {
11210
11264
  const applyDependencyRemovalEffects = (reference) => {
11211
11265
  const { ownerUrlInfo } = reference;
11212
11266
  const { referenceToOthersSet } = ownerUrlInfo;
11213
-
11214
11267
  if (reference.isImplicit && !reference.isInline) {
11215
11268
  let hasAnOtherImplicitRef = false;
11216
11269
  for (const referenceToOther of referenceToOthersSet) {
@@ -11242,8 +11295,29 @@ const applyDependencyRemovalEffects = (reference) => {
11242
11295
  const referencedUrlInfo = reference.urlInfo;
11243
11296
  referencedUrlInfo.referenceFromOthersSet.delete(reference);
11244
11297
 
11245
- const firstReferenceFromOther =
11246
- referencedUrlInfo.getFirstReferenceFromOther();
11298
+ let firstReferenceFromOther;
11299
+ for (const referenceFromOther of referencedUrlInfo.referenceFromOthersSet) {
11300
+ if (referenceFromOther.urlInfo !== referencedUrlInfo) {
11301
+ continue;
11302
+ }
11303
+ // Here we want to know if the file is referenced by an other file.
11304
+ // So we want to ignore reference that are created by other means:
11305
+ // - "http_request"
11306
+ // This type of reference is created when client request a file
11307
+ // that we don't know yet
11308
+ // 1. reference(s) to this file are not yet discovered
11309
+ // 2. there is no reference to this file
11310
+ if (referenceFromOther.type === "http_request") {
11311
+ continue;
11312
+ }
11313
+ if (referenceFromOther.gotInlined()) {
11314
+ // the url info was inlined, an other reference is required
11315
+ // to consider the non-inlined urlInfo as used
11316
+ continue;
11317
+ }
11318
+ firstReferenceFromOther = referenceFromOther;
11319
+ break;
11320
+ }
11247
11321
  if (firstReferenceFromOther) {
11248
11322
  // either applying new ref should override old ref
11249
11323
  // or we should first remove effects before adding new ones
@@ -11254,11 +11328,8 @@ const applyDependencyRemovalEffects = (reference) => {
11254
11328
  }
11255
11329
  return false;
11256
11330
  }
11257
- if (reference.type !== "http_request") {
11258
- referencedUrlInfo.deleteFromGraph(reference);
11259
- return true;
11260
- }
11261
- return false;
11331
+ referencedUrlInfo.onDereferenced(reference);
11332
+ return true;
11262
11333
  };
11263
11334
 
11264
11335
  const traceFromUrlSite = (urlSite) => {
@@ -11371,8 +11442,8 @@ const createUrlGraph = ({
11371
11442
  name = "anonymous",
11372
11443
  }) => {
11373
11444
  const urlGraph = {};
11374
- const createUrlInfoCallbackRef = { current: () => {} };
11375
- const pruneUrlInfoCallbackRef = { current: () => {} };
11445
+ const urlInfoCreatedEventEmitter = createEventEmitter();
11446
+ const urlInfoDereferencedEventEmitter = createEventEmitter();
11376
11447
 
11377
11448
  const urlInfoMap = new Map();
11378
11449
  const hasUrlInfo = (key) => {
@@ -11393,27 +11464,7 @@ const createUrlGraph = ({
11393
11464
  }
11394
11465
  return null;
11395
11466
  };
11396
- const deleteUrlInfo = (url, lastReferenceFromOther) => {
11397
- const urlInfo = urlInfoMap.get(url);
11398
- if (urlInfo) {
11399
- urlInfo.kitchen.urlInfoTransformer.resetContent(urlInfo);
11400
- urlInfoMap.delete(url);
11401
- urlInfo.modifiedTimestamp = Date.now();
11402
- if (lastReferenceFromOther && !urlInfo.isInline) {
11403
- pruneUrlInfoCallbackRef.current(urlInfo, lastReferenceFromOther);
11404
- }
11405
- urlInfo.referenceToOthersSet.forEach((referenceToOther) => {
11406
- referenceToOther.remove();
11407
- });
11408
- if (urlInfo.searchParams.size > 0) {
11409
- const urlWithoutSearch = asUrlWithoutSearch(urlInfo.url);
11410
- const urlInfoWithoutSearch = getUrlInfo(urlWithoutSearch);
11411
- if (urlInfoWithoutSearch) {
11412
- urlInfoWithoutSearch.searchParamVariantSet.delete(urlInfo);
11413
- }
11414
- }
11415
- }
11416
- };
11467
+
11417
11468
  const addUrlInfo = (urlInfo) => {
11418
11469
  urlInfo.graph = urlGraph;
11419
11470
  urlInfo.kitchen = kitchen;
@@ -11430,7 +11481,7 @@ const createUrlGraph = ({
11430
11481
  const context = Object.create(ownerContext);
11431
11482
  referencedUrlInfo = createUrlInfo(referencedUrl, context);
11432
11483
  addUrlInfo(referencedUrlInfo);
11433
- createUrlInfoCallbackRef.current(referencedUrlInfo);
11484
+ urlInfoCreatedEventEmitter.emit(referencedUrlInfo);
11434
11485
  }
11435
11486
  if (referencedUrlInfo.searchParams.size > 0 && !kitchen.context.shape) {
11436
11487
  // A resource is represented by a url.
@@ -11507,17 +11558,16 @@ const createUrlGraph = ({
11507
11558
  Object.assign(urlGraph, {
11508
11559
  name,
11509
11560
  rootUrlInfo,
11510
- createUrlInfoCallbackRef,
11511
- pruneUrlInfoCallbackRef,
11512
11561
 
11513
11562
  urlInfoMap,
11514
11563
  reuseOrCreateUrlInfo,
11515
11564
  hasUrlInfo,
11516
11565
  getUrlInfo,
11517
- deleteUrlInfo,
11518
11566
  getEntryPoints,
11519
11567
 
11520
11568
  inferReference,
11569
+ urlInfoCreatedEventEmitter,
11570
+ urlInfoDereferencedEventEmitter,
11521
11571
 
11522
11572
  toObject: () => {
11523
11573
  const data = {};
@@ -11555,6 +11605,7 @@ const createUrlInfo = (url, context) => {
11555
11605
  context,
11556
11606
  error: null,
11557
11607
  modifiedTimestamp: 0,
11608
+ dereferencedTimestamp: 0,
11558
11609
  originalContentEtag: null,
11559
11610
  contentEtag: null,
11560
11611
  isWatched: false,
@@ -11579,6 +11630,7 @@ const createUrlInfo = (url, context) => {
11579
11630
  originalContentAst: undefined,
11580
11631
  content: undefined,
11581
11632
  contentAst: undefined,
11633
+ contentLength: undefined,
11582
11634
  contentFinalized: false,
11583
11635
 
11584
11636
  sourcemap: null,
@@ -11605,37 +11657,24 @@ const createUrlInfo = (url, context) => {
11605
11657
  urlInfo.searchParams = new URL(url).searchParams;
11606
11658
 
11607
11659
  urlInfo.dependencies = createDependencies(urlInfo);
11608
- urlInfo.getFirstReferenceFromOther = ({ ignoreWeak } = {}) => {
11609
- for (const referenceFromOther of urlInfo.referenceFromOthersSet) {
11610
- if (referenceFromOther.url === urlInfo.url) {
11611
- if (
11612
- !referenceFromOther.isInline &&
11613
- referenceFromOther.next &&
11614
- referenceFromOther.next.isInline
11615
- ) {
11616
- // the url info was inlined, an other reference is required
11617
- // to consider the non-inlined urlInfo as used
11618
- continue;
11619
- }
11620
- if (ignoreWeak && referenceFromOther.isWeak) {
11621
- // weak reference don't count as using the url
11622
- continue;
11623
- }
11624
- return referenceFromOther;
11625
- }
11626
- }
11627
- return null;
11628
- };
11629
11660
  urlInfo.isUsed = () => {
11630
11661
  if (urlInfo.isRoot) {
11631
11662
  return true;
11632
11663
  }
11633
- // if (urlInfo.type === "sourcemap") {
11634
- // return true;
11635
- // }
11636
- // check if there is a strong reference to this urlInfo
11637
- if (urlInfo.getFirstReferenceFromOther({ ignoreWeak: true })) {
11638
- return true;
11664
+ for (const referenceFromOther of urlInfo.referenceFromOthersSet) {
11665
+ if (referenceFromOther.urlInfo !== urlInfo) {
11666
+ continue;
11667
+ }
11668
+ if (referenceFromOther.isWeak) {
11669
+ // weak reference don't count as using the url
11670
+ continue;
11671
+ }
11672
+ if (referenceFromOther.gotInlined()) {
11673
+ // the url info was inlined, an other reference is required
11674
+ // to consider the non-inlined urlInfo as used
11675
+ continue;
11676
+ }
11677
+ return referenceFromOther.ownerUrlInfo.isUsed();
11639
11678
  }
11640
11679
  // nothing uses this url anymore
11641
11680
  // - versioning update inline content
@@ -11725,7 +11764,7 @@ const createUrlInfo = (url, context) => {
11725
11764
  reference.next = referenceWithoutSearchParam;
11726
11765
  return referenceWithoutSearchParam.urlInfo;
11727
11766
  };
11728
- urlInfo.considerModified = ({ modifiedTimestamp = Date.now() } = {}) => {
11767
+ urlInfo.onModified = ({ modifiedTimestamp = Date.now() } = {}) => {
11729
11768
  const visitedSet = new Set();
11730
11769
  const iterate = (urlInfo) => {
11731
11770
  if (visitedSet.has(urlInfo)) {
@@ -11746,9 +11785,29 @@ const createUrlInfo = (url, context) => {
11746
11785
  };
11747
11786
  iterate(urlInfo);
11748
11787
  };
11749
- urlInfo.deleteFromGraph = (reference) => {
11750
- urlInfo.graph.deleteUrlInfo(urlInfo.url, reference);
11788
+ urlInfo.onDereferenced = (lastReferenceFromOther) => {
11789
+ urlInfo.dereferencedTimestamp = Date.now();
11790
+ urlInfo.graph.urlInfoDereferencedEventEmitter.emit(
11791
+ urlInfo,
11792
+ lastReferenceFromOther,
11793
+ );
11751
11794
  };
11795
+
11796
+ // not used for now
11797
+ // urlInfo.deleteFromGraph = () => {
11798
+ // urlInfo.kitchen.urlInfoTransformer.resetContent(urlInfo);
11799
+ // urlInfo.graph.urlInfoMap.delete(url);
11800
+ // urlInfo.referenceToOthersSet.forEach((referenceToOther) => {
11801
+ // referenceToOther.remove();
11802
+ // });
11803
+ // if (urlInfo.searchParams.size > 0) {
11804
+ // const urlWithoutSearch = asUrlWithoutSearch(urlInfo.url);
11805
+ // const urlInfoWithoutSearch = urlInfo.graph.getUrlInfo(urlWithoutSearch);
11806
+ // if (urlInfoWithoutSearch) {
11807
+ // urlInfoWithoutSearch.searchParamVariantSet.delete(urlInfo);
11808
+ // }
11809
+ // }
11810
+ // };
11752
11811
  urlInfo.cook = (customContext) => {
11753
11812
  return urlInfo.context.cook(urlInfo, customContext);
11754
11813
  };
@@ -12231,6 +12290,15 @@ const defineGettersOnPropertiesDerivedFromOriginalContent = (
12231
12290
  };
12232
12291
 
12233
12292
  const defineGettersOnPropertiesDerivedFromContent = (urlInfo) => {
12293
+ const contentLengthDescriptor = Object.getOwnPropertyDescriptor(
12294
+ urlInfo,
12295
+ "contentLength",
12296
+ );
12297
+ if (contentLengthDescriptor.value === undefined) {
12298
+ defineVolatileGetter(urlInfo, "contentLength", () => {
12299
+ return Buffer.byteLength(urlInfo.content);
12300
+ });
12301
+ }
12234
12302
  const contentAstDescriptor = Object.getOwnPropertyDescriptor(
12235
12303
  urlInfo,
12236
12304
  "contentAst",
@@ -12317,11 +12385,6 @@ const createUrlInfoTransformer = ({
12317
12385
  sourcemapsSourcesContent = true;
12318
12386
  }
12319
12387
 
12320
- const sourcemapsEnabled =
12321
- sourcemaps === "inline" ||
12322
- sourcemaps === "file" ||
12323
- sourcemaps === "programmatic";
12324
-
12325
12388
  const normalizeSourcemap = (urlInfo, sourcemap) => {
12326
12389
  let { sources } = sourcemap;
12327
12390
  if (sources) {
@@ -12364,15 +12427,10 @@ const createUrlInfoTransformer = ({
12364
12427
  urlInfo.originalContentEtag = undefined;
12365
12428
  urlInfo.contentAst = undefined;
12366
12429
  urlInfo.contentEtag = undefined;
12430
+ urlInfo.contentLength = undefined;
12367
12431
  urlInfo.content = undefined;
12368
12432
  urlInfo.sourcemap = null;
12369
12433
  urlInfo.sourcemapIsWrong = null;
12370
- urlInfo.referenceToOthersSet.forEach((referenceToOther) => {
12371
- const referencedUrlInfo = referenceToOther.urlInfo;
12372
- if (referencedUrlInfo.isInline) {
12373
- referencedUrlInfo.deleteFromGraph();
12374
- }
12375
- });
12376
12434
  };
12377
12435
 
12378
12436
  const setContent = async (
@@ -12381,6 +12439,7 @@ const createUrlInfoTransformer = ({
12381
12439
  {
12382
12440
  contentAst, // most of the time will be undefined
12383
12441
  contentEtag, // in practice it's always undefined
12442
+ contentLength,
12384
12443
  originalContent = content,
12385
12444
  originalContentAst, // most of the time will be undefined
12386
12445
  originalContentEtag, // in practice always undefined
@@ -12396,17 +12455,12 @@ const createUrlInfoTransformer = ({
12396
12455
 
12397
12456
  urlInfo.contentAst = contentAst;
12398
12457
  urlInfo.contentEtag = contentEtag;
12458
+ urlInfo.contentLength = contentLength;
12399
12459
  urlInfo.content = content;
12400
12460
  defineGettersOnPropertiesDerivedFromContent(urlInfo);
12401
12461
 
12402
12462
  urlInfo.sourcemap = sourcemap;
12403
- if (!sourcemapsEnabled) {
12404
- return;
12405
- }
12406
- if (!SOURCEMAP.enabledOnContentType(urlInfo.contentType)) {
12407
- return;
12408
- }
12409
- if (urlInfo.generatedUrl.startsWith("data:")) {
12463
+ if (!shouldHandleSourcemap(urlInfo)) {
12410
12464
  return;
12411
12465
  }
12412
12466
  // sourcemap is a special kind of reference:
@@ -12469,6 +12523,7 @@ const createUrlInfoTransformer = ({
12469
12523
  content,
12470
12524
  contentAst, // undefined most of the time
12471
12525
  contentEtag, // in practice always undefined
12526
+ contentLength,
12472
12527
  sourcemap,
12473
12528
  sourcemapIsWrong,
12474
12529
  } = transformations;
@@ -12482,10 +12537,11 @@ const createUrlInfoTransformer = ({
12482
12537
  if (contentModified) {
12483
12538
  urlInfo.contentAst = contentAst;
12484
12539
  urlInfo.contentEtag = contentEtag;
12540
+ urlInfo.contentLength = contentLength;
12485
12541
  urlInfo.content = content;
12486
12542
  defineGettersOnPropertiesDerivedFromContent(urlInfo);
12487
12543
  }
12488
- if (sourcemapsEnabled && sourcemap) {
12544
+ if (sourcemap && shouldHandleSourcemap(urlInfo)) {
12489
12545
  const sourcemapNormalized = normalizeSourcemap(urlInfo, sourcemap);
12490
12546
  const finalSourcemap = composeTwoSourcemaps(
12491
12547
  urlInfo.sourcemap,
@@ -12561,13 +12617,7 @@ const createUrlInfoTransformer = ({
12561
12617
  };
12562
12618
 
12563
12619
  const applySourcemapOnContent = (urlInfo) => {
12564
- if (!sourcemapsEnabled) {
12565
- return;
12566
- }
12567
- if (!urlInfo.sourcemap) {
12568
- return;
12569
- }
12570
- if (urlInfo.generatedUrl.startsWith("data:")) {
12620
+ if (!urlInfo.sourcemap || !shouldHandleSourcemap(urlInfo)) {
12571
12621
  return;
12572
12622
  }
12573
12623
 
@@ -12653,6 +12703,24 @@ const createUrlInfoTransformer = ({
12653
12703
  };
12654
12704
  };
12655
12705
 
12706
+ const shouldHandleSourcemap = (urlInfo) => {
12707
+ const { sourcemaps } = urlInfo.context;
12708
+ if (
12709
+ sourcemaps !== "inline" &&
12710
+ sourcemaps !== "file" &&
12711
+ sourcemaps !== "programmatic"
12712
+ ) {
12713
+ return false;
12714
+ }
12715
+ if (urlInfo.url.startsWith("data:")) {
12716
+ return false;
12717
+ }
12718
+ if (!SOURCEMAP.enabledOnContentType(urlInfo.contentType)) {
12719
+ return false;
12720
+ }
12721
+ return true;
12722
+ };
12723
+
12656
12724
  const createResolveUrlError = ({
12657
12725
  pluginController,
12658
12726
  reference,
@@ -13489,12 +13557,10 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
13489
13557
  // "cooked" hook
13490
13558
  pluginController.callHooks("cooked", urlInfo, (cookedReturnValue) => {
13491
13559
  if (typeof cookedReturnValue === "function") {
13492
- const prevCallback = graph.pruneUrlInfoCallbackRef.current;
13493
- graph.pruneUrlInfoCallbackRef.current(
13494
- (prunedUrlInfo, lastReferenceFromOther) => {
13495
- prevCallback();
13496
- if (prunedUrlInfo === urlInfo.url) {
13497
- graph.pruneUrlInfoCallbackRef.current = prevCallback;
13560
+ const removeCallback = urlInfo.graph.urlInfoDereferencedEventEmitter.on(
13561
+ (urlInfoDereferenced, lastReferenceFromOther) => {
13562
+ if (urlInfoDereferenced === urlInfo) {
13563
+ removeCallback();
13498
13564
  cookedReturnValue(lastReferenceFromOther.urlInfo);
13499
13565
  }
13500
13566
  },
@@ -13726,67 +13792,69 @@ const createUrlGraphReport = (urlGraph) => {
13726
13792
  other: 0,
13727
13793
  total: 0,
13728
13794
  };
13729
- urlGraph.urlInfoMap.forEach((urlInfo) => {
13730
- if (urlInfo.isRoot) {
13731
- return;
13732
- }
13733
- // ignore:
13734
- // - ignored files: we don't know their content
13735
- // - inline files and data files: they are already taken into account in the file where they appear
13736
- if (urlInfo.url.startsWith("ignore:")) {
13737
- return;
13738
- }
13739
- if (urlInfo.isInline) {
13740
- return;
13741
- }
13742
- if (urlInfo.url.startsWith("data:")) {
13743
- return;
13744
- }
13745
13795
 
13746
- // file loaded via import assertion are already inside the graph
13747
- // their js module equivalent are ignored to avoid counting it twice
13748
- // in the build graph the file targeted by import assertion will likely be gone
13749
- // and only the js module remain (likely bundled)
13750
- if (
13751
- urlInfo.searchParams.has("as_json_module") ||
13752
- urlInfo.searchParams.has("as_css_module") ||
13753
- urlInfo.searchParams.has("as_text_module")
13754
- ) {
13755
- return;
13756
- }
13757
- const urlContentSize = Buffer.byteLength(urlInfo.content);
13758
- const category = determineCategory(urlInfo);
13759
- if (category === "sourcemap") {
13760
- countGroups.sourcemaps++;
13761
- sizeGroups.sourcemaps += urlContentSize;
13762
- return;
13763
- }
13764
- countGroups.total++;
13765
- sizeGroups.total += urlContentSize;
13766
- if (category === "html") {
13767
- countGroups.html++;
13768
- sizeGroups.html += urlContentSize;
13769
- return;
13770
- }
13771
- if (category === "css") {
13772
- countGroups.css++;
13773
- sizeGroups.css += urlContentSize;
13774
- return;
13775
- }
13776
- if (category === "js") {
13777
- countGroups.js++;
13778
- sizeGroups.js += urlContentSize;
13779
- return;
13780
- }
13781
- if (category === "json") {
13782
- countGroups.json++;
13783
- sizeGroups.json += urlContentSize;
13796
+ GRAPH_VISITOR.forEachUrlInfoStronglyReferenced(
13797
+ urlGraph.rootUrlInfo,
13798
+ (urlInfo) => {
13799
+ // ignore:
13800
+ // - ignored files: we don't know their content
13801
+ // - inline files and data files: they are already taken into account in the file where they appear
13802
+ if (urlInfo.url.startsWith("ignore:")) {
13803
+ return;
13804
+ }
13805
+ if (urlInfo.isInline) {
13806
+ return;
13807
+ }
13808
+ if (urlInfo.url.startsWith("data:")) {
13809
+ return;
13810
+ }
13811
+
13812
+ // file loaded via import assertion are already inside the graph
13813
+ // their js module equivalent are ignored to avoid counting it twice
13814
+ // in the build graph the file targeted by import assertion will likely be gone
13815
+ // and only the js module remain (likely bundled)
13816
+ if (
13817
+ urlInfo.searchParams.has("as_json_module") ||
13818
+ urlInfo.searchParams.has("as_css_module") ||
13819
+ urlInfo.searchParams.has("as_text_module")
13820
+ ) {
13821
+ return;
13822
+ }
13823
+
13824
+ const urlContentSize = Buffer.byteLength(urlInfo.content);
13825
+ const category = determineCategory(urlInfo);
13826
+ if (category === "sourcemap") {
13827
+ countGroups.sourcemaps++;
13828
+ sizeGroups.sourcemaps += urlContentSize;
13829
+ return;
13830
+ }
13831
+ countGroups.total++;
13832
+ sizeGroups.total += urlContentSize;
13833
+ if (category === "html") {
13834
+ countGroups.html++;
13835
+ sizeGroups.html += urlContentSize;
13836
+ return;
13837
+ }
13838
+ if (category === "css") {
13839
+ countGroups.css++;
13840
+ sizeGroups.css += urlContentSize;
13841
+ return;
13842
+ }
13843
+ if (category === "js") {
13844
+ countGroups.js++;
13845
+ sizeGroups.js += urlContentSize;
13846
+ return;
13847
+ }
13848
+ if (category === "json") {
13849
+ countGroups.json++;
13850
+ sizeGroups.json += urlContentSize;
13851
+ return;
13852
+ }
13853
+ countGroups.other++;
13854
+ sizeGroups.other += urlContentSize;
13784
13855
  return;
13785
- }
13786
- countGroups.other++;
13787
- sizeGroups.other += urlContentSize;
13788
- return;
13789
- });
13856
+ },
13857
+ );
13790
13858
 
13791
13859
  const sizesToDistribute = {};
13792
13860
  Object.keys(sizeGroups).forEach((groupName) => {
@@ -18194,27 +18262,20 @@ import.meta.hot = createImportMetaHot(import.meta.url);
18194
18262
  return magicSource.toContentAndSourcemap();
18195
18263
  };
18196
18264
 
18197
- const jsenvPluginHotSearchParam = () => {
18198
- const shouldInjectHotSearchParam = (reference) => {
18199
- if (reference.isImplicit) {
18200
- return false;
18201
- }
18202
- if (reference.original && reference.original.searchParams.has("hot")) {
18203
- return true;
18204
- }
18205
- // parent is using ?hot -> propagate
18206
- const { ownerUrlInfo } = reference;
18207
- const lastReference = ownerUrlInfo.context.reference;
18208
- if (
18209
- lastReference &&
18210
- lastReference.original &&
18211
- lastReference.original.searchParams.has("hot")
18212
- ) {
18213
- return true;
18214
- }
18215
- return false;
18216
- };
18265
+ /*
18266
+ * When client wants to hot reload, it wants to be sure it can reach the server
18267
+ * and bypass any cache. This is done thanks to "hot" search param
18268
+ * being injected by the client: file.js?hot=Date.now()
18269
+ * When it happens server must:
18270
+ * 1. Consider it's a regular request to "file.js" and not a variation
18271
+ * of it (not like file.js?as_js_classic that creates a separate urlInfo)
18272
+ * -> This is done by redirectReference deleting the search param.
18273
+ *
18274
+ * 2. Inject ?hot= into all urls referenced by this one
18275
+ * -> This is done by transformReferenceSearchParams
18276
+ */
18217
18277
 
18278
+ const jsenvPluginHotSearchParam = () => {
18218
18279
  return {
18219
18280
  name: "jsenv:hot_search_param",
18220
18281
  appliesDuring: "dev",
@@ -18228,20 +18289,45 @@ const jsenvPluginHotSearchParam = () => {
18228
18289
  // We get rid of this params so that urlGraph and other parts of the code
18229
18290
  // recognize the url (it is not considered as a different url)
18230
18291
  urlObject.searchParams.delete("hot");
18231
- urlObject.searchParams.delete("v");
18232
18292
  return urlObject.href;
18233
18293
  },
18234
18294
  transformReferenceSearchParams: (reference) => {
18235
- if (!shouldInjectHotSearchParam(reference)) {
18295
+ if (reference.isImplicit) {
18236
18296
  return null;
18237
18297
  }
18298
+ if (reference.original && reference.original.searchParams.has("hot")) {
18299
+ return {
18300
+ hot: reference.original.searchParams.get("hot"),
18301
+ };
18302
+ }
18303
+ const request = reference.ownerUrlInfo.context.request;
18304
+ const parentHotParam = request ? request.searchParams.get("hot") : null;
18305
+ if (!parentHotParam) {
18306
+ return null;
18307
+ }
18308
+ // At this stage the parent is using ?hot and we are going to decide if
18309
+ // we propagate the search param to child.
18238
18310
  const referencedUrlInfo = reference.urlInfo;
18239
- if (!referencedUrlInfo.modifiedTimestamp) {
18311
+ const { modifiedTimestamp, dereferencedTimestamp } = referencedUrlInfo;
18312
+ if (!modifiedTimestamp && !dereferencedTimestamp) {
18240
18313
  return null;
18241
18314
  }
18315
+ // The goal is to send an url that will bypass client (the browser) cache
18316
+ // more precisely the runtime cache of js modules, but also any http cache
18317
+ // that could prevent re-execution of js code
18318
+ // In order to achieve this, this plugin inject ?hot=timestamp
18319
+ // - The browser will likely not have it in cache
18320
+ // and refetch lastest version from server + re-execute it
18321
+ // - If the browser have it in cache, he will not get it from server
18322
+ // We use the latest timestamp to ensure it's fresh
18323
+ // The dereferencedTimestamp is needed because when a js module is re-referenced
18324
+ // browser must re-execute it, even if the code is not modified
18325
+ const latestTimestamp =
18326
+ dereferencedTimestamp && modifiedTimestamp
18327
+ ? Math.max(dereferencedTimestamp, modifiedTimestamp)
18328
+ : dereferencedTimestamp || modifiedTimestamp;
18242
18329
  return {
18243
- hot: "",
18244
- v: referencedUrlInfo.modifiedTimestamp,
18330
+ hot: latestTimestamp,
18245
18331
  };
18246
18332
  },
18247
18333
  };
@@ -18284,8 +18370,8 @@ const jsenvPluginAutoreloadClient = () => {
18284
18370
  };
18285
18371
 
18286
18372
  const jsenvPluginAutoreloadServer = ({
18287
- clientFileChangeCallbackList,
18288
- clientFilesPruneCallbackList,
18373
+ clientFileChangeEventEmitter,
18374
+ clientFileDereferencedEventEmitter,
18289
18375
  }) => {
18290
18376
  return {
18291
18377
  name: "jsenv:autoreload_server",
@@ -18301,29 +18387,7 @@ const jsenvPluginAutoreloadServer = ({
18301
18387
  }
18302
18388
  return url;
18303
18389
  };
18304
- const notifyFullReload = ({ cause, reason, declinedBy }) => {
18305
- serverEventInfo.sendServerEvent({
18306
- cause,
18307
- type: "full",
18308
- typeReason: reason,
18309
- declinedBy,
18310
- });
18311
- };
18312
- const notifyPartialReload = ({ cause, reason, instructions }) => {
18313
- serverEventInfo.sendServerEvent({
18314
- cause,
18315
- type: "hot",
18316
- typeReason: reason,
18317
- hotInstructions: instructions,
18318
- });
18319
- };
18320
18390
  const propagateUpdate = (firstUrlInfo) => {
18321
- if (!serverEventInfo.kitchen.graph.getUrlInfo(firstUrlInfo.url)) {
18322
- return {
18323
- declined: true,
18324
- reason: `url not in the url graph`,
18325
- };
18326
- }
18327
18391
  const iterate = (urlInfo, seen) => {
18328
18392
  if (urlInfo.data.hotAcceptSelf) {
18329
18393
  return {
@@ -18402,86 +18466,150 @@ const jsenvPluginAutoreloadServer = ({
18402
18466
  const seen = [];
18403
18467
  return iterate(firstUrlInfo, seen);
18404
18468
  };
18405
- clientFileChangeCallbackList.push(({ url, event }) => {
18406
- const onUrlInfo = (urlInfo) => {
18407
- if (!urlInfo.isUsed()) {
18408
- return false;
18409
- }
18410
- const relativeUrl = formatUrlForClient(urlInfo.url);
18411
- const hotUpdate = propagateUpdate(urlInfo);
18412
- if (hotUpdate.declined) {
18413
- notifyFullReload({
18414
- cause: `${relativeUrl} ${event}`,
18415
- reason: hotUpdate.reason,
18416
- declinedBy: hotUpdate.declinedBy,
18417
- });
18418
- return true;
18419
- }
18420
- notifyPartialReload({
18421
- cause: `${relativeUrl} ${event}`,
18422
- reason: hotUpdate.reason,
18423
- instructions: hotUpdate.instructions,
18424
- });
18425
- return true;
18426
- };
18427
18469
 
18428
- const urlInfo = serverEventInfo.kitchen.graph.getUrlInfo(url);
18429
- if (urlInfo) {
18430
- if (onUrlInfo(urlInfo)) {
18431
- return;
18470
+ // We are delaying the moment we tell client how to reload because:
18471
+ //
18472
+ // 1. clientFileDereferencedEventEmitter can emit multiple times in a row
18473
+ // It happens when previous references are removed by stopCollecting (in "references.js")
18474
+ // In that case we could regroup the calls but we prefer to rely on debouncing to also cover
18475
+ // code that would remove many url in a row by other means (like reference.remove())
18476
+ //
18477
+ // 2. clientFileChangeEventEmitter can emit a lot of times in a short period (git checkout for instance)
18478
+ // In that case it's better to cooldown thanks to debouncing
18479
+ //
18480
+ // And we want to gather all the actions to take in response to these events because
18481
+ // we want to favor full-reload when needed and resort to partial reload afterwards
18482
+ // it's also important to ensure the client will fetch the server in the same order
18483
+ const delayedActionSet = new Set();
18484
+ let timeout;
18485
+ const delayAction = (action) => {
18486
+ delayedActionSet.add(action);
18487
+ clearTimeout(timeout);
18488
+ timeout = setTimeout(handleDelayedActions);
18489
+ };
18490
+
18491
+ const handleDelayedActions = () => {
18492
+ const actionSet = new Set(delayedActionSet);
18493
+ delayedActionSet.clear();
18494
+ let reloadMessage = null;
18495
+ for (const action of actionSet) {
18496
+ if (action.type === "change") {
18497
+ const { changedUrlInfo, event } = action;
18498
+ if (!changedUrlInfo.isUsed()) {
18499
+ continue;
18500
+ }
18501
+ const hotUpdate = propagateUpdate(changedUrlInfo);
18502
+ const relativeUrl = formatUrlForClient(changedUrlInfo.url);
18503
+ if (hotUpdate.declined) {
18504
+ reloadMessage = {
18505
+ cause: `${relativeUrl} ${event}`,
18506
+ type: "full",
18507
+ typeReason: hotUpdate.reason,
18508
+ declinedBy: hotUpdate.declinedBy,
18509
+ };
18510
+ break;
18511
+ }
18512
+ const instructions = hotUpdate.instructions;
18513
+ if (reloadMessage) {
18514
+ reloadMessage.hotInstructions.push(...instructions);
18515
+ } else {
18516
+ reloadMessage = {
18517
+ cause: `${relativeUrl} ${event}`,
18518
+ type: "hot",
18519
+ typeReason: hotUpdate.reason,
18520
+ hot: changedUrlInfo.modifiedTimestamp,
18521
+ hotInstructions: instructions,
18522
+ };
18523
+ }
18524
+ continue;
18432
18525
  }
18433
- for (const searchParamVariant of urlInfo.searchParamVariantSet) {
18434
- if (onUrlInfo(searchParamVariant)) {
18435
- return;
18526
+
18527
+ if (action.type === "prune") {
18528
+ const { prunedUrlInfo, lastReferenceFromOther } = action;
18529
+ if (lastReferenceFromOther.type === "sourcemap_comment") {
18530
+ // Can happen when starting dev server with sourcemaps: "file"
18531
+ // In that case, as sourcemaps are injected, the reference
18532
+ // are lost and sourcemap is considered as pruned
18533
+ continue;
18534
+ }
18535
+ const { ownerUrlInfo } = lastReferenceFromOther;
18536
+ if (!ownerUrlInfo.isUsed()) {
18537
+ continue;
18538
+ }
18539
+ const ownerHotUpdate = propagateUpdate(ownerUrlInfo);
18540
+ const cause = `${formatUrlForClient(
18541
+ prunedUrlInfo.url,
18542
+ )} is no longer referenced`;
18543
+ // now check if we can hot update the parent resource
18544
+ // then if we can hot update all dependencies
18545
+ if (ownerHotUpdate.declined) {
18546
+ reloadMessage = {
18547
+ cause,
18548
+ type: "full",
18549
+ typeReason: ownerHotUpdate.reason,
18550
+ declinedBy: ownerHotUpdate.declinedBy,
18551
+ };
18552
+ break;
18553
+ }
18554
+ // parent can hot update
18555
+ // but pruned url info declines
18556
+ if (prunedUrlInfo.data.hotDecline) {
18557
+ reloadMessage = {
18558
+ cause,
18559
+ type: "full",
18560
+ typeReason: `a pruned file declines hot reload`,
18561
+ declinedBy: formatUrlForClient(prunedUrlInfo.url),
18562
+ };
18563
+ break;
18564
+ }
18565
+ const pruneInstruction = {
18566
+ type: "prune",
18567
+ boundary: formatUrlForClient(prunedUrlInfo.url),
18568
+ acceptedBy: formatUrlForClient(
18569
+ lastReferenceFromOther.ownerUrlInfo.url,
18570
+ ),
18571
+ };
18572
+ if (reloadMessage) {
18573
+ reloadMessage.hotInstructions.push(pruneInstruction);
18574
+ } else {
18575
+ reloadMessage = {
18576
+ cause,
18577
+ type: "hot",
18578
+ typeReason: ownerHotUpdate.reason,
18579
+ hot: prunedUrlInfo.prunedTimestamp,
18580
+ hotInstructions: [pruneInstruction],
18581
+ };
18436
18582
  }
18437
18583
  }
18438
18584
  }
18439
- });
18440
- clientFilesPruneCallbackList.push(
18441
- (prunedUrlInfo, lastReferenceFromOther) => {
18442
- if (lastReferenceFromOther.type === "sourcemap_comment") {
18443
- // Can happen when starting dev server with sourcemaps: "file"
18444
- // In that case, as sourcemaps are injected, the reference
18445
- // are lost and sourcemap is considered as pruned
18446
- return;
18447
- }
18448
- const parentHotUpdate = propagateUpdate(
18449
- lastReferenceFromOther.ownerUrlInfo,
18450
- );
18451
- const cause = `following file is no longer referenced: ${formatUrlForClient(
18452
- prunedUrlInfo.url,
18453
- )}`;
18454
- // now check if we can hot update the parent resource
18455
- // then if we can hot update all dependencies
18456
- if (parentHotUpdate.declined) {
18457
- notifyFullReload({
18458
- cause,
18459
- reason: parentHotUpdate.reason,
18460
- declinedBy: parentHotUpdate.declinedBy,
18461
- });
18462
- return;
18463
- }
18464
- // parent can hot update
18465
- const instructions = [];
18466
- if (prunedUrlInfo.data.hotDecline) {
18467
- notifyFullReload({
18468
- cause,
18469
- reason: `a pruned file declines hot reload`,
18470
- declinedBy: formatUrlForClient(prunedUrlInfo.url),
18585
+ if (reloadMessage) {
18586
+ serverEventInfo.sendServerEvent(reloadMessage);
18587
+ }
18588
+ };
18589
+
18590
+ clientFileChangeEventEmitter.on(({ url, event }) => {
18591
+ const changedUrlInfo = serverEventInfo.kitchen.graph.getUrlInfo(url);
18592
+ if (changedUrlInfo) {
18593
+ delayAction({
18594
+ type: "change",
18595
+ changedUrlInfo,
18596
+ event,
18597
+ });
18598
+ for (const searchParamVariant of changedUrlInfo.searchParamVariantSet) {
18599
+ delayAction({
18600
+ type: "change",
18601
+ changedUrlInfo: searchParamVariant,
18602
+ event,
18471
18603
  });
18472
- return;
18473
18604
  }
18474
- instructions.push({
18605
+ }
18606
+ });
18607
+ clientFileDereferencedEventEmitter.on(
18608
+ (prunedUrlInfo, lastReferenceFromOther) => {
18609
+ delayAction({
18475
18610
  type: "prune",
18476
- boundary: formatUrlForClient(prunedUrlInfo.url),
18477
- acceptedBy: formatUrlForClient(
18478
- lastReferenceFromOther.ownerUrlInfo.url,
18479
- ),
18480
- });
18481
- notifyPartialReload({
18482
- cause,
18483
- reason: parentHotUpdate.reason,
18484
- instructions,
18611
+ prunedUrlInfo,
18612
+ lastReferenceFromOther,
18485
18613
  });
18486
18614
  },
18487
18615
  );
@@ -18507,15 +18635,15 @@ const jsenvPluginAutoreloadServer = ({
18507
18635
  };
18508
18636
 
18509
18637
  const jsenvPluginAutoreload = ({
18510
- clientFileChangeCallbackList,
18511
- clientFilesPruneCallbackList,
18638
+ clientFileChangeEventEmitter,
18639
+ clientFileDereferencedEventEmitter,
18512
18640
  }) => {
18513
18641
  return [
18514
18642
  jsenvPluginHotSearchParam(),
18515
18643
  jsenvPluginAutoreloadClient(),
18516
18644
  jsenvPluginAutoreloadServer({
18517
- clientFileChangeCallbackList,
18518
- clientFilesPruneCallbackList,
18645
+ clientFileChangeEventEmitter,
18646
+ clientFileDereferencedEventEmitter,
18519
18647
  }),
18520
18648
  ];
18521
18649
  };
@@ -18616,9 +18744,7 @@ const getCorePlugins = ({
18616
18744
  transpilation = true,
18617
18745
  inlining = true,
18618
18746
 
18619
- clientAutoreload = false,
18620
- clientFileChangeCallbackList,
18621
- clientFilesPruneCallbackList,
18747
+ clientAutoreload,
18622
18748
  cacheControl,
18623
18749
  scenarioPlaceholders = true,
18624
18750
  ribbon = true,
@@ -18629,9 +18755,6 @@ const getCorePlugins = ({
18629
18755
  if (supervisor === true) {
18630
18756
  supervisor = {};
18631
18757
  }
18632
- if (clientAutoreload === true) {
18633
- clientAutoreload = {};
18634
- }
18635
18758
  if (ribbon === true) {
18636
18759
  ribbon = {};
18637
18760
  }
@@ -18668,14 +18791,8 @@ const getCorePlugins = ({
18668
18791
  jsenvPluginNodeRuntime({ runtimeCompat }),
18669
18792
 
18670
18793
  jsenvPluginImportMetaHot(),
18671
- ...(clientAutoreload
18672
- ? [
18673
- jsenvPluginAutoreload({
18674
- ...clientAutoreload,
18675
- clientFileChangeCallbackList,
18676
- clientFilesPruneCallbackList,
18677
- }),
18678
- ]
18794
+ ...(clientAutoreload && clientAutoreload.enabled
18795
+ ? [jsenvPluginAutoreload(clientAutoreload)]
18679
18796
  : []),
18680
18797
  ...(cacheControl ? [jsenvPluginCacheControl(cacheControl)] : []),
18681
18798
  ...(ribbon ? [jsenvPluginRibbon({ rootDirectoryUrl, ...ribbon })] : []),
@@ -19189,63 +19306,60 @@ const createBuildVersionsManager = ({
19189
19306
 
19190
19307
  const contentOnlyVersionMap = new Map();
19191
19308
  {
19192
- GRAPH_VISITOR.forEach(finalKitchen.graph, (urlInfo) => {
19193
- // ignore:
19194
- // - inline files and data files:
19195
- // they are already taken into account in the file where they appear
19196
- // - ignored files:
19197
- // we don't know their content
19198
- // - unused files without reference
19199
- // File updated such as style.css -> style.css.js or file.js->file.nomodule.js
19200
- // Are used at some point just to be discarded later because they need to be converted
19201
- // There is no need to version them and we could not because the file have been ignored
19202
- // so their content is unknown
19203
- if (urlInfo.isRoot) {
19204
- return;
19205
- }
19206
- if (urlInfo.type === "sourcemap") {
19207
- return;
19208
- }
19209
- if (urlInfo.isInline) {
19210
- return;
19211
- }
19212
- if (urlInfo.url.startsWith("data:")) {
19213
- // urlInfo became inline and is not referenced by something else
19214
- return;
19215
- }
19216
- if (urlInfo.url.startsWith("ignore:")) {
19217
- return;
19218
- }
19219
- if (!urlInfo.isUsed()) {
19220
- return;
19221
- }
19222
- let content = urlInfo.content;
19223
- if (urlInfo.type === "html") {
19224
- content = stringifyHtmlAst(
19225
- parseHtmlString(urlInfo.content, {
19226
- storeOriginalPositions: false,
19227
- }),
19228
- {
19229
- cleanupJsenvAttributes: true,
19230
- cleanupPositionAttributes: true,
19231
- },
19232
- );
19233
- }
19234
- if (
19235
- CONTENT_TYPE.isTextual(urlInfo.contentType) &&
19236
- urlInfo.referenceToOthersSet.size > 0
19237
- ) {
19238
- const containedPlaceholders = new Set();
19239
- const contentWithPredictibleVersionPlaceholders =
19240
- replaceWithDefaultAndPopulateContainedPlaceholders(
19241
- content,
19242
- containedPlaceholders,
19309
+ GRAPH_VISITOR.forEachUrlInfoStronglyReferenced(
19310
+ finalKitchen.graph.rootUrlInfo,
19311
+ (urlInfo) => {
19312
+ // ignore:
19313
+ // - inline files and data files:
19314
+ // they are already taken into account in the file where they appear
19315
+ // - ignored files:
19316
+ // we don't know their content
19317
+ // - unused files without reference
19318
+ // File updated such as style.css -> style.css.js or file.js->file.nomodule.js
19319
+ // Are used at some point just to be discarded later because they need to be converted
19320
+ // There is no need to version them and we could not because the file have been ignored
19321
+ // so their content is unknown
19322
+ if (urlInfo.type === "sourcemap") {
19323
+ return;
19324
+ }
19325
+ if (urlInfo.isInline) {
19326
+ return;
19327
+ }
19328
+ if (urlInfo.url.startsWith("data:")) {
19329
+ // urlInfo became inline and is not referenced by something else
19330
+ return;
19331
+ }
19332
+ if (urlInfo.url.startsWith("ignore:")) {
19333
+ return;
19334
+ }
19335
+ let content = urlInfo.content;
19336
+ if (urlInfo.type === "html") {
19337
+ content = stringifyHtmlAst(
19338
+ parseHtmlString(urlInfo.content, {
19339
+ storeOriginalPositions: false,
19340
+ }),
19341
+ {
19342
+ cleanupJsenvAttributes: true,
19343
+ cleanupPositionAttributes: true,
19344
+ },
19243
19345
  );
19244
- content = contentWithPredictibleVersionPlaceholders;
19245
- }
19246
- const contentVersion = generateVersion([content], versionLength);
19247
- contentOnlyVersionMap.set(urlInfo, contentVersion);
19248
- });
19346
+ }
19347
+ if (
19348
+ CONTENT_TYPE.isTextual(urlInfo.contentType) &&
19349
+ urlInfo.referenceToOthersSet.size > 0
19350
+ ) {
19351
+ const containedPlaceholders = new Set();
19352
+ const contentWithPredictibleVersionPlaceholders =
19353
+ replaceWithDefaultAndPopulateContainedPlaceholders(
19354
+ content,
19355
+ containedPlaceholders,
19356
+ );
19357
+ content = contentWithPredictibleVersionPlaceholders;
19358
+ }
19359
+ const contentVersion = generateVersion([content], versionLength);
19360
+ contentOnlyVersionMap.set(urlInfo, contentVersion);
19361
+ },
19362
+ );
19249
19363
  }
19250
19364
 
19251
19365
  {
@@ -19771,7 +19885,7 @@ build ${entryPointKeys.length} entry points`);
19771
19885
  sourcemaps,
19772
19886
  sourcemapsSourcesContent,
19773
19887
  outDirectoryUrl: outDirectoryUrl
19774
- ? new URL("build/", outDirectoryUrl)
19888
+ ? new URL("prebuild/", outDirectoryUrl)
19775
19889
  : undefined,
19776
19890
  });
19777
19891
 
@@ -20026,13 +20140,6 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
20026
20140
  rawUrlInfo.url,
20027
20141
  "raw file",
20028
20142
  );
20029
- if (buildUrl.includes("?")) {
20030
- associateBuildUrlAndRawUrl(
20031
- asUrlWithoutSearch(buildUrl),
20032
- rawUrlInfo.url,
20033
- "raw file",
20034
- );
20035
- }
20036
20143
  return buildUrl;
20037
20144
  }
20038
20145
  if (reference.type === "sourcemap_comment") {
@@ -20246,82 +20353,84 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
20246
20353
  bundler.urlInfoMap.set(rawUrlInfo.url, rawUrlInfo);
20247
20354
  }
20248
20355
  };
20249
- GRAPH_VISITOR.forEach(rawKitchen.graph, (rawUrlInfo) => {
20250
- // ignore unused urls (avoid bundling things that are not actually used)
20251
- // happens for:
20252
- // - js import assertions
20253
- // - conversion to js classic using ?as_js_classic or ?js_module_fallback
20254
- if (!rawUrlInfo.isUsed()) {
20255
- return;
20256
- }
20257
- if (rawUrlInfo.isEntryPoint) {
20258
- addToBundlerIfAny(rawUrlInfo);
20259
- }
20260
- if (rawUrlInfo.type === "html") {
20261
- rawUrlInfo.referenceToOthersSet.forEach((referenceToOther) => {
20262
- if (referenceToOther.isWeak) {
20263
- return;
20264
- }
20265
- const referencedUrlInfo = referenceToOther.urlInfo;
20266
- if (referencedUrlInfo.isInline) {
20267
- if (referencedUrlInfo.type === "js_module") {
20268
- // bundle inline script type module deps
20269
- referencedUrlInfo.referenceToOthersSet.forEach(
20270
- (jsModuleReferenceToOther) => {
20271
- if (jsModuleReferenceToOther.type === "js_import") {
20272
- const inlineUrlInfo = jsModuleReferenceToOther.urlInfo;
20273
- addToBundlerIfAny(inlineUrlInfo);
20274
- }
20275
- },
20276
- );
20356
+ // ignore unused urls thanks to "forEachUrlInfoStronglyReferenced"
20357
+ // it avoid bundling things that are not actually used
20358
+ // happens for:
20359
+ // - js import assertions
20360
+ // - conversion to js classic using ?as_js_classic or ?js_module_fallback
20361
+ GRAPH_VISITOR.forEachUrlInfoStronglyReferenced(
20362
+ rawKitchen.graph.rootUrlInfo,
20363
+ (rawUrlInfo) => {
20364
+ if (rawUrlInfo.isEntryPoint) {
20365
+ addToBundlerIfAny(rawUrlInfo);
20366
+ }
20367
+ if (rawUrlInfo.type === "html") {
20368
+ rawUrlInfo.referenceToOthersSet.forEach((referenceToOther) => {
20369
+ if (referenceToOther.isWeak) {
20370
+ return;
20277
20371
  }
20278
- // inline content cannot be bundled
20279
- return;
20280
- }
20281
- addToBundlerIfAny(referencedUrlInfo);
20282
- });
20283
- rawUrlInfo.referenceToOthersSet.forEach((referenceToOther) => {
20284
- if (
20285
- referenceToOther.isResourceHint &&
20286
- referenceToOther.expectedType === "js_module"
20287
- ) {
20288
20372
  const referencedUrlInfo = referenceToOther.urlInfo;
20373
+ if (referencedUrlInfo.isInline) {
20374
+ if (referencedUrlInfo.type === "js_module") {
20375
+ // bundle inline script type module deps
20376
+ referencedUrlInfo.referenceToOthersSet.forEach(
20377
+ (jsModuleReferenceToOther) => {
20378
+ if (jsModuleReferenceToOther.type === "js_import") {
20379
+ const inlineUrlInfo =
20380
+ jsModuleReferenceToOther.urlInfo;
20381
+ addToBundlerIfAny(inlineUrlInfo);
20382
+ }
20383
+ },
20384
+ );
20385
+ }
20386
+ // inline content cannot be bundled
20387
+ return;
20388
+ }
20389
+ addToBundlerIfAny(referencedUrlInfo);
20390
+ });
20391
+ rawUrlInfo.referenceToOthersSet.forEach((referenceToOther) => {
20289
20392
  if (
20290
- referencedUrlInfo &&
20291
- // something else than the resource hint is using this url
20292
- referencedUrlInfo.referenceFromOthersSet.size > 0
20393
+ referenceToOther.isResourceHint &&
20394
+ referenceToOther.expectedType === "js_module"
20293
20395
  ) {
20294
- addToBundlerIfAny(referencedUrlInfo);
20396
+ const referencedUrlInfo = referenceToOther.urlInfo;
20397
+ if (
20398
+ referencedUrlInfo &&
20399
+ // something else than the resource hint is using this url
20400
+ referencedUrlInfo.referenceFromOthersSet.size > 0
20401
+ ) {
20402
+ addToBundlerIfAny(referencedUrlInfo);
20403
+ }
20295
20404
  }
20296
- }
20297
- });
20298
- return;
20299
- }
20300
- // File referenced with new URL('./file.js', import.meta.url)
20301
- // are entry points that should be bundled
20302
- // For instance we will bundle service worker/workers detected like this
20303
- if (rawUrlInfo.type === "js_module") {
20304
- rawUrlInfo.referenceToOthersSet.forEach((referenceToOther) => {
20305
- if (referenceToOther.type === "js_url") {
20306
- const referencedUrlInfo = referenceToOther.urlInfo;
20307
- for (const referenceFromOther of referencedUrlInfo.referenceFromOthersSet) {
20308
- if (referenceFromOther.url === referencedUrlInfo.url) {
20309
- if (
20310
- referenceFromOther.subtype === "import_dynamic" ||
20311
- referenceFromOther.type === "script"
20312
- ) {
20313
- // will already be bundled
20314
- return;
20405
+ });
20406
+ return;
20407
+ }
20408
+ // File referenced with new URL('./file.js', import.meta.url)
20409
+ // are entry points that should be bundled
20410
+ // For instance we will bundle service worker/workers detected like this
20411
+ if (rawUrlInfo.type === "js_module") {
20412
+ rawUrlInfo.referenceToOthersSet.forEach((referenceToOther) => {
20413
+ if (referenceToOther.type === "js_url") {
20414
+ const referencedUrlInfo = referenceToOther.urlInfo;
20415
+ for (const referenceFromOther of referencedUrlInfo.referenceFromOthersSet) {
20416
+ if (referenceFromOther.url === referencedUrlInfo.url) {
20417
+ if (
20418
+ referenceFromOther.subtype === "import_dynamic" ||
20419
+ referenceFromOther.type === "script"
20420
+ ) {
20421
+ // will already be bundled
20422
+ return;
20423
+ }
20315
20424
  }
20316
20425
  }
20426
+ addToBundlerIfAny(referencedUrlInfo);
20427
+ return;
20317
20428
  }
20318
- addToBundlerIfAny(referencedUrlInfo);
20319
- return;
20320
- }
20321
- if (referenceToOther.type === "js_inline_content") ;
20322
- });
20323
- }
20324
- });
20429
+ if (referenceToOther.type === "js_inline_content") ;
20430
+ });
20431
+ }
20432
+ },
20433
+ );
20325
20434
  await Object.keys(bundlers).reduce(async (previous, type) => {
20326
20435
  await previous;
20327
20436
  const bundler = bundlers[type];
@@ -20631,24 +20740,14 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
20631
20740
  resyncTask.done();
20632
20741
  }
20633
20742
  }
20634
- {
20635
- const actions = [];
20636
- GRAPH_VISITOR.forEach(finalKitchen.graph, (urlInfo) => {
20637
- if (!urlInfo.isUsed()) {
20638
- actions.push(() => {
20639
- urlInfo.deleteFromGraph();
20640
- });
20641
- }
20642
- });
20643
- actions.forEach((action) => action());
20644
- }
20645
20743
  {
20646
20744
  const serviceWorkerEntryUrlInfos = GRAPH_VISITOR.filter(
20647
20745
  finalKitchen.graph,
20648
20746
  (finalUrlInfo) => {
20649
20747
  return (
20650
20748
  finalUrlInfo.subtype === "service_worker" &&
20651
- finalUrlInfo.isEntryPoint
20749
+ finalUrlInfo.isEntryPoint &&
20750
+ finalUrlInfo.isUsed()
20652
20751
  );
20653
20752
  },
20654
20753
  );
@@ -20657,34 +20756,34 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
20657
20756
  "inject urls in service worker",
20658
20757
  );
20659
20758
  const serviceWorkerResources = {};
20660
- GRAPH_VISITOR.forEach(finalKitchen.graph, (urlInfo) => {
20661
- if (urlInfo.isRoot) {
20662
- return;
20663
- }
20664
- if (!urlInfo.url.startsWith("file:")) {
20665
- return;
20666
- }
20667
- if (urlInfo.isInline) {
20668
- return;
20669
- }
20670
- if (!canUseVersionedUrl(urlInfo)) {
20671
- // when url is not versioned we compute a "version" for that url anyway
20672
- // so that service worker source still changes and navigator
20673
- // detect there is a change
20759
+ GRAPH_VISITOR.forEachUrlInfoStronglyReferenced(
20760
+ finalKitchen.graph.rootUrlInfo,
20761
+ (urlInfo) => {
20762
+ if (!urlInfo.url.startsWith("file:")) {
20763
+ return;
20764
+ }
20765
+ if (urlInfo.isInline) {
20766
+ return;
20767
+ }
20768
+ if (!canUseVersionedUrl(urlInfo)) {
20769
+ // when url is not versioned we compute a "version" for that url anyway
20770
+ // so that service worker source still changes and navigator
20771
+ // detect there is a change
20772
+ const buildSpecifier = findKey(buildSpecifierMap, urlInfo.url);
20773
+ serviceWorkerResources[buildSpecifier] = {
20774
+ version: buildVersionsManager.getVersion(urlInfo),
20775
+ };
20776
+ return;
20777
+ }
20674
20778
  const buildSpecifier = findKey(buildSpecifierMap, urlInfo.url);
20779
+ const buildSpecifierVersioned =
20780
+ buildVersionsManager.getBuildSpecifierVersioned(buildSpecifier);
20675
20781
  serviceWorkerResources[buildSpecifier] = {
20676
20782
  version: buildVersionsManager.getVersion(urlInfo),
20783
+ versionedUrl: buildSpecifierVersioned,
20677
20784
  };
20678
- return;
20679
- }
20680
- const buildSpecifier = findKey(buildSpecifierMap, urlInfo.url);
20681
- const buildSpecifierVersioned =
20682
- buildVersionsManager.getBuildSpecifierVersioned(buildSpecifier);
20683
- serviceWorkerResources[buildSpecifier] = {
20684
- version: buildVersionsManager.getVersion(urlInfo),
20685
- versionedUrl: buildSpecifierVersioned,
20686
- };
20687
- });
20785
+ },
20786
+ );
20688
20787
  for (const serviceWorkerEntryUrlInfo of serviceWorkerEntryUrlInfos) {
20689
20788
  const serviceWorkerResourcesWithoutSwScriptItSelf = {
20690
20789
  ...serviceWorkerResources,
@@ -20724,48 +20823,48 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
20724
20823
  const buildRelativeUrl = urlToRelativeUrl(url, buildDirectoryUrl);
20725
20824
  return buildRelativeUrl;
20726
20825
  };
20727
- GRAPH_VISITOR.forEach(finalKitchen.graph, (urlInfo) => {
20728
- if (urlInfo.isRoot) {
20729
- return;
20730
- }
20731
- if (!urlInfo.url.startsWith("file:")) {
20732
- return;
20733
- }
20734
- if (urlInfo.type === "directory") {
20735
- return;
20736
- }
20737
- if (urlInfo.isInline) {
20738
- const buildRelativeUrl = getBuildRelativeUrl(urlInfo.url);
20739
- buildContents[buildRelativeUrl] = urlInfo.content;
20740
- buildInlineRelativeUrls.push(buildRelativeUrl);
20741
- } else {
20742
- const buildRelativeUrl = getBuildRelativeUrl(urlInfo.url);
20743
- if (
20744
- buildVersionsManager.getVersion(urlInfo) &&
20745
- canUseVersionedUrl(urlInfo)
20746
- ) {
20747
- const buildSpecifier = findKey(buildSpecifierMap, urlInfo.url);
20748
- const buildSpecifierVersioned =
20749
- buildVersionsManager.getBuildSpecifierVersioned(buildSpecifier);
20750
- const buildUrlVersioned = asBuildUrlVersioned({
20751
- buildSpecifierVersioned,
20752
- buildDirectoryUrl,
20753
- });
20754
- const buildRelativeUrlVersioned = urlToRelativeUrl(
20755
- buildUrlVersioned,
20756
- buildDirectoryUrl,
20757
- );
20758
- if (versioningMethod === "search_param") {
20759
- buildContents[buildRelativeUrl] = urlInfo.content;
20826
+ GRAPH_VISITOR.forEachUrlInfoStronglyReferenced(
20827
+ finalKitchen.graph.rootUrlInfo,
20828
+ (urlInfo) => {
20829
+ if (!urlInfo.url.startsWith("file:")) {
20830
+ return;
20831
+ }
20832
+ if (urlInfo.type === "directory") {
20833
+ return;
20834
+ }
20835
+ if (urlInfo.isInline) {
20836
+ const buildRelativeUrl = getBuildRelativeUrl(urlInfo.url);
20837
+ buildContents[buildRelativeUrl] = urlInfo.content;
20838
+ buildInlineRelativeUrls.push(buildRelativeUrl);
20839
+ } else {
20840
+ const buildRelativeUrl = getBuildRelativeUrl(urlInfo.url);
20841
+ if (
20842
+ buildVersionsManager.getVersion(urlInfo) &&
20843
+ canUseVersionedUrl(urlInfo)
20844
+ ) {
20845
+ const buildSpecifier = findKey(buildSpecifierMap, urlInfo.url);
20846
+ const buildSpecifierVersioned =
20847
+ buildVersionsManager.getBuildSpecifierVersioned(buildSpecifier);
20848
+ const buildUrlVersioned = asBuildUrlVersioned({
20849
+ buildSpecifierVersioned,
20850
+ buildDirectoryUrl,
20851
+ });
20852
+ const buildRelativeUrlVersioned = urlToRelativeUrl(
20853
+ buildUrlVersioned,
20854
+ buildDirectoryUrl,
20855
+ );
20856
+ if (versioningMethod === "search_param") {
20857
+ buildContents[buildRelativeUrl] = urlInfo.content;
20858
+ } else {
20859
+ buildContents[buildRelativeUrlVersioned] = urlInfo.content;
20860
+ }
20861
+ buildManifest[buildRelativeUrl] = buildRelativeUrlVersioned;
20760
20862
  } else {
20761
- buildContents[buildRelativeUrlVersioned] = urlInfo.content;
20863
+ buildContents[buildRelativeUrl] = urlInfo.content;
20762
20864
  }
20763
- buildManifest[buildRelativeUrl] = buildRelativeUrlVersioned;
20764
- } else {
20765
- buildContents[buildRelativeUrl] = urlInfo.content;
20766
20865
  }
20767
- }
20768
- });
20866
+ },
20867
+ );
20769
20868
  const buildFileContents = {};
20770
20869
  const buildInlineContents = {};
20771
20870
  Object.keys(buildContents)
@@ -20800,7 +20899,9 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
20800
20899
  writingFiles.done();
20801
20900
  }
20802
20901
  logger.info(
20803
- createUrlGraphSummary(finalKitchen.graph, { title: "build files" }),
20902
+ createUrlGraphSummary(finalKitchen.graph, {
20903
+ title: "build files",
20904
+ }),
20804
20905
  );
20805
20906
  return {
20806
20907
  buildFileContents,
@@ -21020,6 +21121,7 @@ const createFileService = ({
21020
21121
  serverStopCallbacks,
21021
21122
  serverEventsDispatcher,
21022
21123
  kitchenCache,
21124
+ onKitchenCreated = () => {},
21023
21125
 
21024
21126
  sourceDirectoryUrl,
21025
21127
  sourceMainFilePath,
@@ -21035,8 +21137,6 @@ const createFileService = ({
21035
21137
  supervisor,
21036
21138
  transpilation,
21037
21139
  clientAutoreload,
21038
- cooldownBetweenFileEvents,
21039
- clientServerEventsConfig,
21040
21140
  cacheControl,
21041
21141
  ribbon,
21042
21142
  sourcemaps,
@@ -21044,19 +21144,32 @@ const createFileService = ({
21044
21144
  sourcemapsSourcesContent,
21045
21145
  outDirectoryUrl,
21046
21146
  }) => {
21047
- const clientFileChangeCallbackList = [];
21048
- const clientFilesPruneCallbackList = [];
21147
+ if (clientAutoreload === true) {
21148
+ clientAutoreload = {};
21149
+ }
21150
+ if (clientAutoreload === false) {
21151
+ clientAutoreload = { enabled: false };
21152
+ }
21153
+ const clientFileChangeEventEmitter = createEventEmitter();
21154
+ const clientFileDereferencedEventEmitter = createEventEmitter();
21155
+
21156
+ clientAutoreload = {
21157
+ enabled: true,
21158
+ clientServerEventsConfig: {},
21159
+ clientFileChangeEventEmitter,
21160
+ clientFileDereferencedEventEmitter,
21161
+ ...clientAutoreload,
21162
+ };
21163
+
21049
21164
  const stopWatchingSourceFiles = watchSourceFiles(
21050
21165
  sourceDirectoryUrl,
21051
21166
  (fileInfo) => {
21052
- clientFileChangeCallbackList.forEach((callback) => {
21053
- callback(fileInfo);
21054
- });
21167
+ clientFileChangeEventEmitter.emit(fileInfo);
21055
21168
  },
21056
21169
  {
21057
21170
  sourceFilesConfig,
21058
21171
  keepProcessAlive: false,
21059
- cooldownBetweenFileEvents,
21172
+ cooldownBetweenFileEvents: clientAutoreload.cooldownBetweenFileEvents,
21060
21173
  },
21061
21174
  );
21062
21175
  serverStopCallbacks.push(stopWatchingSourceFiles);
@@ -21075,10 +21188,10 @@ const createFileService = ({
21075
21188
  sourceDirectoryUrl,
21076
21189
  );
21077
21190
  let kitchen;
21078
- clientFileChangeCallbackList.push(({ url }) => {
21191
+ clientFileChangeEventEmitter.on(({ url }) => {
21079
21192
  const urlInfo = kitchen.graph.getUrlInfo(url);
21080
21193
  if (urlInfo) {
21081
- urlInfo.considerModified();
21194
+ urlInfo.onModified();
21082
21195
  }
21083
21196
  });
21084
21197
  const clientRuntimeCompat = { [runtimeName]: runtimeVersion };
@@ -21114,8 +21227,6 @@ const createFileService = ({
21114
21227
  transpilation,
21115
21228
 
21116
21229
  clientAutoreload,
21117
- clientFileChangeCallbackList,
21118
- clientFilesPruneCallbackList,
21119
21230
  cacheControl,
21120
21231
  ribbon,
21121
21232
  }),
@@ -21129,18 +21240,18 @@ const createFileService = ({
21129
21240
  ? new URL(`${runtimeName}@${runtimeVersion}/`, outDirectoryUrl)
21130
21241
  : undefined,
21131
21242
  });
21132
- kitchen.graph.createUrlInfoCallbackRef.current = (urlInfo) => {
21243
+ kitchen.graph.urlInfoCreatedEventEmitter.on((urlInfoCreated) => {
21133
21244
  const { watch } = URL_META.applyAssociations({
21134
- url: urlInfo.url,
21245
+ url: urlInfoCreated.url,
21135
21246
  associations: watchAssociations,
21136
21247
  });
21137
- urlInfo.isWatched = watch;
21248
+ urlInfoCreated.isWatched = watch;
21138
21249
  // wehn an url depends on many others, we check all these (like package.json)
21139
- urlInfo.isValid = () => {
21140
- if (!urlInfo.url.startsWith("file:")) {
21250
+ urlInfoCreated.isValid = () => {
21251
+ if (!urlInfoCreated.url.startsWith("file:")) {
21141
21252
  return false;
21142
21253
  }
21143
- if (urlInfo.content === undefined) {
21254
+ if (urlInfoCreated.content === undefined) {
21144
21255
  // urlInfo content is undefined when:
21145
21256
  // - url info content never fetched
21146
21257
  // - it is considered as modified because undelying file is watched and got saved
@@ -21152,21 +21263,20 @@ const createFileService = ({
21152
21263
  // file is not watched, check the filesystem
21153
21264
  let fileContentAsBuffer;
21154
21265
  try {
21155
- fileContentAsBuffer = readFileSync(new URL(urlInfo.url));
21266
+ fileContentAsBuffer = readFileSync(new URL(urlInfoCreated.url));
21156
21267
  } catch (e) {
21157
21268
  if (e.code === "ENOENT") {
21158
- urlInfo.considerModified();
21159
- urlInfo.deleteFromGraph();
21269
+ urlInfoCreated.onModified();
21160
21270
  return false;
21161
21271
  }
21162
21272
  return false;
21163
21273
  }
21164
21274
  const fileContentEtag = bufferToEtag$1(fileContentAsBuffer);
21165
- if (fileContentEtag !== urlInfo.originalContentEtag) {
21166
- urlInfo.considerModified();
21275
+ if (fileContentEtag !== urlInfoCreated.originalContentEtag) {
21276
+ urlInfoCreated.onModified();
21167
21277
  // restore content to be able to compare it again later
21168
- urlInfo.kitchen.urlInfoTransformer.setContent(
21169
- urlInfo,
21278
+ urlInfoCreated.kitchen.urlInfoTransformer.setContent(
21279
+ urlInfoCreated,
21170
21280
  String(fileContentAsBuffer),
21171
21281
  {
21172
21282
  contentEtag: fileContentEtag,
@@ -21175,23 +21285,24 @@ const createFileService = ({
21175
21285
  return false;
21176
21286
  }
21177
21287
  }
21178
- for (const implicitUrl of urlInfo.implicitUrlSet) {
21179
- const implicitUrlInfo = kitchen.graph.getUrlInfo(implicitUrl);
21288
+ for (const implicitUrl of urlInfoCreated.implicitUrlSet) {
21289
+ const implicitUrlInfo = urlInfoCreated.graph.getUrlInfo(implicitUrl);
21180
21290
  if (implicitUrlInfo && !implicitUrlInfo.isValid()) {
21181
21291
  return false;
21182
21292
  }
21183
21293
  }
21184
21294
  return true;
21185
21295
  };
21186
- };
21187
- kitchen.graph.pruneUrlInfoCallbackRef.current = (
21188
- prunedUrlInfo,
21189
- lastReferenceFromOther,
21190
- ) => {
21191
- clientFilesPruneCallbackList.forEach((callback) => {
21192
- callback(prunedUrlInfo, lastReferenceFromOther);
21193
- });
21194
- };
21296
+ });
21297
+ kitchen.graph.urlInfoDereferencedEventEmitter.on(
21298
+ (urlInfoDereferenced, lastReferenceFromOther) => {
21299
+ clientFileDereferencedEventEmitter.emit(
21300
+ urlInfoDereferenced,
21301
+ lastReferenceFromOther,
21302
+ );
21303
+ },
21304
+ );
21305
+
21195
21306
  serverStopCallbacks.push(() => {
21196
21307
  kitchen.pluginController.callHooks("destroy", kitchen.context);
21197
21308
  });
@@ -21224,12 +21335,15 @@ const createFileService = ({
21224
21335
  });
21225
21336
  // "pushPlugin" so that event source client connection can be put as early as possible in html
21226
21337
  kitchen.pluginController.pushPlugin(
21227
- jsenvPluginServerEventsClientInjection(clientServerEventsConfig),
21338
+ jsenvPluginServerEventsClientInjection(
21339
+ clientAutoreload.clientServerEventsConfig,
21340
+ ),
21228
21341
  );
21229
21342
  }
21230
21343
  }
21231
21344
 
21232
21345
  kitchenCache.set(runtimeId, kitchen);
21346
+ onKitchenCreated(kitchen);
21233
21347
  return kitchen;
21234
21348
  };
21235
21349
 
@@ -21247,31 +21361,28 @@ const createFileService = ({
21247
21361
  if (responseFromPlugin) {
21248
21362
  return responseFromPlugin;
21249
21363
  }
21250
- let reference;
21251
- const parentUrl = inferParentFromRequest(request, sourceDirectoryUrl);
21252
- if (parentUrl) {
21253
- reference = kitchen.graph.inferReference(request.resource, parentUrl);
21254
- }
21364
+ const { referer } = request.headers;
21365
+ const parentUrl = referer
21366
+ ? WEB_URL_CONVERTER.asFileUrl(referer, {
21367
+ origin: request.origin,
21368
+ rootDirectoryUrl: sourceDirectoryUrl,
21369
+ })
21370
+ : sourceDirectoryUrl;
21371
+ let reference = kitchen.graph.inferReference(request.resource, parentUrl);
21255
21372
  if (!reference) {
21256
- let parentUrlInfo;
21257
- if (parentUrl) {
21258
- parentUrlInfo = kitchen.graph.getUrlInfo(parentUrl);
21259
- }
21260
- if (!parentUrlInfo) {
21261
- parentUrlInfo = kitchen.graph.rootUrlInfo;
21262
- }
21263
- reference = parentUrlInfo.dependencies.createResolveAndFinalize({
21264
- trace: { message: parentUrl || sourceDirectoryUrl },
21265
- type: "http_request",
21266
- specifier: request.resource,
21267
- });
21373
+ reference =
21374
+ kitchen.graph.rootUrlInfo.dependencies.createResolveAndFinalize({
21375
+ trace: { message: parentUrl },
21376
+ type: "http_request",
21377
+ specifier: request.resource,
21378
+ });
21268
21379
  }
21269
21380
  const urlInfo = reference.urlInfo;
21270
21381
  const ifNoneMatch = request.headers["if-none-match"];
21271
21382
  const urlInfoTargetedByCache = urlInfo.findParentIfInline() || urlInfo;
21272
21383
 
21273
21384
  try {
21274
- if (ifNoneMatch) {
21385
+ if (!urlInfo.error && ifNoneMatch) {
21275
21386
  const [clientOriginalContentEtag, clientContentEtag] =
21276
21387
  ifNoneMatch.split("_");
21277
21388
  if (
@@ -21319,7 +21430,7 @@ const createFileService = ({
21319
21430
  }),
21320
21431
  ...urlInfo.headers,
21321
21432
  "content-type": urlInfo.contentType,
21322
- "content-length": Buffer.byteLength(urlInfo.content),
21433
+ "content-length": urlInfo.contentLength,
21323
21434
  },
21324
21435
  body: urlInfo.content,
21325
21436
  timing: urlInfo.timing,
@@ -21358,7 +21469,7 @@ const createFileService = ({
21358
21469
  statusMessage: originalError.message,
21359
21470
  headers: {
21360
21471
  "content-type": urlInfo.contentType,
21361
- "content-length": Buffer.byteLength(urlInfo.content),
21472
+ "content-length": urlInfo.contentLength,
21362
21473
  "cache-control": "no-store",
21363
21474
  },
21364
21475
  body: urlInfo.content,
@@ -21405,30 +21516,10 @@ const createFileService = ({
21405
21516
  statusText: e.reason,
21406
21517
  statusMessage: e.stack,
21407
21518
  };
21408
- } finally {
21409
- // remove http_request when there is other references keeping url info alive
21410
- if (
21411
- reference.type === "http_request" &&
21412
- reference.urlInfo.referenceFromOthersSet.size > 1
21413
- ) {
21414
- reference.remove();
21415
- }
21416
21519
  }
21417
21520
  };
21418
21521
  };
21419
21522
 
21420
- const inferParentFromRequest = (request, sourceDirectoryUrl) => {
21421
- const { referer } = request.headers;
21422
- if (!referer) {
21423
- return null;
21424
- }
21425
- const refererUrl = referer;
21426
- return WEB_URL_CONVERTER.asFileUrl(refererUrl, {
21427
- origin: request.origin,
21428
- rootDirectoryUrl: sourceDirectoryUrl,
21429
- });
21430
- };
21431
-
21432
21523
  /**
21433
21524
  * Start a server for source files:
21434
21525
  * - cook source files according to jsenv plugins
@@ -21459,8 +21550,6 @@ const startDevServer = async ({
21459
21550
 
21460
21551
  sourceFilesConfig,
21461
21552
  clientAutoreload = true,
21462
- cooldownBetweenFileEvents,
21463
- clientServerEventsConfig = {},
21464
21553
 
21465
21554
  // runtimeCompat is the runtimeCompat for the build
21466
21555
  // when specified, dev server use it to warn in case
@@ -21476,6 +21565,7 @@ const startDevServer = async ({
21476
21565
  cacheControl = true,
21477
21566
  ribbon = true,
21478
21567
  // toolbar = false,
21568
+ onKitchenCreated = () => {},
21479
21569
 
21480
21570
  sourcemaps = "inline",
21481
21571
  sourcemapsSourcesProtocol,
@@ -21600,6 +21690,7 @@ const startDevServer = async ({
21600
21690
  serverStopCallbacks,
21601
21691
  serverEventsDispatcher,
21602
21692
  kitchenCache,
21693
+ onKitchenCreated,
21603
21694
 
21604
21695
  sourceDirectoryUrl,
21605
21696
  sourceMainFilePath,
@@ -21615,8 +21706,6 @@ const startDevServer = async ({
21615
21706
  supervisor,
21616
21707
  transpilation,
21617
21708
  clientAutoreload,
21618
- cooldownBetweenFileEvents,
21619
- clientServerEventsConfig,
21620
21709
  cacheControl,
21621
21710
  ribbon,
21622
21711
  sourcemaps,