@jsenv/core 37.0.5 → 37.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
  };
@@ -9995,17 +9996,21 @@ const babelPluginReplaceTopLevelThis = () => {
9995
9996
 
9996
9997
 
9997
9998
  const jsenvPluginAsJsModule = () => {
9999
+ const markAsJsModuleProxy = (reference) => {
10000
+ reference.expectedType = "js_module";
10001
+ if (!reference.filename) {
10002
+ const filename = urlToFilename$1(reference.url);
10003
+ const [basename] = splitFileExtension$1(filename);
10004
+ reference.filename = `${basename}.mjs`;
10005
+ }
10006
+ };
10007
+
9998
10008
  return {
9999
10009
  name: "jsenv:as_js_module",
10000
10010
  appliesDuring: "*",
10001
10011
  redirectReference: (reference) => {
10002
10012
  if (reference.searchParams.has("as_js_module")) {
10003
- reference.expectedType = "js_module";
10004
- if (!reference.filename) {
10005
- const filename = urlToFilename$1(reference.url);
10006
- const [basename] = splitFileExtension$1(filename);
10007
- reference.filename = `${basename}.mjs`;
10008
- }
10013
+ markAsJsModuleProxy(reference);
10009
10014
  }
10010
10015
  },
10011
10016
  fetchUrlContent: async (urlInfo) => {
@@ -10455,6 +10460,55 @@ GRAPH_VISITOR.findDependency = (urlInfo, visitor) => {
10455
10460
  return found;
10456
10461
  };
10457
10462
 
10463
+ // This function will be used in "build.js"
10464
+ // by passing rootUrlInfo as first arg
10465
+ // -> this ensure we visit only urls with strong references
10466
+ // because we start from root and ignore weak ref
10467
+ // The alternative would be to iterate on urlInfoMap
10468
+ // and call urlInfo.isUsed() but that would be more expensive
10469
+ GRAPH_VISITOR.forEachUrlInfoStronglyReferenced = (initialUrlInfo, callback) => {
10470
+ const seen = new Set();
10471
+ seen.add(initialUrlInfo);
10472
+ const iterateOnReferences = (urlInfo) => {
10473
+ for (const referenceToOther of urlInfo.referenceToOthersSet) {
10474
+ if (referenceToOther.isWeak) {
10475
+ continue;
10476
+ }
10477
+ if (referenceToOther.gotInlined()) {
10478
+ continue;
10479
+ }
10480
+ const referencedUrlInfo = referenceToOther.urlInfo;
10481
+ if (seen.has(referencedUrlInfo)) {
10482
+ continue;
10483
+ }
10484
+ seen.add(referencedUrlInfo);
10485
+ callback(referencedUrlInfo);
10486
+ iterateOnReferences(referencedUrlInfo);
10487
+ }
10488
+ };
10489
+ iterateOnReferences(initialUrlInfo);
10490
+ seen.clear();
10491
+ };
10492
+
10493
+ const createEventEmitter = () => {
10494
+ const callbackSet = new Set();
10495
+ const on = (callback) => {
10496
+ callbackSet.add(callback);
10497
+ return () => {
10498
+ callbackSet.delete(callback);
10499
+ };
10500
+ };
10501
+ const off = (callback) => {
10502
+ callbackSet.delete(callback);
10503
+ };
10504
+ const emit = (...args) => {
10505
+ callbackSet.forEach((callback) => {
10506
+ callback(...args);
10507
+ });
10508
+ };
10509
+ return { on, off, emit };
10510
+ };
10511
+
10458
10512
  const urlSpecifierEncoding = {
10459
10513
  encode: (reference) => {
10460
10514
  const { generatedSpecifier } = reference;
@@ -11116,6 +11170,10 @@ const createReference = ({
11116
11170
  return implicitReference;
11117
11171
  };
11118
11172
 
11173
+ reference.gotInlined = () => {
11174
+ return !reference.isInline && reference.next && reference.next.isInline;
11175
+ };
11176
+
11119
11177
  reference.remove = () => removeDependency(reference);
11120
11178
 
11121
11179
  // Object.preventExtensions(reference) // useful to ensure all properties are declared here
@@ -11210,7 +11268,6 @@ const canAddOrRemoveReference = (reference) => {
11210
11268
  const applyDependencyRemovalEffects = (reference) => {
11211
11269
  const { ownerUrlInfo } = reference;
11212
11270
  const { referenceToOthersSet } = ownerUrlInfo;
11213
-
11214
11271
  if (reference.isImplicit && !reference.isInline) {
11215
11272
  let hasAnOtherImplicitRef = false;
11216
11273
  for (const referenceToOther of referenceToOthersSet) {
@@ -11242,8 +11299,29 @@ const applyDependencyRemovalEffects = (reference) => {
11242
11299
  const referencedUrlInfo = reference.urlInfo;
11243
11300
  referencedUrlInfo.referenceFromOthersSet.delete(reference);
11244
11301
 
11245
- const firstReferenceFromOther =
11246
- referencedUrlInfo.getFirstReferenceFromOther();
11302
+ let firstReferenceFromOther;
11303
+ for (const referenceFromOther of referencedUrlInfo.referenceFromOthersSet) {
11304
+ if (referenceFromOther.urlInfo !== referencedUrlInfo) {
11305
+ continue;
11306
+ }
11307
+ // Here we want to know if the file is referenced by an other file.
11308
+ // So we want to ignore reference that are created by other means:
11309
+ // - "http_request"
11310
+ // This type of reference is created when client request a file
11311
+ // that we don't know yet
11312
+ // 1. reference(s) to this file are not yet discovered
11313
+ // 2. there is no reference to this file
11314
+ if (referenceFromOther.type === "http_request") {
11315
+ continue;
11316
+ }
11317
+ if (referenceFromOther.gotInlined()) {
11318
+ // the url info was inlined, an other reference is required
11319
+ // to consider the non-inlined urlInfo as used
11320
+ continue;
11321
+ }
11322
+ firstReferenceFromOther = referenceFromOther;
11323
+ break;
11324
+ }
11247
11325
  if (firstReferenceFromOther) {
11248
11326
  // either applying new ref should override old ref
11249
11327
  // or we should first remove effects before adding new ones
@@ -11254,11 +11332,8 @@ const applyDependencyRemovalEffects = (reference) => {
11254
11332
  }
11255
11333
  return false;
11256
11334
  }
11257
- if (reference.type !== "http_request") {
11258
- referencedUrlInfo.deleteFromGraph(reference);
11259
- return true;
11260
- }
11261
- return false;
11335
+ referencedUrlInfo.onDereferenced(reference);
11336
+ return true;
11262
11337
  };
11263
11338
 
11264
11339
  const traceFromUrlSite = (urlSite) => {
@@ -11371,8 +11446,8 @@ const createUrlGraph = ({
11371
11446
  name = "anonymous",
11372
11447
  }) => {
11373
11448
  const urlGraph = {};
11374
- const createUrlInfoCallbackRef = { current: () => {} };
11375
- const pruneUrlInfoCallbackRef = { current: () => {} };
11449
+ const urlInfoCreatedEventEmitter = createEventEmitter();
11450
+ const urlInfoDereferencedEventEmitter = createEventEmitter();
11376
11451
 
11377
11452
  const urlInfoMap = new Map();
11378
11453
  const hasUrlInfo = (key) => {
@@ -11393,27 +11468,7 @@ const createUrlGraph = ({
11393
11468
  }
11394
11469
  return null;
11395
11470
  };
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
- };
11471
+
11417
11472
  const addUrlInfo = (urlInfo) => {
11418
11473
  urlInfo.graph = urlGraph;
11419
11474
  urlInfo.kitchen = kitchen;
@@ -11430,7 +11485,7 @@ const createUrlGraph = ({
11430
11485
  const context = Object.create(ownerContext);
11431
11486
  referencedUrlInfo = createUrlInfo(referencedUrl, context);
11432
11487
  addUrlInfo(referencedUrlInfo);
11433
- createUrlInfoCallbackRef.current(referencedUrlInfo);
11488
+ urlInfoCreatedEventEmitter.emit(referencedUrlInfo);
11434
11489
  }
11435
11490
  if (referencedUrlInfo.searchParams.size > 0 && !kitchen.context.shape) {
11436
11491
  // A resource is represented by a url.
@@ -11507,17 +11562,16 @@ const createUrlGraph = ({
11507
11562
  Object.assign(urlGraph, {
11508
11563
  name,
11509
11564
  rootUrlInfo,
11510
- createUrlInfoCallbackRef,
11511
- pruneUrlInfoCallbackRef,
11512
11565
 
11513
11566
  urlInfoMap,
11514
11567
  reuseOrCreateUrlInfo,
11515
11568
  hasUrlInfo,
11516
11569
  getUrlInfo,
11517
- deleteUrlInfo,
11518
11570
  getEntryPoints,
11519
11571
 
11520
11572
  inferReference,
11573
+ urlInfoCreatedEventEmitter,
11574
+ urlInfoDereferencedEventEmitter,
11521
11575
 
11522
11576
  toObject: () => {
11523
11577
  const data = {};
@@ -11555,6 +11609,7 @@ const createUrlInfo = (url, context) => {
11555
11609
  context,
11556
11610
  error: null,
11557
11611
  modifiedTimestamp: 0,
11612
+ dereferencedTimestamp: 0,
11558
11613
  originalContentEtag: null,
11559
11614
  contentEtag: null,
11560
11615
  isWatched: false,
@@ -11579,6 +11634,7 @@ const createUrlInfo = (url, context) => {
11579
11634
  originalContentAst: undefined,
11580
11635
  content: undefined,
11581
11636
  contentAst: undefined,
11637
+ contentLength: undefined,
11582
11638
  contentFinalized: false,
11583
11639
 
11584
11640
  sourcemap: null,
@@ -11605,37 +11661,24 @@ const createUrlInfo = (url, context) => {
11605
11661
  urlInfo.searchParams = new URL(url).searchParams;
11606
11662
 
11607
11663
  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
11664
  urlInfo.isUsed = () => {
11630
11665
  if (urlInfo.isRoot) {
11631
11666
  return true;
11632
11667
  }
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;
11668
+ for (const referenceFromOther of urlInfo.referenceFromOthersSet) {
11669
+ if (referenceFromOther.urlInfo !== urlInfo) {
11670
+ continue;
11671
+ }
11672
+ if (referenceFromOther.isWeak) {
11673
+ // weak reference don't count as using the url
11674
+ continue;
11675
+ }
11676
+ if (referenceFromOther.gotInlined()) {
11677
+ // the url info was inlined, an other reference is required
11678
+ // to consider the non-inlined urlInfo as used
11679
+ continue;
11680
+ }
11681
+ return referenceFromOther.ownerUrlInfo.isUsed();
11639
11682
  }
11640
11683
  // nothing uses this url anymore
11641
11684
  // - versioning update inline content
@@ -11655,6 +11698,9 @@ const createUrlInfo = (url, context) => {
11655
11698
  }
11656
11699
  return null;
11657
11700
  };
11701
+ urlInfo.findDependent = (callback) => {
11702
+ return GRAPH_VISITOR.findDependent(urlInfo, callback);
11703
+ };
11658
11704
  urlInfo.isSearchParamVariantOf = (otherUrlInfo) => {
11659
11705
  if (urlInfo.searchParams.size === 0) {
11660
11706
  return false;
@@ -11725,7 +11771,7 @@ const createUrlInfo = (url, context) => {
11725
11771
  reference.next = referenceWithoutSearchParam;
11726
11772
  return referenceWithoutSearchParam.urlInfo;
11727
11773
  };
11728
- urlInfo.considerModified = ({ modifiedTimestamp = Date.now() } = {}) => {
11774
+ urlInfo.onModified = ({ modifiedTimestamp = Date.now() } = {}) => {
11729
11775
  const visitedSet = new Set();
11730
11776
  const iterate = (urlInfo) => {
11731
11777
  if (visitedSet.has(urlInfo)) {
@@ -11746,9 +11792,29 @@ const createUrlInfo = (url, context) => {
11746
11792
  };
11747
11793
  iterate(urlInfo);
11748
11794
  };
11749
- urlInfo.deleteFromGraph = (reference) => {
11750
- urlInfo.graph.deleteUrlInfo(urlInfo.url, reference);
11795
+ urlInfo.onDereferenced = (lastReferenceFromOther) => {
11796
+ urlInfo.dereferencedTimestamp = Date.now();
11797
+ urlInfo.graph.urlInfoDereferencedEventEmitter.emit(
11798
+ urlInfo,
11799
+ lastReferenceFromOther,
11800
+ );
11751
11801
  };
11802
+
11803
+ // not used for now
11804
+ // urlInfo.deleteFromGraph = () => {
11805
+ // urlInfo.kitchen.urlInfoTransformer.resetContent(urlInfo);
11806
+ // urlInfo.graph.urlInfoMap.delete(url);
11807
+ // urlInfo.referenceToOthersSet.forEach((referenceToOther) => {
11808
+ // referenceToOther.remove();
11809
+ // });
11810
+ // if (urlInfo.searchParams.size > 0) {
11811
+ // const urlWithoutSearch = asUrlWithoutSearch(urlInfo.url);
11812
+ // const urlInfoWithoutSearch = urlInfo.graph.getUrlInfo(urlWithoutSearch);
11813
+ // if (urlInfoWithoutSearch) {
11814
+ // urlInfoWithoutSearch.searchParamVariantSet.delete(urlInfo);
11815
+ // }
11816
+ // }
11817
+ // };
11752
11818
  urlInfo.cook = (customContext) => {
11753
11819
  return urlInfo.context.cook(urlInfo, customContext);
11754
11820
  };
@@ -12231,6 +12297,15 @@ const defineGettersOnPropertiesDerivedFromOriginalContent = (
12231
12297
  };
12232
12298
 
12233
12299
  const defineGettersOnPropertiesDerivedFromContent = (urlInfo) => {
12300
+ const contentLengthDescriptor = Object.getOwnPropertyDescriptor(
12301
+ urlInfo,
12302
+ "contentLength",
12303
+ );
12304
+ if (contentLengthDescriptor.value === undefined) {
12305
+ defineVolatileGetter(urlInfo, "contentLength", () => {
12306
+ return Buffer.byteLength(urlInfo.content);
12307
+ });
12308
+ }
12234
12309
  const contentAstDescriptor = Object.getOwnPropertyDescriptor(
12235
12310
  urlInfo,
12236
12311
  "contentAst",
@@ -12317,11 +12392,6 @@ const createUrlInfoTransformer = ({
12317
12392
  sourcemapsSourcesContent = true;
12318
12393
  }
12319
12394
 
12320
- const sourcemapsEnabled =
12321
- sourcemaps === "inline" ||
12322
- sourcemaps === "file" ||
12323
- sourcemaps === "programmatic";
12324
-
12325
12395
  const normalizeSourcemap = (urlInfo, sourcemap) => {
12326
12396
  let { sources } = sourcemap;
12327
12397
  if (sources) {
@@ -12364,15 +12434,10 @@ const createUrlInfoTransformer = ({
12364
12434
  urlInfo.originalContentEtag = undefined;
12365
12435
  urlInfo.contentAst = undefined;
12366
12436
  urlInfo.contentEtag = undefined;
12437
+ urlInfo.contentLength = undefined;
12367
12438
  urlInfo.content = undefined;
12368
12439
  urlInfo.sourcemap = null;
12369
12440
  urlInfo.sourcemapIsWrong = null;
12370
- urlInfo.referenceToOthersSet.forEach((referenceToOther) => {
12371
- const referencedUrlInfo = referenceToOther.urlInfo;
12372
- if (referencedUrlInfo.isInline) {
12373
- referencedUrlInfo.deleteFromGraph();
12374
- }
12375
- });
12376
12441
  };
12377
12442
 
12378
12443
  const setContent = async (
@@ -12381,6 +12446,7 @@ const createUrlInfoTransformer = ({
12381
12446
  {
12382
12447
  contentAst, // most of the time will be undefined
12383
12448
  contentEtag, // in practice it's always undefined
12449
+ contentLength,
12384
12450
  originalContent = content,
12385
12451
  originalContentAst, // most of the time will be undefined
12386
12452
  originalContentEtag, // in practice always undefined
@@ -12396,17 +12462,12 @@ const createUrlInfoTransformer = ({
12396
12462
 
12397
12463
  urlInfo.contentAst = contentAst;
12398
12464
  urlInfo.contentEtag = contentEtag;
12465
+ urlInfo.contentLength = contentLength;
12399
12466
  urlInfo.content = content;
12400
12467
  defineGettersOnPropertiesDerivedFromContent(urlInfo);
12401
12468
 
12402
12469
  urlInfo.sourcemap = sourcemap;
12403
- if (!sourcemapsEnabled) {
12404
- return;
12405
- }
12406
- if (!SOURCEMAP.enabledOnContentType(urlInfo.contentType)) {
12407
- return;
12408
- }
12409
- if (urlInfo.generatedUrl.startsWith("data:")) {
12470
+ if (!shouldHandleSourcemap(urlInfo)) {
12410
12471
  return;
12411
12472
  }
12412
12473
  // sourcemap is a special kind of reference:
@@ -12469,6 +12530,7 @@ const createUrlInfoTransformer = ({
12469
12530
  content,
12470
12531
  contentAst, // undefined most of the time
12471
12532
  contentEtag, // in practice always undefined
12533
+ contentLength,
12472
12534
  sourcemap,
12473
12535
  sourcemapIsWrong,
12474
12536
  } = transformations;
@@ -12482,10 +12544,11 @@ const createUrlInfoTransformer = ({
12482
12544
  if (contentModified) {
12483
12545
  urlInfo.contentAst = contentAst;
12484
12546
  urlInfo.contentEtag = contentEtag;
12547
+ urlInfo.contentLength = contentLength;
12485
12548
  urlInfo.content = content;
12486
12549
  defineGettersOnPropertiesDerivedFromContent(urlInfo);
12487
12550
  }
12488
- if (sourcemapsEnabled && sourcemap) {
12551
+ if (sourcemap && shouldHandleSourcemap(urlInfo)) {
12489
12552
  const sourcemapNormalized = normalizeSourcemap(urlInfo, sourcemap);
12490
12553
  const finalSourcemap = composeTwoSourcemaps(
12491
12554
  urlInfo.sourcemap,
@@ -12561,13 +12624,7 @@ const createUrlInfoTransformer = ({
12561
12624
  };
12562
12625
 
12563
12626
  const applySourcemapOnContent = (urlInfo) => {
12564
- if (!sourcemapsEnabled) {
12565
- return;
12566
- }
12567
- if (!urlInfo.sourcemap) {
12568
- return;
12569
- }
12570
- if (urlInfo.generatedUrl.startsWith("data:")) {
12627
+ if (!urlInfo.sourcemap || !shouldHandleSourcemap(urlInfo)) {
12571
12628
  return;
12572
12629
  }
12573
12630
 
@@ -12653,6 +12710,24 @@ const createUrlInfoTransformer = ({
12653
12710
  };
12654
12711
  };
12655
12712
 
12713
+ const shouldHandleSourcemap = (urlInfo) => {
12714
+ const { sourcemaps } = urlInfo.context;
12715
+ if (
12716
+ sourcemaps !== "inline" &&
12717
+ sourcemaps !== "file" &&
12718
+ sourcemaps !== "programmatic"
12719
+ ) {
12720
+ return false;
12721
+ }
12722
+ if (urlInfo.url.startsWith("data:")) {
12723
+ return false;
12724
+ }
12725
+ if (!SOURCEMAP.enabledOnContentType(urlInfo.contentType)) {
12726
+ return false;
12727
+ }
12728
+ return true;
12729
+ };
12730
+
12656
12731
  const createResolveUrlError = ({
12657
12732
  pluginController,
12658
12733
  reference,
@@ -13489,12 +13564,10 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
13489
13564
  // "cooked" hook
13490
13565
  pluginController.callHooks("cooked", urlInfo, (cookedReturnValue) => {
13491
13566
  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;
13567
+ const removeCallback = urlInfo.graph.urlInfoDereferencedEventEmitter.on(
13568
+ (urlInfoDereferenced, lastReferenceFromOther) => {
13569
+ if (urlInfoDereferenced === urlInfo) {
13570
+ removeCallback();
13498
13571
  cookedReturnValue(lastReferenceFromOther.urlInfo);
13499
13572
  }
13500
13573
  },
@@ -13726,67 +13799,69 @@ const createUrlGraphReport = (urlGraph) => {
13726
13799
  other: 0,
13727
13800
  total: 0,
13728
13801
  };
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
13802
 
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;
13803
+ GRAPH_VISITOR.forEachUrlInfoStronglyReferenced(
13804
+ urlGraph.rootUrlInfo,
13805
+ (urlInfo) => {
13806
+ // ignore:
13807
+ // - ignored files: we don't know their content
13808
+ // - inline files and data files: they are already taken into account in the file where they appear
13809
+ if (urlInfo.url.startsWith("ignore:")) {
13810
+ return;
13811
+ }
13812
+ if (urlInfo.isInline) {
13813
+ return;
13814
+ }
13815
+ if (urlInfo.url.startsWith("data:")) {
13816
+ return;
13817
+ }
13818
+
13819
+ // file loaded via import assertion are already inside the graph
13820
+ // their js module equivalent are ignored to avoid counting it twice
13821
+ // in the build graph the file targeted by import assertion will likely be gone
13822
+ // and only the js module remain (likely bundled)
13823
+ if (
13824
+ urlInfo.searchParams.has("as_json_module") ||
13825
+ urlInfo.searchParams.has("as_css_module") ||
13826
+ urlInfo.searchParams.has("as_text_module")
13827
+ ) {
13828
+ return;
13829
+ }
13830
+
13831
+ const urlContentSize = Buffer.byteLength(urlInfo.content);
13832
+ const category = determineCategory(urlInfo);
13833
+ if (category === "sourcemap") {
13834
+ countGroups.sourcemaps++;
13835
+ sizeGroups.sourcemaps += urlContentSize;
13836
+ return;
13837
+ }
13838
+ countGroups.total++;
13839
+ sizeGroups.total += urlContentSize;
13840
+ if (category === "html") {
13841
+ countGroups.html++;
13842
+ sizeGroups.html += urlContentSize;
13843
+ return;
13844
+ }
13845
+ if (category === "css") {
13846
+ countGroups.css++;
13847
+ sizeGroups.css += urlContentSize;
13848
+ return;
13849
+ }
13850
+ if (category === "js") {
13851
+ countGroups.js++;
13852
+ sizeGroups.js += urlContentSize;
13853
+ return;
13854
+ }
13855
+ if (category === "json") {
13856
+ countGroups.json++;
13857
+ sizeGroups.json += urlContentSize;
13858
+ return;
13859
+ }
13860
+ countGroups.other++;
13861
+ sizeGroups.other += urlContentSize;
13784
13862
  return;
13785
- }
13786
- countGroups.other++;
13787
- sizeGroups.other += urlContentSize;
13788
- return;
13789
- });
13863
+ },
13864
+ );
13790
13865
 
13791
13866
  const sizesToDistribute = {};
13792
13867
  Object.keys(sizeGroups).forEach((groupName) => {
@@ -16953,6 +17028,10 @@ const jsenvPluginNodeEsmResolution = (resolutionConfig = {}) => {
16953
17028
  if (reference.subtype === "self_import_scripts_arg") {
16954
17029
  return nodeEsmResolverDefault(reference);
16955
17030
  }
17031
+ if (reference.type === "js_import") {
17032
+ // happens for ?as_js_module
17033
+ return nodeEsmResolverDefault(reference);
17034
+ }
16956
17035
  return null;
16957
17036
  };
16958
17037
  }
@@ -18194,27 +18273,20 @@ import.meta.hot = createImportMetaHot(import.meta.url);
18194
18273
  return magicSource.toContentAndSourcemap();
18195
18274
  };
18196
18275
 
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
- };
18276
+ /*
18277
+ * When client wants to hot reload, it wants to be sure it can reach the server
18278
+ * and bypass any cache. This is done thanks to "hot" search param
18279
+ * being injected by the client: file.js?hot=Date.now()
18280
+ * When it happens server must:
18281
+ * 1. Consider it's a regular request to "file.js" and not a variation
18282
+ * of it (not like file.js?as_js_classic that creates a separate urlInfo)
18283
+ * -> This is done by redirectReference deleting the search param.
18284
+ *
18285
+ * 2. Inject ?hot= into all urls referenced by this one
18286
+ * -> This is done by transformReferenceSearchParams
18287
+ */
18217
18288
 
18289
+ const jsenvPluginHotSearchParam = () => {
18218
18290
  return {
18219
18291
  name: "jsenv:hot_search_param",
18220
18292
  appliesDuring: "dev",
@@ -18228,20 +18300,45 @@ const jsenvPluginHotSearchParam = () => {
18228
18300
  // We get rid of this params so that urlGraph and other parts of the code
18229
18301
  // recognize the url (it is not considered as a different url)
18230
18302
  urlObject.searchParams.delete("hot");
18231
- urlObject.searchParams.delete("v");
18232
18303
  return urlObject.href;
18233
18304
  },
18234
18305
  transformReferenceSearchParams: (reference) => {
18235
- if (!shouldInjectHotSearchParam(reference)) {
18306
+ if (reference.isImplicit) {
18307
+ return null;
18308
+ }
18309
+ if (reference.original && reference.original.searchParams.has("hot")) {
18310
+ return {
18311
+ hot: reference.original.searchParams.get("hot"),
18312
+ };
18313
+ }
18314
+ const request = reference.ownerUrlInfo.context.request;
18315
+ const parentHotParam = request ? request.searchParams.get("hot") : null;
18316
+ if (!parentHotParam) {
18236
18317
  return null;
18237
18318
  }
18319
+ // At this stage the parent is using ?hot and we are going to decide if
18320
+ // we propagate the search param to child.
18238
18321
  const referencedUrlInfo = reference.urlInfo;
18239
- if (!referencedUrlInfo.modifiedTimestamp) {
18322
+ const { modifiedTimestamp, dereferencedTimestamp } = referencedUrlInfo;
18323
+ if (!modifiedTimestamp && !dereferencedTimestamp) {
18240
18324
  return null;
18241
18325
  }
18326
+ // The goal is to send an url that will bypass client (the browser) cache
18327
+ // more precisely the runtime cache of js modules, but also any http cache
18328
+ // that could prevent re-execution of js code
18329
+ // In order to achieve this, this plugin inject ?hot=timestamp
18330
+ // - The browser will likely not have it in cache
18331
+ // and refetch lastest version from server + re-execute it
18332
+ // - If the browser have it in cache, he will not get it from server
18333
+ // We use the latest timestamp to ensure it's fresh
18334
+ // The dereferencedTimestamp is needed because when a js module is re-referenced
18335
+ // browser must re-execute it, even if the code is not modified
18336
+ const latestTimestamp =
18337
+ dereferencedTimestamp && modifiedTimestamp
18338
+ ? Math.max(dereferencedTimestamp, modifiedTimestamp)
18339
+ : dereferencedTimestamp || modifiedTimestamp;
18242
18340
  return {
18243
- hot: "",
18244
- v: referencedUrlInfo.modifiedTimestamp,
18341
+ hot: latestTimestamp,
18245
18342
  };
18246
18343
  },
18247
18344
  };
@@ -18284,8 +18381,8 @@ const jsenvPluginAutoreloadClient = () => {
18284
18381
  };
18285
18382
 
18286
18383
  const jsenvPluginAutoreloadServer = ({
18287
- clientFileChangeCallbackList,
18288
- clientFilesPruneCallbackList,
18384
+ clientFileChangeEventEmitter,
18385
+ clientFileDereferencedEventEmitter,
18289
18386
  }) => {
18290
18387
  return {
18291
18388
  name: "jsenv:autoreload_server",
@@ -18301,29 +18398,7 @@ const jsenvPluginAutoreloadServer = ({
18301
18398
  }
18302
18399
  return url;
18303
18400
  };
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
18401
  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
18402
  const iterate = (urlInfo, seen) => {
18328
18403
  if (urlInfo.data.hotAcceptSelf) {
18329
18404
  return {
@@ -18402,86 +18477,150 @@ const jsenvPluginAutoreloadServer = ({
18402
18477
  const seen = [];
18403
18478
  return iterate(firstUrlInfo, seen);
18404
18479
  };
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
18480
 
18428
- const urlInfo = serverEventInfo.kitchen.graph.getUrlInfo(url);
18429
- if (urlInfo) {
18430
- if (onUrlInfo(urlInfo)) {
18431
- return;
18481
+ // We are delaying the moment we tell client how to reload because:
18482
+ //
18483
+ // 1. clientFileDereferencedEventEmitter can emit multiple times in a row
18484
+ // It happens when previous references are removed by stopCollecting (in "references.js")
18485
+ // In that case we could regroup the calls but we prefer to rely on debouncing to also cover
18486
+ // code that would remove many url in a row by other means (like reference.remove())
18487
+ //
18488
+ // 2. clientFileChangeEventEmitter can emit a lot of times in a short period (git checkout for instance)
18489
+ // In that case it's better to cooldown thanks to debouncing
18490
+ //
18491
+ // And we want to gather all the actions to take in response to these events because
18492
+ // we want to favor full-reload when needed and resort to partial reload afterwards
18493
+ // it's also important to ensure the client will fetch the server in the same order
18494
+ const delayedActionSet = new Set();
18495
+ let timeout;
18496
+ const delayAction = (action) => {
18497
+ delayedActionSet.add(action);
18498
+ clearTimeout(timeout);
18499
+ timeout = setTimeout(handleDelayedActions);
18500
+ };
18501
+
18502
+ const handleDelayedActions = () => {
18503
+ const actionSet = new Set(delayedActionSet);
18504
+ delayedActionSet.clear();
18505
+ let reloadMessage = null;
18506
+ for (const action of actionSet) {
18507
+ if (action.type === "change") {
18508
+ const { changedUrlInfo, event } = action;
18509
+ if (!changedUrlInfo.isUsed()) {
18510
+ continue;
18511
+ }
18512
+ const hotUpdate = propagateUpdate(changedUrlInfo);
18513
+ const relativeUrl = formatUrlForClient(changedUrlInfo.url);
18514
+ if (hotUpdate.declined) {
18515
+ reloadMessage = {
18516
+ cause: `${relativeUrl} ${event}`,
18517
+ type: "full",
18518
+ typeReason: hotUpdate.reason,
18519
+ declinedBy: hotUpdate.declinedBy,
18520
+ };
18521
+ break;
18522
+ }
18523
+ const instructions = hotUpdate.instructions;
18524
+ if (reloadMessage) {
18525
+ reloadMessage.hotInstructions.push(...instructions);
18526
+ } else {
18527
+ reloadMessage = {
18528
+ cause: `${relativeUrl} ${event}`,
18529
+ type: "hot",
18530
+ typeReason: hotUpdate.reason,
18531
+ hot: changedUrlInfo.modifiedTimestamp,
18532
+ hotInstructions: instructions,
18533
+ };
18534
+ }
18535
+ continue;
18432
18536
  }
18433
- for (const searchParamVariant of urlInfo.searchParamVariantSet) {
18434
- if (onUrlInfo(searchParamVariant)) {
18435
- return;
18537
+
18538
+ if (action.type === "prune") {
18539
+ const { prunedUrlInfo, lastReferenceFromOther } = action;
18540
+ if (lastReferenceFromOther.type === "sourcemap_comment") {
18541
+ // Can happen when starting dev server with sourcemaps: "file"
18542
+ // In that case, as sourcemaps are injected, the reference
18543
+ // are lost and sourcemap is considered as pruned
18544
+ continue;
18545
+ }
18546
+ const { ownerUrlInfo } = lastReferenceFromOther;
18547
+ if (!ownerUrlInfo.isUsed()) {
18548
+ continue;
18549
+ }
18550
+ const ownerHotUpdate = propagateUpdate(ownerUrlInfo);
18551
+ const cause = `${formatUrlForClient(
18552
+ prunedUrlInfo.url,
18553
+ )} is no longer referenced`;
18554
+ // now check if we can hot update the parent resource
18555
+ // then if we can hot update all dependencies
18556
+ if (ownerHotUpdate.declined) {
18557
+ reloadMessage = {
18558
+ cause,
18559
+ type: "full",
18560
+ typeReason: ownerHotUpdate.reason,
18561
+ declinedBy: ownerHotUpdate.declinedBy,
18562
+ };
18563
+ break;
18564
+ }
18565
+ // parent can hot update
18566
+ // but pruned url info declines
18567
+ if (prunedUrlInfo.data.hotDecline) {
18568
+ reloadMessage = {
18569
+ cause,
18570
+ type: "full",
18571
+ typeReason: `a pruned file declines hot reload`,
18572
+ declinedBy: formatUrlForClient(prunedUrlInfo.url),
18573
+ };
18574
+ break;
18575
+ }
18576
+ const pruneInstruction = {
18577
+ type: "prune",
18578
+ boundary: formatUrlForClient(prunedUrlInfo.url),
18579
+ acceptedBy: formatUrlForClient(
18580
+ lastReferenceFromOther.ownerUrlInfo.url,
18581
+ ),
18582
+ };
18583
+ if (reloadMessage) {
18584
+ reloadMessage.hotInstructions.push(pruneInstruction);
18585
+ } else {
18586
+ reloadMessage = {
18587
+ cause,
18588
+ type: "hot",
18589
+ typeReason: ownerHotUpdate.reason,
18590
+ hot: prunedUrlInfo.prunedTimestamp,
18591
+ hotInstructions: [pruneInstruction],
18592
+ };
18436
18593
  }
18437
18594
  }
18438
18595
  }
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),
18596
+ if (reloadMessage) {
18597
+ serverEventInfo.sendServerEvent(reloadMessage);
18598
+ }
18599
+ };
18600
+
18601
+ clientFileChangeEventEmitter.on(({ url, event }) => {
18602
+ const changedUrlInfo = serverEventInfo.kitchen.graph.getUrlInfo(url);
18603
+ if (changedUrlInfo) {
18604
+ delayAction({
18605
+ type: "change",
18606
+ changedUrlInfo,
18607
+ event,
18608
+ });
18609
+ for (const searchParamVariant of changedUrlInfo.searchParamVariantSet) {
18610
+ delayAction({
18611
+ type: "change",
18612
+ changedUrlInfo: searchParamVariant,
18613
+ event,
18471
18614
  });
18472
- return;
18473
18615
  }
18474
- instructions.push({
18616
+ }
18617
+ });
18618
+ clientFileDereferencedEventEmitter.on(
18619
+ (prunedUrlInfo, lastReferenceFromOther) => {
18620
+ delayAction({
18475
18621
  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,
18622
+ prunedUrlInfo,
18623
+ lastReferenceFromOther,
18485
18624
  });
18486
18625
  },
18487
18626
  );
@@ -18507,15 +18646,15 @@ const jsenvPluginAutoreloadServer = ({
18507
18646
  };
18508
18647
 
18509
18648
  const jsenvPluginAutoreload = ({
18510
- clientFileChangeCallbackList,
18511
- clientFilesPruneCallbackList,
18649
+ clientFileChangeEventEmitter,
18650
+ clientFileDereferencedEventEmitter,
18512
18651
  }) => {
18513
18652
  return [
18514
18653
  jsenvPluginHotSearchParam(),
18515
18654
  jsenvPluginAutoreloadClient(),
18516
18655
  jsenvPluginAutoreloadServer({
18517
- clientFileChangeCallbackList,
18518
- clientFilesPruneCallbackList,
18656
+ clientFileChangeEventEmitter,
18657
+ clientFileDereferencedEventEmitter,
18519
18658
  }),
18520
18659
  ];
18521
18660
  };
@@ -18616,9 +18755,7 @@ const getCorePlugins = ({
18616
18755
  transpilation = true,
18617
18756
  inlining = true,
18618
18757
 
18619
- clientAutoreload = false,
18620
- clientFileChangeCallbackList,
18621
- clientFilesPruneCallbackList,
18758
+ clientAutoreload,
18622
18759
  cacheControl,
18623
18760
  scenarioPlaceholders = true,
18624
18761
  ribbon = true,
@@ -18629,9 +18766,6 @@ const getCorePlugins = ({
18629
18766
  if (supervisor === true) {
18630
18767
  supervisor = {};
18631
18768
  }
18632
- if (clientAutoreload === true) {
18633
- clientAutoreload = {};
18634
- }
18635
18769
  if (ribbon === true) {
18636
18770
  ribbon = {};
18637
18771
  }
@@ -18668,14 +18802,8 @@ const getCorePlugins = ({
18668
18802
  jsenvPluginNodeRuntime({ runtimeCompat }),
18669
18803
 
18670
18804
  jsenvPluginImportMetaHot(),
18671
- ...(clientAutoreload
18672
- ? [
18673
- jsenvPluginAutoreload({
18674
- ...clientAutoreload,
18675
- clientFileChangeCallbackList,
18676
- clientFilesPruneCallbackList,
18677
- }),
18678
- ]
18805
+ ...(clientAutoreload && clientAutoreload.enabled
18806
+ ? [jsenvPluginAutoreload(clientAutoreload)]
18679
18807
  : []),
18680
18808
  ...(cacheControl ? [jsenvPluginCacheControl(cacheControl)] : []),
18681
18809
  ...(ribbon ? [jsenvPluginRibbon({ rootDirectoryUrl, ...ribbon })] : []),
@@ -19189,63 +19317,60 @@ const createBuildVersionsManager = ({
19189
19317
 
19190
19318
  const contentOnlyVersionMap = new Map();
19191
19319
  {
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,
19320
+ GRAPH_VISITOR.forEachUrlInfoStronglyReferenced(
19321
+ finalKitchen.graph.rootUrlInfo,
19322
+ (urlInfo) => {
19323
+ // ignore:
19324
+ // - inline files and data files:
19325
+ // they are already taken into account in the file where they appear
19326
+ // - ignored files:
19327
+ // we don't know their content
19328
+ // - unused files without reference
19329
+ // File updated such as style.css -> style.css.js or file.js->file.nomodule.js
19330
+ // Are used at some point just to be discarded later because they need to be converted
19331
+ // There is no need to version them and we could not because the file have been ignored
19332
+ // so their content is unknown
19333
+ if (urlInfo.type === "sourcemap") {
19334
+ return;
19335
+ }
19336
+ if (urlInfo.isInline) {
19337
+ return;
19338
+ }
19339
+ if (urlInfo.url.startsWith("data:")) {
19340
+ // urlInfo became inline and is not referenced by something else
19341
+ return;
19342
+ }
19343
+ if (urlInfo.url.startsWith("ignore:")) {
19344
+ return;
19345
+ }
19346
+ let content = urlInfo.content;
19347
+ if (urlInfo.type === "html") {
19348
+ content = stringifyHtmlAst(
19349
+ parseHtmlString(urlInfo.content, {
19350
+ storeOriginalPositions: false,
19351
+ }),
19352
+ {
19353
+ cleanupJsenvAttributes: true,
19354
+ cleanupPositionAttributes: true,
19355
+ },
19243
19356
  );
19244
- content = contentWithPredictibleVersionPlaceholders;
19245
- }
19246
- const contentVersion = generateVersion([content], versionLength);
19247
- contentOnlyVersionMap.set(urlInfo, contentVersion);
19248
- });
19357
+ }
19358
+ if (
19359
+ CONTENT_TYPE.isTextual(urlInfo.contentType) &&
19360
+ urlInfo.referenceToOthersSet.size > 0
19361
+ ) {
19362
+ const containedPlaceholders = new Set();
19363
+ const contentWithPredictibleVersionPlaceholders =
19364
+ replaceWithDefaultAndPopulateContainedPlaceholders(
19365
+ content,
19366
+ containedPlaceholders,
19367
+ );
19368
+ content = contentWithPredictibleVersionPlaceholders;
19369
+ }
19370
+ const contentVersion = generateVersion([content], versionLength);
19371
+ contentOnlyVersionMap.set(urlInfo, contentVersion);
19372
+ },
19373
+ );
19249
19374
  }
19250
19375
 
19251
19376
  {
@@ -19771,7 +19896,7 @@ build ${entryPointKeys.length} entry points`);
19771
19896
  sourcemaps,
19772
19897
  sourcemapsSourcesContent,
19773
19898
  outDirectoryUrl: outDirectoryUrl
19774
- ? new URL("build/", outDirectoryUrl)
19899
+ ? new URL("prebuild/", outDirectoryUrl)
19775
19900
  : undefined,
19776
19901
  });
19777
19902
 
@@ -20026,13 +20151,6 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
20026
20151
  rawUrlInfo.url,
20027
20152
  "raw file",
20028
20153
  );
20029
- if (buildUrl.includes("?")) {
20030
- associateBuildUrlAndRawUrl(
20031
- asUrlWithoutSearch(buildUrl),
20032
- rawUrlInfo.url,
20033
- "raw file",
20034
- );
20035
- }
20036
20154
  return buildUrl;
20037
20155
  }
20038
20156
  if (reference.type === "sourcemap_comment") {
@@ -20246,82 +20364,84 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
20246
20364
  bundler.urlInfoMap.set(rawUrlInfo.url, rawUrlInfo);
20247
20365
  }
20248
20366
  };
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
- );
20367
+ // ignore unused urls thanks to "forEachUrlInfoStronglyReferenced"
20368
+ // it avoid bundling things that are not actually used
20369
+ // happens for:
20370
+ // - js import assertions
20371
+ // - conversion to js classic using ?as_js_classic or ?js_module_fallback
20372
+ GRAPH_VISITOR.forEachUrlInfoStronglyReferenced(
20373
+ rawKitchen.graph.rootUrlInfo,
20374
+ (rawUrlInfo) => {
20375
+ if (rawUrlInfo.isEntryPoint) {
20376
+ addToBundlerIfAny(rawUrlInfo);
20377
+ }
20378
+ if (rawUrlInfo.type === "html") {
20379
+ rawUrlInfo.referenceToOthersSet.forEach((referenceToOther) => {
20380
+ if (referenceToOther.isWeak) {
20381
+ return;
20277
20382
  }
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
20383
  const referencedUrlInfo = referenceToOther.urlInfo;
20384
+ if (referencedUrlInfo.isInline) {
20385
+ if (referencedUrlInfo.type === "js_module") {
20386
+ // bundle inline script type module deps
20387
+ referencedUrlInfo.referenceToOthersSet.forEach(
20388
+ (jsModuleReferenceToOther) => {
20389
+ if (jsModuleReferenceToOther.type === "js_import") {
20390
+ const inlineUrlInfo =
20391
+ jsModuleReferenceToOther.urlInfo;
20392
+ addToBundlerIfAny(inlineUrlInfo);
20393
+ }
20394
+ },
20395
+ );
20396
+ }
20397
+ // inline content cannot be bundled
20398
+ return;
20399
+ }
20400
+ addToBundlerIfAny(referencedUrlInfo);
20401
+ });
20402
+ rawUrlInfo.referenceToOthersSet.forEach((referenceToOther) => {
20289
20403
  if (
20290
- referencedUrlInfo &&
20291
- // something else than the resource hint is using this url
20292
- referencedUrlInfo.referenceFromOthersSet.size > 0
20404
+ referenceToOther.isResourceHint &&
20405
+ referenceToOther.expectedType === "js_module"
20293
20406
  ) {
20294
- addToBundlerIfAny(referencedUrlInfo);
20407
+ const referencedUrlInfo = referenceToOther.urlInfo;
20408
+ if (
20409
+ referencedUrlInfo &&
20410
+ // something else than the resource hint is using this url
20411
+ referencedUrlInfo.referenceFromOthersSet.size > 0
20412
+ ) {
20413
+ addToBundlerIfAny(referencedUrlInfo);
20414
+ }
20295
20415
  }
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;
20416
+ });
20417
+ return;
20418
+ }
20419
+ // File referenced with new URL('./file.js', import.meta.url)
20420
+ // are entry points that should be bundled
20421
+ // For instance we will bundle service worker/workers detected like this
20422
+ if (rawUrlInfo.type === "js_module") {
20423
+ rawUrlInfo.referenceToOthersSet.forEach((referenceToOther) => {
20424
+ if (referenceToOther.type === "js_url") {
20425
+ const referencedUrlInfo = referenceToOther.urlInfo;
20426
+ for (const referenceFromOther of referencedUrlInfo.referenceFromOthersSet) {
20427
+ if (referenceFromOther.url === referencedUrlInfo.url) {
20428
+ if (
20429
+ referenceFromOther.subtype === "import_dynamic" ||
20430
+ referenceFromOther.type === "script"
20431
+ ) {
20432
+ // will already be bundled
20433
+ return;
20434
+ }
20315
20435
  }
20316
20436
  }
20437
+ addToBundlerIfAny(referencedUrlInfo);
20438
+ return;
20317
20439
  }
20318
- addToBundlerIfAny(referencedUrlInfo);
20319
- return;
20320
- }
20321
- if (referenceToOther.type === "js_inline_content") ;
20322
- });
20323
- }
20324
- });
20440
+ if (referenceToOther.type === "js_inline_content") ;
20441
+ });
20442
+ }
20443
+ },
20444
+ );
20325
20445
  await Object.keys(bundlers).reduce(async (previous, type) => {
20326
20446
  await previous;
20327
20447
  const bundler = bundlers[type];
@@ -20631,24 +20751,14 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
20631
20751
  resyncTask.done();
20632
20752
  }
20633
20753
  }
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
20754
  {
20646
20755
  const serviceWorkerEntryUrlInfos = GRAPH_VISITOR.filter(
20647
20756
  finalKitchen.graph,
20648
20757
  (finalUrlInfo) => {
20649
20758
  return (
20650
20759
  finalUrlInfo.subtype === "service_worker" &&
20651
- finalUrlInfo.isEntryPoint
20760
+ finalUrlInfo.isEntryPoint &&
20761
+ finalUrlInfo.isUsed()
20652
20762
  );
20653
20763
  },
20654
20764
  );
@@ -20657,34 +20767,34 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
20657
20767
  "inject urls in service worker",
20658
20768
  );
20659
20769
  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
20770
+ GRAPH_VISITOR.forEachUrlInfoStronglyReferenced(
20771
+ finalKitchen.graph.rootUrlInfo,
20772
+ (urlInfo) => {
20773
+ if (!urlInfo.url.startsWith("file:")) {
20774
+ return;
20775
+ }
20776
+ if (urlInfo.isInline) {
20777
+ return;
20778
+ }
20779
+ if (!canUseVersionedUrl(urlInfo)) {
20780
+ // when url is not versioned we compute a "version" for that url anyway
20781
+ // so that service worker source still changes and navigator
20782
+ // detect there is a change
20783
+ const buildSpecifier = findKey(buildSpecifierMap, urlInfo.url);
20784
+ serviceWorkerResources[buildSpecifier] = {
20785
+ version: buildVersionsManager.getVersion(urlInfo),
20786
+ };
20787
+ return;
20788
+ }
20674
20789
  const buildSpecifier = findKey(buildSpecifierMap, urlInfo.url);
20790
+ const buildSpecifierVersioned =
20791
+ buildVersionsManager.getBuildSpecifierVersioned(buildSpecifier);
20675
20792
  serviceWorkerResources[buildSpecifier] = {
20676
20793
  version: buildVersionsManager.getVersion(urlInfo),
20794
+ versionedUrl: buildSpecifierVersioned,
20677
20795
  };
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
- });
20796
+ },
20797
+ );
20688
20798
  for (const serviceWorkerEntryUrlInfo of serviceWorkerEntryUrlInfos) {
20689
20799
  const serviceWorkerResourcesWithoutSwScriptItSelf = {
20690
20800
  ...serviceWorkerResources,
@@ -20724,48 +20834,48 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
20724
20834
  const buildRelativeUrl = urlToRelativeUrl(url, buildDirectoryUrl);
20725
20835
  return buildRelativeUrl;
20726
20836
  };
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;
20837
+ GRAPH_VISITOR.forEachUrlInfoStronglyReferenced(
20838
+ finalKitchen.graph.rootUrlInfo,
20839
+ (urlInfo) => {
20840
+ if (!urlInfo.url.startsWith("file:")) {
20841
+ return;
20842
+ }
20843
+ if (urlInfo.type === "directory") {
20844
+ return;
20845
+ }
20846
+ if (urlInfo.isInline) {
20847
+ const buildRelativeUrl = getBuildRelativeUrl(urlInfo.url);
20848
+ buildContents[buildRelativeUrl] = urlInfo.content;
20849
+ buildInlineRelativeUrls.push(buildRelativeUrl);
20850
+ } else {
20851
+ const buildRelativeUrl = getBuildRelativeUrl(urlInfo.url);
20852
+ if (
20853
+ buildVersionsManager.getVersion(urlInfo) &&
20854
+ canUseVersionedUrl(urlInfo)
20855
+ ) {
20856
+ const buildSpecifier = findKey(buildSpecifierMap, urlInfo.url);
20857
+ const buildSpecifierVersioned =
20858
+ buildVersionsManager.getBuildSpecifierVersioned(buildSpecifier);
20859
+ const buildUrlVersioned = asBuildUrlVersioned({
20860
+ buildSpecifierVersioned,
20861
+ buildDirectoryUrl,
20862
+ });
20863
+ const buildRelativeUrlVersioned = urlToRelativeUrl(
20864
+ buildUrlVersioned,
20865
+ buildDirectoryUrl,
20866
+ );
20867
+ if (versioningMethod === "search_param") {
20868
+ buildContents[buildRelativeUrl] = urlInfo.content;
20869
+ } else {
20870
+ buildContents[buildRelativeUrlVersioned] = urlInfo.content;
20871
+ }
20872
+ buildManifest[buildRelativeUrl] = buildRelativeUrlVersioned;
20760
20873
  } else {
20761
- buildContents[buildRelativeUrlVersioned] = urlInfo.content;
20874
+ buildContents[buildRelativeUrl] = urlInfo.content;
20762
20875
  }
20763
- buildManifest[buildRelativeUrl] = buildRelativeUrlVersioned;
20764
- } else {
20765
- buildContents[buildRelativeUrl] = urlInfo.content;
20766
20876
  }
20767
- }
20768
- });
20877
+ },
20878
+ );
20769
20879
  const buildFileContents = {};
20770
20880
  const buildInlineContents = {};
20771
20881
  Object.keys(buildContents)
@@ -20800,7 +20910,9 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
20800
20910
  writingFiles.done();
20801
20911
  }
20802
20912
  logger.info(
20803
- createUrlGraphSummary(finalKitchen.graph, { title: "build files" }),
20913
+ createUrlGraphSummary(finalKitchen.graph, {
20914
+ title: "build files",
20915
+ }),
20804
20916
  );
20805
20917
  return {
20806
20918
  buildFileContents,
@@ -21020,6 +21132,7 @@ const createFileService = ({
21020
21132
  serverStopCallbacks,
21021
21133
  serverEventsDispatcher,
21022
21134
  kitchenCache,
21135
+ onKitchenCreated = () => {},
21023
21136
 
21024
21137
  sourceDirectoryUrl,
21025
21138
  sourceMainFilePath,
@@ -21035,8 +21148,6 @@ const createFileService = ({
21035
21148
  supervisor,
21036
21149
  transpilation,
21037
21150
  clientAutoreload,
21038
- cooldownBetweenFileEvents,
21039
- clientServerEventsConfig,
21040
21151
  cacheControl,
21041
21152
  ribbon,
21042
21153
  sourcemaps,
@@ -21044,19 +21155,32 @@ const createFileService = ({
21044
21155
  sourcemapsSourcesContent,
21045
21156
  outDirectoryUrl,
21046
21157
  }) => {
21047
- const clientFileChangeCallbackList = [];
21048
- const clientFilesPruneCallbackList = [];
21158
+ if (clientAutoreload === true) {
21159
+ clientAutoreload = {};
21160
+ }
21161
+ if (clientAutoreload === false) {
21162
+ clientAutoreload = { enabled: false };
21163
+ }
21164
+ const clientFileChangeEventEmitter = createEventEmitter();
21165
+ const clientFileDereferencedEventEmitter = createEventEmitter();
21166
+
21167
+ clientAutoreload = {
21168
+ enabled: true,
21169
+ clientServerEventsConfig: {},
21170
+ clientFileChangeEventEmitter,
21171
+ clientFileDereferencedEventEmitter,
21172
+ ...clientAutoreload,
21173
+ };
21174
+
21049
21175
  const stopWatchingSourceFiles = watchSourceFiles(
21050
21176
  sourceDirectoryUrl,
21051
21177
  (fileInfo) => {
21052
- clientFileChangeCallbackList.forEach((callback) => {
21053
- callback(fileInfo);
21054
- });
21178
+ clientFileChangeEventEmitter.emit(fileInfo);
21055
21179
  },
21056
21180
  {
21057
21181
  sourceFilesConfig,
21058
21182
  keepProcessAlive: false,
21059
- cooldownBetweenFileEvents,
21183
+ cooldownBetweenFileEvents: clientAutoreload.cooldownBetweenFileEvents,
21060
21184
  },
21061
21185
  );
21062
21186
  serverStopCallbacks.push(stopWatchingSourceFiles);
@@ -21075,10 +21199,10 @@ const createFileService = ({
21075
21199
  sourceDirectoryUrl,
21076
21200
  );
21077
21201
  let kitchen;
21078
- clientFileChangeCallbackList.push(({ url }) => {
21202
+ clientFileChangeEventEmitter.on(({ url }) => {
21079
21203
  const urlInfo = kitchen.graph.getUrlInfo(url);
21080
21204
  if (urlInfo) {
21081
- urlInfo.considerModified();
21205
+ urlInfo.onModified();
21082
21206
  }
21083
21207
  });
21084
21208
  const clientRuntimeCompat = { [runtimeName]: runtimeVersion };
@@ -21114,8 +21238,6 @@ const createFileService = ({
21114
21238
  transpilation,
21115
21239
 
21116
21240
  clientAutoreload,
21117
- clientFileChangeCallbackList,
21118
- clientFilesPruneCallbackList,
21119
21241
  cacheControl,
21120
21242
  ribbon,
21121
21243
  }),
@@ -21129,18 +21251,18 @@ const createFileService = ({
21129
21251
  ? new URL(`${runtimeName}@${runtimeVersion}/`, outDirectoryUrl)
21130
21252
  : undefined,
21131
21253
  });
21132
- kitchen.graph.createUrlInfoCallbackRef.current = (urlInfo) => {
21254
+ kitchen.graph.urlInfoCreatedEventEmitter.on((urlInfoCreated) => {
21133
21255
  const { watch } = URL_META.applyAssociations({
21134
- url: urlInfo.url,
21256
+ url: urlInfoCreated.url,
21135
21257
  associations: watchAssociations,
21136
21258
  });
21137
- urlInfo.isWatched = watch;
21259
+ urlInfoCreated.isWatched = watch;
21138
21260
  // wehn an url depends on many others, we check all these (like package.json)
21139
- urlInfo.isValid = () => {
21140
- if (!urlInfo.url.startsWith("file:")) {
21261
+ urlInfoCreated.isValid = () => {
21262
+ if (!urlInfoCreated.url.startsWith("file:")) {
21141
21263
  return false;
21142
21264
  }
21143
- if (urlInfo.content === undefined) {
21265
+ if (urlInfoCreated.content === undefined) {
21144
21266
  // urlInfo content is undefined when:
21145
21267
  // - url info content never fetched
21146
21268
  // - it is considered as modified because undelying file is watched and got saved
@@ -21152,21 +21274,20 @@ const createFileService = ({
21152
21274
  // file is not watched, check the filesystem
21153
21275
  let fileContentAsBuffer;
21154
21276
  try {
21155
- fileContentAsBuffer = readFileSync(new URL(urlInfo.url));
21277
+ fileContentAsBuffer = readFileSync(new URL(urlInfoCreated.url));
21156
21278
  } catch (e) {
21157
21279
  if (e.code === "ENOENT") {
21158
- urlInfo.considerModified();
21159
- urlInfo.deleteFromGraph();
21280
+ urlInfoCreated.onModified();
21160
21281
  return false;
21161
21282
  }
21162
21283
  return false;
21163
21284
  }
21164
21285
  const fileContentEtag = bufferToEtag$1(fileContentAsBuffer);
21165
- if (fileContentEtag !== urlInfo.originalContentEtag) {
21166
- urlInfo.considerModified();
21286
+ if (fileContentEtag !== urlInfoCreated.originalContentEtag) {
21287
+ urlInfoCreated.onModified();
21167
21288
  // restore content to be able to compare it again later
21168
- urlInfo.kitchen.urlInfoTransformer.setContent(
21169
- urlInfo,
21289
+ urlInfoCreated.kitchen.urlInfoTransformer.setContent(
21290
+ urlInfoCreated,
21170
21291
  String(fileContentAsBuffer),
21171
21292
  {
21172
21293
  contentEtag: fileContentEtag,
@@ -21175,23 +21296,24 @@ const createFileService = ({
21175
21296
  return false;
21176
21297
  }
21177
21298
  }
21178
- for (const implicitUrl of urlInfo.implicitUrlSet) {
21179
- const implicitUrlInfo = kitchen.graph.getUrlInfo(implicitUrl);
21299
+ for (const implicitUrl of urlInfoCreated.implicitUrlSet) {
21300
+ const implicitUrlInfo = urlInfoCreated.graph.getUrlInfo(implicitUrl);
21180
21301
  if (implicitUrlInfo && !implicitUrlInfo.isValid()) {
21181
21302
  return false;
21182
21303
  }
21183
21304
  }
21184
21305
  return true;
21185
21306
  };
21186
- };
21187
- kitchen.graph.pruneUrlInfoCallbackRef.current = (
21188
- prunedUrlInfo,
21189
- lastReferenceFromOther,
21190
- ) => {
21191
- clientFilesPruneCallbackList.forEach((callback) => {
21192
- callback(prunedUrlInfo, lastReferenceFromOther);
21193
- });
21194
- };
21307
+ });
21308
+ kitchen.graph.urlInfoDereferencedEventEmitter.on(
21309
+ (urlInfoDereferenced, lastReferenceFromOther) => {
21310
+ clientFileDereferencedEventEmitter.emit(
21311
+ urlInfoDereferenced,
21312
+ lastReferenceFromOther,
21313
+ );
21314
+ },
21315
+ );
21316
+
21195
21317
  serverStopCallbacks.push(() => {
21196
21318
  kitchen.pluginController.callHooks("destroy", kitchen.context);
21197
21319
  });
@@ -21224,12 +21346,15 @@ const createFileService = ({
21224
21346
  });
21225
21347
  // "pushPlugin" so that event source client connection can be put as early as possible in html
21226
21348
  kitchen.pluginController.pushPlugin(
21227
- jsenvPluginServerEventsClientInjection(clientServerEventsConfig),
21349
+ jsenvPluginServerEventsClientInjection(
21350
+ clientAutoreload.clientServerEventsConfig,
21351
+ ),
21228
21352
  );
21229
21353
  }
21230
21354
  }
21231
21355
 
21232
21356
  kitchenCache.set(runtimeId, kitchen);
21357
+ onKitchenCreated(kitchen);
21233
21358
  return kitchen;
21234
21359
  };
21235
21360
 
@@ -21247,31 +21372,28 @@ const createFileService = ({
21247
21372
  if (responseFromPlugin) {
21248
21373
  return responseFromPlugin;
21249
21374
  }
21250
- let reference;
21251
- const parentUrl = inferParentFromRequest(request, sourceDirectoryUrl);
21252
- if (parentUrl) {
21253
- reference = kitchen.graph.inferReference(request.resource, parentUrl);
21254
- }
21375
+ const { referer } = request.headers;
21376
+ const parentUrl = referer
21377
+ ? WEB_URL_CONVERTER.asFileUrl(referer, {
21378
+ origin: request.origin,
21379
+ rootDirectoryUrl: sourceDirectoryUrl,
21380
+ })
21381
+ : sourceDirectoryUrl;
21382
+ let reference = kitchen.graph.inferReference(request.resource, parentUrl);
21255
21383
  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
- });
21384
+ reference =
21385
+ kitchen.graph.rootUrlInfo.dependencies.createResolveAndFinalize({
21386
+ trace: { message: parentUrl },
21387
+ type: "http_request",
21388
+ specifier: request.resource,
21389
+ });
21268
21390
  }
21269
21391
  const urlInfo = reference.urlInfo;
21270
21392
  const ifNoneMatch = request.headers["if-none-match"];
21271
21393
  const urlInfoTargetedByCache = urlInfo.findParentIfInline() || urlInfo;
21272
21394
 
21273
21395
  try {
21274
- if (ifNoneMatch) {
21396
+ if (!urlInfo.error && ifNoneMatch) {
21275
21397
  const [clientOriginalContentEtag, clientContentEtag] =
21276
21398
  ifNoneMatch.split("_");
21277
21399
  if (
@@ -21319,7 +21441,7 @@ const createFileService = ({
21319
21441
  }),
21320
21442
  ...urlInfo.headers,
21321
21443
  "content-type": urlInfo.contentType,
21322
- "content-length": Buffer.byteLength(urlInfo.content),
21444
+ "content-length": urlInfo.contentLength,
21323
21445
  },
21324
21446
  body: urlInfo.content,
21325
21447
  timing: urlInfo.timing,
@@ -21358,7 +21480,7 @@ const createFileService = ({
21358
21480
  statusMessage: originalError.message,
21359
21481
  headers: {
21360
21482
  "content-type": urlInfo.contentType,
21361
- "content-length": Buffer.byteLength(urlInfo.content),
21483
+ "content-length": urlInfo.contentLength,
21362
21484
  "cache-control": "no-store",
21363
21485
  },
21364
21486
  body: urlInfo.content,
@@ -21405,30 +21527,10 @@ const createFileService = ({
21405
21527
  statusText: e.reason,
21406
21528
  statusMessage: e.stack,
21407
21529
  };
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
21530
  }
21417
21531
  };
21418
21532
  };
21419
21533
 
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
21534
  /**
21433
21535
  * Start a server for source files:
21434
21536
  * - cook source files according to jsenv plugins
@@ -21459,8 +21561,6 @@ const startDevServer = async ({
21459
21561
 
21460
21562
  sourceFilesConfig,
21461
21563
  clientAutoreload = true,
21462
- cooldownBetweenFileEvents,
21463
- clientServerEventsConfig = {},
21464
21564
 
21465
21565
  // runtimeCompat is the runtimeCompat for the build
21466
21566
  // when specified, dev server use it to warn in case
@@ -21476,6 +21576,7 @@ const startDevServer = async ({
21476
21576
  cacheControl = true,
21477
21577
  ribbon = true,
21478
21578
  // toolbar = false,
21579
+ onKitchenCreated = () => {},
21479
21580
 
21480
21581
  sourcemaps = "inline",
21481
21582
  sourcemapsSourcesProtocol,
@@ -21600,6 +21701,7 @@ const startDevServer = async ({
21600
21701
  serverStopCallbacks,
21601
21702
  serverEventsDispatcher,
21602
21703
  kitchenCache,
21704
+ onKitchenCreated,
21603
21705
 
21604
21706
  sourceDirectoryUrl,
21605
21707
  sourceMainFilePath,
@@ -21615,8 +21717,6 @@ const startDevServer = async ({
21615
21717
  supervisor,
21616
21718
  transpilation,
21617
21719
  clientAutoreload,
21618
- cooldownBetweenFileEvents,
21619
- clientServerEventsConfig,
21620
21720
  cacheControl,
21621
21721
  ribbon,
21622
21722
  sourcemaps,