@jsenv/core 37.0.4 → 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;
@@ -11423,14 +11474,15 @@ const createUrlGraph = ({
11423
11474
  const referencedUrl = useGeneratedUrl
11424
11475
  ? reference.generatedUrl
11425
11476
  : reference.url;
11426
- const existingUrlInfo = getUrlInfo(referencedUrl);
11427
- if (existingUrlInfo) return existingUrlInfo;
11428
- const ownerUrlInfo = reference.ownerUrlInfo;
11429
- const ownerContext = ownerUrlInfo.context;
11430
- const context = Object.create(ownerContext);
11431
- const referencedUrlInfo = createUrlInfo(referencedUrl, context);
11432
- addUrlInfo(referencedUrlInfo);
11433
- createUrlInfoCallbackRef.current(referencedUrlInfo);
11477
+ let referencedUrlInfo = getUrlInfo(referencedUrl);
11478
+ if (!referencedUrlInfo) {
11479
+ const ownerUrlInfo = reference.ownerUrlInfo;
11480
+ const ownerContext = ownerUrlInfo.context;
11481
+ const context = Object.create(ownerContext);
11482
+ referencedUrlInfo = createUrlInfo(referencedUrl, context);
11483
+ addUrlInfo(referencedUrlInfo);
11484
+ urlInfoCreatedEventEmitter.emit(referencedUrlInfo);
11485
+ }
11434
11486
  if (referencedUrlInfo.searchParams.size > 0 && !kitchen.context.shape) {
11435
11487
  // A resource is represented by a url.
11436
11488
  // Variations of a resource are represented by url search params
@@ -11506,17 +11558,16 @@ const createUrlGraph = ({
11506
11558
  Object.assign(urlGraph, {
11507
11559
  name,
11508
11560
  rootUrlInfo,
11509
- createUrlInfoCallbackRef,
11510
- pruneUrlInfoCallbackRef,
11511
11561
 
11512
11562
  urlInfoMap,
11513
11563
  reuseOrCreateUrlInfo,
11514
11564
  hasUrlInfo,
11515
11565
  getUrlInfo,
11516
- deleteUrlInfo,
11517
11566
  getEntryPoints,
11518
11567
 
11519
11568
  inferReference,
11569
+ urlInfoCreatedEventEmitter,
11570
+ urlInfoDereferencedEventEmitter,
11520
11571
 
11521
11572
  toObject: () => {
11522
11573
  const data = {};
@@ -11554,6 +11605,7 @@ const createUrlInfo = (url, context) => {
11554
11605
  context,
11555
11606
  error: null,
11556
11607
  modifiedTimestamp: 0,
11608
+ dereferencedTimestamp: 0,
11557
11609
  originalContentEtag: null,
11558
11610
  contentEtag: null,
11559
11611
  isWatched: false,
@@ -11578,6 +11630,7 @@ const createUrlInfo = (url, context) => {
11578
11630
  originalContentAst: undefined,
11579
11631
  content: undefined,
11580
11632
  contentAst: undefined,
11633
+ contentLength: undefined,
11581
11634
  contentFinalized: false,
11582
11635
 
11583
11636
  sourcemap: null,
@@ -11604,37 +11657,24 @@ const createUrlInfo = (url, context) => {
11604
11657
  urlInfo.searchParams = new URL(url).searchParams;
11605
11658
 
11606
11659
  urlInfo.dependencies = createDependencies(urlInfo);
11607
- urlInfo.getFirstReferenceFromOther = ({ ignoreWeak } = {}) => {
11608
- for (const referenceFromOther of urlInfo.referenceFromOthersSet) {
11609
- if (referenceFromOther.url === urlInfo.url) {
11610
- if (
11611
- !referenceFromOther.isInline &&
11612
- referenceFromOther.next &&
11613
- referenceFromOther.next.isInline
11614
- ) {
11615
- // the url info was inlined, an other reference is required
11616
- // to consider the non-inlined urlInfo as used
11617
- continue;
11618
- }
11619
- if (ignoreWeak && referenceFromOther.isWeak) {
11620
- // weak reference don't count as using the url
11621
- continue;
11622
- }
11623
- return referenceFromOther;
11624
- }
11625
- }
11626
- return null;
11627
- };
11628
11660
  urlInfo.isUsed = () => {
11629
11661
  if (urlInfo.isRoot) {
11630
11662
  return true;
11631
11663
  }
11632
- // if (urlInfo.type === "sourcemap") {
11633
- // return true;
11634
- // }
11635
- // check if there is a strong reference to this urlInfo
11636
- if (urlInfo.getFirstReferenceFromOther({ ignoreWeak: true })) {
11637
- 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();
11638
11678
  }
11639
11679
  // nothing uses this url anymore
11640
11680
  // - versioning update inline content
@@ -11724,7 +11764,7 @@ const createUrlInfo = (url, context) => {
11724
11764
  reference.next = referenceWithoutSearchParam;
11725
11765
  return referenceWithoutSearchParam.urlInfo;
11726
11766
  };
11727
- urlInfo.considerModified = ({ modifiedTimestamp = Date.now() } = {}) => {
11767
+ urlInfo.onModified = ({ modifiedTimestamp = Date.now() } = {}) => {
11728
11768
  const visitedSet = new Set();
11729
11769
  const iterate = (urlInfo) => {
11730
11770
  if (visitedSet.has(urlInfo)) {
@@ -11745,9 +11785,29 @@ const createUrlInfo = (url, context) => {
11745
11785
  };
11746
11786
  iterate(urlInfo);
11747
11787
  };
11748
- urlInfo.deleteFromGraph = (reference) => {
11749
- 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
+ );
11750
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
+ // };
11751
11811
  urlInfo.cook = (customContext) => {
11752
11812
  return urlInfo.context.cook(urlInfo, customContext);
11753
11813
  };
@@ -12230,6 +12290,15 @@ const defineGettersOnPropertiesDerivedFromOriginalContent = (
12230
12290
  };
12231
12291
 
12232
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
+ }
12233
12302
  const contentAstDescriptor = Object.getOwnPropertyDescriptor(
12234
12303
  urlInfo,
12235
12304
  "contentAst",
@@ -12316,11 +12385,6 @@ const createUrlInfoTransformer = ({
12316
12385
  sourcemapsSourcesContent = true;
12317
12386
  }
12318
12387
 
12319
- const sourcemapsEnabled =
12320
- sourcemaps === "inline" ||
12321
- sourcemaps === "file" ||
12322
- sourcemaps === "programmatic";
12323
-
12324
12388
  const normalizeSourcemap = (urlInfo, sourcemap) => {
12325
12389
  let { sources } = sourcemap;
12326
12390
  if (sources) {
@@ -12363,15 +12427,10 @@ const createUrlInfoTransformer = ({
12363
12427
  urlInfo.originalContentEtag = undefined;
12364
12428
  urlInfo.contentAst = undefined;
12365
12429
  urlInfo.contentEtag = undefined;
12430
+ urlInfo.contentLength = undefined;
12366
12431
  urlInfo.content = undefined;
12367
12432
  urlInfo.sourcemap = null;
12368
12433
  urlInfo.sourcemapIsWrong = null;
12369
- urlInfo.referenceToOthersSet.forEach((referenceToOther) => {
12370
- const referencedUrlInfo = referenceToOther.urlInfo;
12371
- if (referencedUrlInfo.isInline) {
12372
- referencedUrlInfo.deleteFromGraph();
12373
- }
12374
- });
12375
12434
  };
12376
12435
 
12377
12436
  const setContent = async (
@@ -12380,6 +12439,7 @@ const createUrlInfoTransformer = ({
12380
12439
  {
12381
12440
  contentAst, // most of the time will be undefined
12382
12441
  contentEtag, // in practice it's always undefined
12442
+ contentLength,
12383
12443
  originalContent = content,
12384
12444
  originalContentAst, // most of the time will be undefined
12385
12445
  originalContentEtag, // in practice always undefined
@@ -12395,17 +12455,12 @@ const createUrlInfoTransformer = ({
12395
12455
 
12396
12456
  urlInfo.contentAst = contentAst;
12397
12457
  urlInfo.contentEtag = contentEtag;
12458
+ urlInfo.contentLength = contentLength;
12398
12459
  urlInfo.content = content;
12399
12460
  defineGettersOnPropertiesDerivedFromContent(urlInfo);
12400
12461
 
12401
12462
  urlInfo.sourcemap = sourcemap;
12402
- if (!sourcemapsEnabled) {
12403
- return;
12404
- }
12405
- if (!SOURCEMAP.enabledOnContentType(urlInfo.contentType)) {
12406
- return;
12407
- }
12408
- if (urlInfo.generatedUrl.startsWith("data:")) {
12463
+ if (!shouldHandleSourcemap(urlInfo)) {
12409
12464
  return;
12410
12465
  }
12411
12466
  // sourcemap is a special kind of reference:
@@ -12468,6 +12523,7 @@ const createUrlInfoTransformer = ({
12468
12523
  content,
12469
12524
  contentAst, // undefined most of the time
12470
12525
  contentEtag, // in practice always undefined
12526
+ contentLength,
12471
12527
  sourcemap,
12472
12528
  sourcemapIsWrong,
12473
12529
  } = transformations;
@@ -12481,10 +12537,11 @@ const createUrlInfoTransformer = ({
12481
12537
  if (contentModified) {
12482
12538
  urlInfo.contentAst = contentAst;
12483
12539
  urlInfo.contentEtag = contentEtag;
12540
+ urlInfo.contentLength = contentLength;
12484
12541
  urlInfo.content = content;
12485
12542
  defineGettersOnPropertiesDerivedFromContent(urlInfo);
12486
12543
  }
12487
- if (sourcemapsEnabled && sourcemap) {
12544
+ if (sourcemap && shouldHandleSourcemap(urlInfo)) {
12488
12545
  const sourcemapNormalized = normalizeSourcemap(urlInfo, sourcemap);
12489
12546
  const finalSourcemap = composeTwoSourcemaps(
12490
12547
  urlInfo.sourcemap,
@@ -12560,13 +12617,7 @@ const createUrlInfoTransformer = ({
12560
12617
  };
12561
12618
 
12562
12619
  const applySourcemapOnContent = (urlInfo) => {
12563
- if (!sourcemapsEnabled) {
12564
- return;
12565
- }
12566
- if (!urlInfo.sourcemap) {
12567
- return;
12568
- }
12569
- if (urlInfo.generatedUrl.startsWith("data:")) {
12620
+ if (!urlInfo.sourcemap || !shouldHandleSourcemap(urlInfo)) {
12570
12621
  return;
12571
12622
  }
12572
12623
 
@@ -12652,6 +12703,24 @@ const createUrlInfoTransformer = ({
12652
12703
  };
12653
12704
  };
12654
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
+
12655
12724
  const createResolveUrlError = ({
12656
12725
  pluginController,
12657
12726
  reference,
@@ -13488,12 +13557,10 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
13488
13557
  // "cooked" hook
13489
13558
  pluginController.callHooks("cooked", urlInfo, (cookedReturnValue) => {
13490
13559
  if (typeof cookedReturnValue === "function") {
13491
- const prevCallback = graph.pruneUrlInfoCallbackRef.current;
13492
- graph.pruneUrlInfoCallbackRef.current(
13493
- (prunedUrlInfo, lastReferenceFromOther) => {
13494
- prevCallback();
13495
- if (prunedUrlInfo === urlInfo.url) {
13496
- graph.pruneUrlInfoCallbackRef.current = prevCallback;
13560
+ const removeCallback = urlInfo.graph.urlInfoDereferencedEventEmitter.on(
13561
+ (urlInfoDereferenced, lastReferenceFromOther) => {
13562
+ if (urlInfoDereferenced === urlInfo) {
13563
+ removeCallback();
13497
13564
  cookedReturnValue(lastReferenceFromOther.urlInfo);
13498
13565
  }
13499
13566
  },
@@ -13725,67 +13792,69 @@ const createUrlGraphReport = (urlGraph) => {
13725
13792
  other: 0,
13726
13793
  total: 0,
13727
13794
  };
13728
- urlGraph.urlInfoMap.forEach((urlInfo) => {
13729
- if (urlInfo.isRoot) {
13730
- return;
13731
- }
13732
- // ignore:
13733
- // - ignored files: we don't know their content
13734
- // - inline files and data files: they are already taken into account in the file where they appear
13735
- if (urlInfo.url.startsWith("ignore:")) {
13736
- return;
13737
- }
13738
- if (urlInfo.isInline) {
13739
- return;
13740
- }
13741
- if (urlInfo.url.startsWith("data:")) {
13742
- return;
13743
- }
13744
13795
 
13745
- // file loaded via import assertion are already inside the graph
13746
- // their js module equivalent are ignored to avoid counting it twice
13747
- // in the build graph the file targeted by import assertion will likely be gone
13748
- // and only the js module remain (likely bundled)
13749
- if (
13750
- urlInfo.searchParams.has("as_json_module") ||
13751
- urlInfo.searchParams.has("as_css_module") ||
13752
- urlInfo.searchParams.has("as_text_module")
13753
- ) {
13754
- return;
13755
- }
13756
- const urlContentSize = Buffer.byteLength(urlInfo.content);
13757
- const category = determineCategory(urlInfo);
13758
- if (category === "sourcemap") {
13759
- countGroups.sourcemaps++;
13760
- sizeGroups.sourcemaps += urlContentSize;
13761
- return;
13762
- }
13763
- countGroups.total++;
13764
- sizeGroups.total += urlContentSize;
13765
- if (category === "html") {
13766
- countGroups.html++;
13767
- sizeGroups.html += urlContentSize;
13768
- return;
13769
- }
13770
- if (category === "css") {
13771
- countGroups.css++;
13772
- sizeGroups.css += urlContentSize;
13773
- return;
13774
- }
13775
- if (category === "js") {
13776
- countGroups.js++;
13777
- sizeGroups.js += urlContentSize;
13778
- return;
13779
- }
13780
- if (category === "json") {
13781
- countGroups.json++;
13782
- 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;
13783
13855
  return;
13784
- }
13785
- countGroups.other++;
13786
- sizeGroups.other += urlContentSize;
13787
- return;
13788
- });
13856
+ },
13857
+ );
13789
13858
 
13790
13859
  const sizesToDistribute = {};
13791
13860
  Object.keys(sizeGroups).forEach((groupName) => {
@@ -18193,27 +18262,20 @@ import.meta.hot = createImportMetaHot(import.meta.url);
18193
18262
  return magicSource.toContentAndSourcemap();
18194
18263
  };
18195
18264
 
18196
- const jsenvPluginHotSearchParam = () => {
18197
- const shouldInjectHotSearchParam = (reference) => {
18198
- if (reference.isImplicit) {
18199
- return false;
18200
- }
18201
- if (reference.original && reference.original.searchParams.has("hot")) {
18202
- return true;
18203
- }
18204
- // parent is using ?hot -> propagate
18205
- const { ownerUrlInfo } = reference;
18206
- const lastReference = ownerUrlInfo.context.reference;
18207
- if (
18208
- lastReference &&
18209
- lastReference.original &&
18210
- lastReference.original.searchParams.has("hot")
18211
- ) {
18212
- return true;
18213
- }
18214
- return false;
18215
- };
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
+ */
18216
18277
 
18278
+ const jsenvPluginHotSearchParam = () => {
18217
18279
  return {
18218
18280
  name: "jsenv:hot_search_param",
18219
18281
  appliesDuring: "dev",
@@ -18227,20 +18289,45 @@ const jsenvPluginHotSearchParam = () => {
18227
18289
  // We get rid of this params so that urlGraph and other parts of the code
18228
18290
  // recognize the url (it is not considered as a different url)
18229
18291
  urlObject.searchParams.delete("hot");
18230
- urlObject.searchParams.delete("v");
18231
18292
  return urlObject.href;
18232
18293
  },
18233
18294
  transformReferenceSearchParams: (reference) => {
18234
- if (!shouldInjectHotSearchParam(reference)) {
18295
+ if (reference.isImplicit) {
18296
+ return null;
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) {
18235
18306
  return null;
18236
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.
18237
18310
  const referencedUrlInfo = reference.urlInfo;
18238
- if (!referencedUrlInfo.modifiedTimestamp) {
18311
+ const { modifiedTimestamp, dereferencedTimestamp } = referencedUrlInfo;
18312
+ if (!modifiedTimestamp && !dereferencedTimestamp) {
18239
18313
  return null;
18240
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;
18241
18329
  return {
18242
- hot: "",
18243
- v: referencedUrlInfo.modifiedTimestamp,
18330
+ hot: latestTimestamp,
18244
18331
  };
18245
18332
  },
18246
18333
  };
@@ -18283,8 +18370,8 @@ const jsenvPluginAutoreloadClient = () => {
18283
18370
  };
18284
18371
 
18285
18372
  const jsenvPluginAutoreloadServer = ({
18286
- clientFileChangeCallbackList,
18287
- clientFilesPruneCallbackList,
18373
+ clientFileChangeEventEmitter,
18374
+ clientFileDereferencedEventEmitter,
18288
18375
  }) => {
18289
18376
  return {
18290
18377
  name: "jsenv:autoreload_server",
@@ -18300,29 +18387,7 @@ const jsenvPluginAutoreloadServer = ({
18300
18387
  }
18301
18388
  return url;
18302
18389
  };
18303
- const notifyFullReload = ({ cause, reason, declinedBy }) => {
18304
- serverEventInfo.sendServerEvent({
18305
- cause,
18306
- type: "full",
18307
- typeReason: reason,
18308
- declinedBy,
18309
- });
18310
- };
18311
- const notifyPartialReload = ({ cause, reason, instructions }) => {
18312
- serverEventInfo.sendServerEvent({
18313
- cause,
18314
- type: "hot",
18315
- typeReason: reason,
18316
- hotInstructions: instructions,
18317
- });
18318
- };
18319
18390
  const propagateUpdate = (firstUrlInfo) => {
18320
- if (!serverEventInfo.kitchen.graph.getUrlInfo(firstUrlInfo.url)) {
18321
- return {
18322
- declined: true,
18323
- reason: `url not in the url graph`,
18324
- };
18325
- }
18326
18391
  const iterate = (urlInfo, seen) => {
18327
18392
  if (urlInfo.data.hotAcceptSelf) {
18328
18393
  return {
@@ -18401,86 +18466,150 @@ const jsenvPluginAutoreloadServer = ({
18401
18466
  const seen = [];
18402
18467
  return iterate(firstUrlInfo, seen);
18403
18468
  };
18404
- clientFileChangeCallbackList.push(({ url, event }) => {
18405
- const onUrlInfo = (urlInfo) => {
18406
- if (!urlInfo.isUsed()) {
18407
- return false;
18408
- }
18409
- const relativeUrl = formatUrlForClient(urlInfo.url);
18410
- const hotUpdate = propagateUpdate(urlInfo);
18411
- if (hotUpdate.declined) {
18412
- notifyFullReload({
18413
- cause: `${relativeUrl} ${event}`,
18414
- reason: hotUpdate.reason,
18415
- declinedBy: hotUpdate.declinedBy,
18416
- });
18417
- return true;
18418
- }
18419
- notifyPartialReload({
18420
- cause: `${relativeUrl} ${event}`,
18421
- reason: hotUpdate.reason,
18422
- instructions: hotUpdate.instructions,
18423
- });
18424
- return true;
18425
- };
18426
18469
 
18427
- const urlInfo = serverEventInfo.kitchen.graph.getUrlInfo(url);
18428
- if (urlInfo) {
18429
- if (onUrlInfo(urlInfo)) {
18430
- 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;
18431
18525
  }
18432
- for (const searchParamVariant of urlInfo.searchParamVariantSet) {
18433
- if (onUrlInfo(searchParamVariant)) {
18434
- 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
+ };
18435
18582
  }
18436
18583
  }
18437
18584
  }
18438
- });
18439
- clientFilesPruneCallbackList.push(
18440
- (prunedUrlInfo, lastReferenceFromOther) => {
18441
- if (lastReferenceFromOther.type === "sourcemap_comment") {
18442
- // Can happen when starting dev server with sourcemaps: "file"
18443
- // In that case, as sourcemaps are injected, the reference
18444
- // are lost and sourcemap is considered as pruned
18445
- return;
18446
- }
18447
- const parentHotUpdate = propagateUpdate(
18448
- lastReferenceFromOther.ownerUrlInfo,
18449
- );
18450
- const cause = `following file is no longer referenced: ${formatUrlForClient(
18451
- prunedUrlInfo.url,
18452
- )}`;
18453
- // now check if we can hot update the parent resource
18454
- // then if we can hot update all dependencies
18455
- if (parentHotUpdate.declined) {
18456
- notifyFullReload({
18457
- cause,
18458
- reason: parentHotUpdate.reason,
18459
- declinedBy: parentHotUpdate.declinedBy,
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,
18460
18603
  });
18461
- return;
18462
18604
  }
18463
- // parent can hot update
18464
- const instructions = [];
18465
- if (prunedUrlInfo.data.hotDecline) {
18466
- notifyFullReload({
18467
- cause,
18468
- reason: `a pruned file declines hot reload`,
18469
- declinedBy: formatUrlForClient(prunedUrlInfo.url),
18470
- });
18471
- return;
18472
- }
18473
- instructions.push({
18605
+ }
18606
+ });
18607
+ clientFileDereferencedEventEmitter.on(
18608
+ (prunedUrlInfo, lastReferenceFromOther) => {
18609
+ delayAction({
18474
18610
  type: "prune",
18475
- boundary: formatUrlForClient(prunedUrlInfo.url),
18476
- acceptedBy: formatUrlForClient(
18477
- lastReferenceFromOther.ownerUrlInfo.url,
18478
- ),
18479
- });
18480
- notifyPartialReload({
18481
- cause,
18482
- reason: parentHotUpdate.reason,
18483
- instructions,
18611
+ prunedUrlInfo,
18612
+ lastReferenceFromOther,
18484
18613
  });
18485
18614
  },
18486
18615
  );
@@ -18506,15 +18635,15 @@ const jsenvPluginAutoreloadServer = ({
18506
18635
  };
18507
18636
 
18508
18637
  const jsenvPluginAutoreload = ({
18509
- clientFileChangeCallbackList,
18510
- clientFilesPruneCallbackList,
18638
+ clientFileChangeEventEmitter,
18639
+ clientFileDereferencedEventEmitter,
18511
18640
  }) => {
18512
18641
  return [
18513
18642
  jsenvPluginHotSearchParam(),
18514
18643
  jsenvPluginAutoreloadClient(),
18515
18644
  jsenvPluginAutoreloadServer({
18516
- clientFileChangeCallbackList,
18517
- clientFilesPruneCallbackList,
18645
+ clientFileChangeEventEmitter,
18646
+ clientFileDereferencedEventEmitter,
18518
18647
  }),
18519
18648
  ];
18520
18649
  };
@@ -18615,9 +18744,7 @@ const getCorePlugins = ({
18615
18744
  transpilation = true,
18616
18745
  inlining = true,
18617
18746
 
18618
- clientAutoreload = false,
18619
- clientFileChangeCallbackList,
18620
- clientFilesPruneCallbackList,
18747
+ clientAutoreload,
18621
18748
  cacheControl,
18622
18749
  scenarioPlaceholders = true,
18623
18750
  ribbon = true,
@@ -18628,9 +18755,6 @@ const getCorePlugins = ({
18628
18755
  if (supervisor === true) {
18629
18756
  supervisor = {};
18630
18757
  }
18631
- if (clientAutoreload === true) {
18632
- clientAutoreload = {};
18633
- }
18634
18758
  if (ribbon === true) {
18635
18759
  ribbon = {};
18636
18760
  }
@@ -18667,14 +18791,8 @@ const getCorePlugins = ({
18667
18791
  jsenvPluginNodeRuntime({ runtimeCompat }),
18668
18792
 
18669
18793
  jsenvPluginImportMetaHot(),
18670
- ...(clientAutoreload
18671
- ? [
18672
- jsenvPluginAutoreload({
18673
- ...clientAutoreload,
18674
- clientFileChangeCallbackList,
18675
- clientFilesPruneCallbackList,
18676
- }),
18677
- ]
18794
+ ...(clientAutoreload && clientAutoreload.enabled
18795
+ ? [jsenvPluginAutoreload(clientAutoreload)]
18678
18796
  : []),
18679
18797
  ...(cacheControl ? [jsenvPluginCacheControl(cacheControl)] : []),
18680
18798
  ...(ribbon ? [jsenvPluginRibbon({ rootDirectoryUrl, ...ribbon })] : []),
@@ -19188,63 +19306,60 @@ const createBuildVersionsManager = ({
19188
19306
 
19189
19307
  const contentOnlyVersionMap = new Map();
19190
19308
  {
19191
- GRAPH_VISITOR.forEach(finalKitchen.graph, (urlInfo) => {
19192
- // ignore:
19193
- // - inline files and data files:
19194
- // they are already taken into account in the file where they appear
19195
- // - ignored files:
19196
- // we don't know their content
19197
- // - unused files without reference
19198
- // File updated such as style.css -> style.css.js or file.js->file.nomodule.js
19199
- // Are used at some point just to be discarded later because they need to be converted
19200
- // There is no need to version them and we could not because the file have been ignored
19201
- // so their content is unknown
19202
- if (urlInfo.isRoot) {
19203
- return;
19204
- }
19205
- if (urlInfo.type === "sourcemap") {
19206
- return;
19207
- }
19208
- if (urlInfo.isInline) {
19209
- return;
19210
- }
19211
- if (urlInfo.url.startsWith("data:")) {
19212
- // urlInfo became inline and is not referenced by something else
19213
- return;
19214
- }
19215
- if (urlInfo.url.startsWith("ignore:")) {
19216
- return;
19217
- }
19218
- if (!urlInfo.isUsed()) {
19219
- return;
19220
- }
19221
- let content = urlInfo.content;
19222
- if (urlInfo.type === "html") {
19223
- content = stringifyHtmlAst(
19224
- parseHtmlString(urlInfo.content, {
19225
- storeOriginalPositions: false,
19226
- }),
19227
- {
19228
- cleanupJsenvAttributes: true,
19229
- cleanupPositionAttributes: true,
19230
- },
19231
- );
19232
- }
19233
- if (
19234
- CONTENT_TYPE.isTextual(urlInfo.contentType) &&
19235
- urlInfo.referenceToOthersSet.size > 0
19236
- ) {
19237
- const containedPlaceholders = new Set();
19238
- const contentWithPredictibleVersionPlaceholders =
19239
- replaceWithDefaultAndPopulateContainedPlaceholders(
19240
- content,
19241
- 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
+ },
19242
19345
  );
19243
- content = contentWithPredictibleVersionPlaceholders;
19244
- }
19245
- const contentVersion = generateVersion([content], versionLength);
19246
- contentOnlyVersionMap.set(urlInfo, contentVersion);
19247
- });
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
+ );
19248
19363
  }
19249
19364
 
19250
19365
  {
@@ -19770,7 +19885,7 @@ build ${entryPointKeys.length} entry points`);
19770
19885
  sourcemaps,
19771
19886
  sourcemapsSourcesContent,
19772
19887
  outDirectoryUrl: outDirectoryUrl
19773
- ? new URL("build/", outDirectoryUrl)
19888
+ ? new URL("prebuild/", outDirectoryUrl)
19774
19889
  : undefined,
19775
19890
  });
19776
19891
 
@@ -20025,13 +20140,6 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
20025
20140
  rawUrlInfo.url,
20026
20141
  "raw file",
20027
20142
  );
20028
- if (buildUrl.includes("?")) {
20029
- associateBuildUrlAndRawUrl(
20030
- asUrlWithoutSearch(buildUrl),
20031
- rawUrlInfo.url,
20032
- "raw file",
20033
- );
20034
- }
20035
20143
  return buildUrl;
20036
20144
  }
20037
20145
  if (reference.type === "sourcemap_comment") {
@@ -20245,82 +20353,84 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
20245
20353
  bundler.urlInfoMap.set(rawUrlInfo.url, rawUrlInfo);
20246
20354
  }
20247
20355
  };
20248
- GRAPH_VISITOR.forEach(rawKitchen.graph, (rawUrlInfo) => {
20249
- // ignore unused urls (avoid bundling things that are not actually used)
20250
- // happens for:
20251
- // - js import assertions
20252
- // - conversion to js classic using ?as_js_classic or ?js_module_fallback
20253
- if (!rawUrlInfo.isUsed()) {
20254
- return;
20255
- }
20256
- if (rawUrlInfo.isEntryPoint) {
20257
- addToBundlerIfAny(rawUrlInfo);
20258
- }
20259
- if (rawUrlInfo.type === "html") {
20260
- rawUrlInfo.referenceToOthersSet.forEach((referenceToOther) => {
20261
- if (referenceToOther.isWeak) {
20262
- return;
20263
- }
20264
- const referencedUrlInfo = referenceToOther.urlInfo;
20265
- if (referencedUrlInfo.isInline) {
20266
- if (referencedUrlInfo.type === "js_module") {
20267
- // bundle inline script type module deps
20268
- referencedUrlInfo.referenceToOthersSet.forEach(
20269
- (jsModuleReferenceToOther) => {
20270
- if (jsModuleReferenceToOther.type === "js_import") {
20271
- const inlineUrlInfo = jsModuleReferenceToOther.urlInfo;
20272
- addToBundlerIfAny(inlineUrlInfo);
20273
- }
20274
- },
20275
- );
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;
20276
20371
  }
20277
- // inline content cannot be bundled
20278
- return;
20279
- }
20280
- addToBundlerIfAny(referencedUrlInfo);
20281
- });
20282
- rawUrlInfo.referenceToOthersSet.forEach((referenceToOther) => {
20283
- if (
20284
- referenceToOther.isResourceHint &&
20285
- referenceToOther.expectedType === "js_module"
20286
- ) {
20287
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) => {
20288
20392
  if (
20289
- referencedUrlInfo &&
20290
- // something else than the resource hint is using this url
20291
- referencedUrlInfo.referenceFromOthersSet.size > 0
20393
+ referenceToOther.isResourceHint &&
20394
+ referenceToOther.expectedType === "js_module"
20292
20395
  ) {
20293
- 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
+ }
20294
20404
  }
20295
- }
20296
- });
20297
- return;
20298
- }
20299
- // File referenced with new URL('./file.js', import.meta.url)
20300
- // are entry points that should be bundled
20301
- // For instance we will bundle service worker/workers detected like this
20302
- if (rawUrlInfo.type === "js_module") {
20303
- rawUrlInfo.referenceToOthersSet.forEach((referenceToOther) => {
20304
- if (referenceToOther.type === "js_url") {
20305
- const referencedUrlInfo = referenceToOther.urlInfo;
20306
- for (const referenceFromOther of referencedUrlInfo.referenceFromOthersSet) {
20307
- if (referenceFromOther.url === referencedUrlInfo.url) {
20308
- if (
20309
- referenceFromOther.subtype === "import_dynamic" ||
20310
- referenceFromOther.type === "script"
20311
- ) {
20312
- // will already be bundled
20313
- 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
+ }
20314
20424
  }
20315
20425
  }
20426
+ addToBundlerIfAny(referencedUrlInfo);
20427
+ return;
20316
20428
  }
20317
- addToBundlerIfAny(referencedUrlInfo);
20318
- return;
20319
- }
20320
- if (referenceToOther.type === "js_inline_content") ;
20321
- });
20322
- }
20323
- });
20429
+ if (referenceToOther.type === "js_inline_content") ;
20430
+ });
20431
+ }
20432
+ },
20433
+ );
20324
20434
  await Object.keys(bundlers).reduce(async (previous, type) => {
20325
20435
  await previous;
20326
20436
  const bundler = bundlers[type];
@@ -20630,24 +20740,14 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
20630
20740
  resyncTask.done();
20631
20741
  }
20632
20742
  }
20633
- {
20634
- const actions = [];
20635
- GRAPH_VISITOR.forEach(finalKitchen.graph, (urlInfo) => {
20636
- if (!urlInfo.isUsed()) {
20637
- actions.push(() => {
20638
- urlInfo.deleteFromGraph();
20639
- });
20640
- }
20641
- });
20642
- actions.forEach((action) => action());
20643
- }
20644
20743
  {
20645
20744
  const serviceWorkerEntryUrlInfos = GRAPH_VISITOR.filter(
20646
20745
  finalKitchen.graph,
20647
20746
  (finalUrlInfo) => {
20648
20747
  return (
20649
20748
  finalUrlInfo.subtype === "service_worker" &&
20650
- finalUrlInfo.isEntryPoint
20749
+ finalUrlInfo.isEntryPoint &&
20750
+ finalUrlInfo.isUsed()
20651
20751
  );
20652
20752
  },
20653
20753
  );
@@ -20656,34 +20756,34 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
20656
20756
  "inject urls in service worker",
20657
20757
  );
20658
20758
  const serviceWorkerResources = {};
20659
- GRAPH_VISITOR.forEach(finalKitchen.graph, (urlInfo) => {
20660
- if (urlInfo.isRoot) {
20661
- return;
20662
- }
20663
- if (!urlInfo.url.startsWith("file:")) {
20664
- return;
20665
- }
20666
- if (urlInfo.isInline) {
20667
- return;
20668
- }
20669
- if (!canUseVersionedUrl(urlInfo)) {
20670
- // when url is not versioned we compute a "version" for that url anyway
20671
- // so that service worker source still changes and navigator
20672
- // 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
+ }
20673
20778
  const buildSpecifier = findKey(buildSpecifierMap, urlInfo.url);
20779
+ const buildSpecifierVersioned =
20780
+ buildVersionsManager.getBuildSpecifierVersioned(buildSpecifier);
20674
20781
  serviceWorkerResources[buildSpecifier] = {
20675
20782
  version: buildVersionsManager.getVersion(urlInfo),
20783
+ versionedUrl: buildSpecifierVersioned,
20676
20784
  };
20677
- return;
20678
- }
20679
- const buildSpecifier = findKey(buildSpecifierMap, urlInfo.url);
20680
- const buildSpecifierVersioned =
20681
- buildVersionsManager.getBuildSpecifierVersioned(buildSpecifier);
20682
- serviceWorkerResources[buildSpecifier] = {
20683
- version: buildVersionsManager.getVersion(urlInfo),
20684
- versionedUrl: buildSpecifierVersioned,
20685
- };
20686
- });
20785
+ },
20786
+ );
20687
20787
  for (const serviceWorkerEntryUrlInfo of serviceWorkerEntryUrlInfos) {
20688
20788
  const serviceWorkerResourcesWithoutSwScriptItSelf = {
20689
20789
  ...serviceWorkerResources,
@@ -20723,48 +20823,48 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
20723
20823
  const buildRelativeUrl = urlToRelativeUrl(url, buildDirectoryUrl);
20724
20824
  return buildRelativeUrl;
20725
20825
  };
20726
- GRAPH_VISITOR.forEach(finalKitchen.graph, (urlInfo) => {
20727
- if (urlInfo.isRoot) {
20728
- return;
20729
- }
20730
- if (!urlInfo.url.startsWith("file:")) {
20731
- return;
20732
- }
20733
- if (urlInfo.type === "directory") {
20734
- return;
20735
- }
20736
- if (urlInfo.isInline) {
20737
- const buildRelativeUrl = getBuildRelativeUrl(urlInfo.url);
20738
- buildContents[buildRelativeUrl] = urlInfo.content;
20739
- buildInlineRelativeUrls.push(buildRelativeUrl);
20740
- } else {
20741
- const buildRelativeUrl = getBuildRelativeUrl(urlInfo.url);
20742
- if (
20743
- buildVersionsManager.getVersion(urlInfo) &&
20744
- canUseVersionedUrl(urlInfo)
20745
- ) {
20746
- const buildSpecifier = findKey(buildSpecifierMap, urlInfo.url);
20747
- const buildSpecifierVersioned =
20748
- buildVersionsManager.getBuildSpecifierVersioned(buildSpecifier);
20749
- const buildUrlVersioned = asBuildUrlVersioned({
20750
- buildSpecifierVersioned,
20751
- buildDirectoryUrl,
20752
- });
20753
- const buildRelativeUrlVersioned = urlToRelativeUrl(
20754
- buildUrlVersioned,
20755
- buildDirectoryUrl,
20756
- );
20757
- if (versioningMethod === "search_param") {
20758
- 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;
20759
20862
  } else {
20760
- buildContents[buildRelativeUrlVersioned] = urlInfo.content;
20863
+ buildContents[buildRelativeUrl] = urlInfo.content;
20761
20864
  }
20762
- buildManifest[buildRelativeUrl] = buildRelativeUrlVersioned;
20763
- } else {
20764
- buildContents[buildRelativeUrl] = urlInfo.content;
20765
20865
  }
20766
- }
20767
- });
20866
+ },
20867
+ );
20768
20868
  const buildFileContents = {};
20769
20869
  const buildInlineContents = {};
20770
20870
  Object.keys(buildContents)
@@ -20799,7 +20899,9 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
20799
20899
  writingFiles.done();
20800
20900
  }
20801
20901
  logger.info(
20802
- createUrlGraphSummary(finalKitchen.graph, { title: "build files" }),
20902
+ createUrlGraphSummary(finalKitchen.graph, {
20903
+ title: "build files",
20904
+ }),
20803
20905
  );
20804
20906
  return {
20805
20907
  buildFileContents,
@@ -21019,6 +21121,7 @@ const createFileService = ({
21019
21121
  serverStopCallbacks,
21020
21122
  serverEventsDispatcher,
21021
21123
  kitchenCache,
21124
+ onKitchenCreated = () => {},
21022
21125
 
21023
21126
  sourceDirectoryUrl,
21024
21127
  sourceMainFilePath,
@@ -21034,8 +21137,6 @@ const createFileService = ({
21034
21137
  supervisor,
21035
21138
  transpilation,
21036
21139
  clientAutoreload,
21037
- cooldownBetweenFileEvents,
21038
- clientServerEventsConfig,
21039
21140
  cacheControl,
21040
21141
  ribbon,
21041
21142
  sourcemaps,
@@ -21043,19 +21144,32 @@ const createFileService = ({
21043
21144
  sourcemapsSourcesContent,
21044
21145
  outDirectoryUrl,
21045
21146
  }) => {
21046
- const clientFileChangeCallbackList = [];
21047
- 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
+
21048
21164
  const stopWatchingSourceFiles = watchSourceFiles(
21049
21165
  sourceDirectoryUrl,
21050
21166
  (fileInfo) => {
21051
- clientFileChangeCallbackList.forEach((callback) => {
21052
- callback(fileInfo);
21053
- });
21167
+ clientFileChangeEventEmitter.emit(fileInfo);
21054
21168
  },
21055
21169
  {
21056
21170
  sourceFilesConfig,
21057
21171
  keepProcessAlive: false,
21058
- cooldownBetweenFileEvents,
21172
+ cooldownBetweenFileEvents: clientAutoreload.cooldownBetweenFileEvents,
21059
21173
  },
21060
21174
  );
21061
21175
  serverStopCallbacks.push(stopWatchingSourceFiles);
@@ -21074,10 +21188,10 @@ const createFileService = ({
21074
21188
  sourceDirectoryUrl,
21075
21189
  );
21076
21190
  let kitchen;
21077
- clientFileChangeCallbackList.push(({ url }) => {
21191
+ clientFileChangeEventEmitter.on(({ url }) => {
21078
21192
  const urlInfo = kitchen.graph.getUrlInfo(url);
21079
21193
  if (urlInfo) {
21080
- urlInfo.considerModified();
21194
+ urlInfo.onModified();
21081
21195
  }
21082
21196
  });
21083
21197
  const clientRuntimeCompat = { [runtimeName]: runtimeVersion };
@@ -21113,8 +21227,6 @@ const createFileService = ({
21113
21227
  transpilation,
21114
21228
 
21115
21229
  clientAutoreload,
21116
- clientFileChangeCallbackList,
21117
- clientFilesPruneCallbackList,
21118
21230
  cacheControl,
21119
21231
  ribbon,
21120
21232
  }),
@@ -21128,18 +21240,18 @@ const createFileService = ({
21128
21240
  ? new URL(`${runtimeName}@${runtimeVersion}/`, outDirectoryUrl)
21129
21241
  : undefined,
21130
21242
  });
21131
- kitchen.graph.createUrlInfoCallbackRef.current = (urlInfo) => {
21243
+ kitchen.graph.urlInfoCreatedEventEmitter.on((urlInfoCreated) => {
21132
21244
  const { watch } = URL_META.applyAssociations({
21133
- url: urlInfo.url,
21245
+ url: urlInfoCreated.url,
21134
21246
  associations: watchAssociations,
21135
21247
  });
21136
- urlInfo.isWatched = watch;
21248
+ urlInfoCreated.isWatched = watch;
21137
21249
  // wehn an url depends on many others, we check all these (like package.json)
21138
- urlInfo.isValid = () => {
21139
- if (!urlInfo.url.startsWith("file:")) {
21250
+ urlInfoCreated.isValid = () => {
21251
+ if (!urlInfoCreated.url.startsWith("file:")) {
21140
21252
  return false;
21141
21253
  }
21142
- if (urlInfo.content === undefined) {
21254
+ if (urlInfoCreated.content === undefined) {
21143
21255
  // urlInfo content is undefined when:
21144
21256
  // - url info content never fetched
21145
21257
  // - it is considered as modified because undelying file is watched and got saved
@@ -21151,21 +21263,20 @@ const createFileService = ({
21151
21263
  // file is not watched, check the filesystem
21152
21264
  let fileContentAsBuffer;
21153
21265
  try {
21154
- fileContentAsBuffer = readFileSync(new URL(urlInfo.url));
21266
+ fileContentAsBuffer = readFileSync(new URL(urlInfoCreated.url));
21155
21267
  } catch (e) {
21156
21268
  if (e.code === "ENOENT") {
21157
- urlInfo.considerModified();
21158
- urlInfo.deleteFromGraph();
21269
+ urlInfoCreated.onModified();
21159
21270
  return false;
21160
21271
  }
21161
21272
  return false;
21162
21273
  }
21163
21274
  const fileContentEtag = bufferToEtag$1(fileContentAsBuffer);
21164
- if (fileContentEtag !== urlInfo.originalContentEtag) {
21165
- urlInfo.considerModified();
21275
+ if (fileContentEtag !== urlInfoCreated.originalContentEtag) {
21276
+ urlInfoCreated.onModified();
21166
21277
  // restore content to be able to compare it again later
21167
- urlInfo.kitchen.urlInfoTransformer.setContent(
21168
- urlInfo,
21278
+ urlInfoCreated.kitchen.urlInfoTransformer.setContent(
21279
+ urlInfoCreated,
21169
21280
  String(fileContentAsBuffer),
21170
21281
  {
21171
21282
  contentEtag: fileContentEtag,
@@ -21174,23 +21285,24 @@ const createFileService = ({
21174
21285
  return false;
21175
21286
  }
21176
21287
  }
21177
- for (const implicitUrl of urlInfo.implicitUrlSet) {
21178
- const implicitUrlInfo = kitchen.graph.getUrlInfo(implicitUrl);
21288
+ for (const implicitUrl of urlInfoCreated.implicitUrlSet) {
21289
+ const implicitUrlInfo = urlInfoCreated.graph.getUrlInfo(implicitUrl);
21179
21290
  if (implicitUrlInfo && !implicitUrlInfo.isValid()) {
21180
21291
  return false;
21181
21292
  }
21182
21293
  }
21183
21294
  return true;
21184
21295
  };
21185
- };
21186
- kitchen.graph.pruneUrlInfoCallbackRef.current = (
21187
- prunedUrlInfo,
21188
- lastReferenceFromOther,
21189
- ) => {
21190
- clientFilesPruneCallbackList.forEach((callback) => {
21191
- callback(prunedUrlInfo, lastReferenceFromOther);
21192
- });
21193
- };
21296
+ });
21297
+ kitchen.graph.urlInfoDereferencedEventEmitter.on(
21298
+ (urlInfoDereferenced, lastReferenceFromOther) => {
21299
+ clientFileDereferencedEventEmitter.emit(
21300
+ urlInfoDereferenced,
21301
+ lastReferenceFromOther,
21302
+ );
21303
+ },
21304
+ );
21305
+
21194
21306
  serverStopCallbacks.push(() => {
21195
21307
  kitchen.pluginController.callHooks("destroy", kitchen.context);
21196
21308
  });
@@ -21223,12 +21335,15 @@ const createFileService = ({
21223
21335
  });
21224
21336
  // "pushPlugin" so that event source client connection can be put as early as possible in html
21225
21337
  kitchen.pluginController.pushPlugin(
21226
- jsenvPluginServerEventsClientInjection(clientServerEventsConfig),
21338
+ jsenvPluginServerEventsClientInjection(
21339
+ clientAutoreload.clientServerEventsConfig,
21340
+ ),
21227
21341
  );
21228
21342
  }
21229
21343
  }
21230
21344
 
21231
21345
  kitchenCache.set(runtimeId, kitchen);
21346
+ onKitchenCreated(kitchen);
21232
21347
  return kitchen;
21233
21348
  };
21234
21349
 
@@ -21246,35 +21361,28 @@ const createFileService = ({
21246
21361
  if (responseFromPlugin) {
21247
21362
  return responseFromPlugin;
21248
21363
  }
21249
- let reference;
21250
- const parentUrl = inferParentFromRequest(
21251
- request,
21252
- sourceDirectoryUrl,
21253
- sourceMainFilePath,
21254
- );
21255
- if (parentUrl) {
21256
- reference = kitchen.graph.inferReference(request.resource, parentUrl);
21257
- }
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);
21258
21372
  if (!reference) {
21259
- let parentUrlInfo;
21260
- if (parentUrl) {
21261
- parentUrlInfo = kitchen.graph.getUrlInfo(parentUrl);
21262
- }
21263
- if (!parentUrlInfo) {
21264
- parentUrlInfo = kitchen.graph.rootUrlInfo;
21265
- }
21266
- reference = parentUrlInfo.dependencies.createResolveAndFinalize({
21267
- trace: { message: parentUrl || sourceDirectoryUrl },
21268
- type: "http_request",
21269
- specifier: request.resource,
21270
- });
21373
+ reference =
21374
+ kitchen.graph.rootUrlInfo.dependencies.createResolveAndFinalize({
21375
+ trace: { message: parentUrl },
21376
+ type: "http_request",
21377
+ specifier: request.resource,
21378
+ });
21271
21379
  }
21272
21380
  const urlInfo = reference.urlInfo;
21273
21381
  const ifNoneMatch = request.headers["if-none-match"];
21274
21382
  const urlInfoTargetedByCache = urlInfo.findParentIfInline() || urlInfo;
21275
21383
 
21276
21384
  try {
21277
- if (ifNoneMatch) {
21385
+ if (!urlInfo.error && ifNoneMatch) {
21278
21386
  const [clientOriginalContentEtag, clientContentEtag] =
21279
21387
  ifNoneMatch.split("_");
21280
21388
  if (
@@ -21322,7 +21430,7 @@ const createFileService = ({
21322
21430
  }),
21323
21431
  ...urlInfo.headers,
21324
21432
  "content-type": urlInfo.contentType,
21325
- "content-length": Buffer.byteLength(urlInfo.content),
21433
+ "content-length": urlInfo.contentLength,
21326
21434
  },
21327
21435
  body: urlInfo.content,
21328
21436
  timing: urlInfo.timing,
@@ -21361,7 +21469,7 @@ const createFileService = ({
21361
21469
  statusMessage: originalError.message,
21362
21470
  headers: {
21363
21471
  "content-type": urlInfo.contentType,
21364
- "content-length": Buffer.byteLength(urlInfo.content),
21472
+ "content-length": urlInfo.contentLength,
21365
21473
  "cache-control": "no-store",
21366
21474
  },
21367
21475
  body: urlInfo.content,
@@ -21408,38 +21516,10 @@ const createFileService = ({
21408
21516
  statusText: e.reason,
21409
21517
  statusMessage: e.stack,
21410
21518
  };
21411
- } finally {
21412
- // remove http_request when there is other references keeping url info alive
21413
- if (
21414
- reference.type === "http_request" &&
21415
- reference.urlInfo.referenceFromOthersSet.size > 1
21416
- ) {
21417
- reference.remove();
21418
- }
21419
21519
  }
21420
21520
  };
21421
21521
  };
21422
21522
 
21423
- const inferParentFromRequest = (
21424
- request,
21425
- sourceDirectoryUrl,
21426
- sourceMainFilePath,
21427
- ) => {
21428
- const { referer } = request.headers;
21429
- if (!referer) {
21430
- return null;
21431
- }
21432
- const refererUrlObject = new URL(referer);
21433
- const refererUrl =
21434
- refererUrlObject.pathname === `/`
21435
- ? new URL(sourceMainFilePath, request.origin).href
21436
- : referer;
21437
- return WEB_URL_CONVERTER.asFileUrl(refererUrl, {
21438
- origin: request.origin,
21439
- rootDirectoryUrl: sourceDirectoryUrl,
21440
- });
21441
- };
21442
-
21443
21523
  /**
21444
21524
  * Start a server for source files:
21445
21525
  * - cook source files according to jsenv plugins
@@ -21470,8 +21550,6 @@ const startDevServer = async ({
21470
21550
 
21471
21551
  sourceFilesConfig,
21472
21552
  clientAutoreload = true,
21473
- cooldownBetweenFileEvents,
21474
- clientServerEventsConfig = {},
21475
21553
 
21476
21554
  // runtimeCompat is the runtimeCompat for the build
21477
21555
  // when specified, dev server use it to warn in case
@@ -21487,6 +21565,7 @@ const startDevServer = async ({
21487
21565
  cacheControl = true,
21488
21566
  ribbon = true,
21489
21567
  // toolbar = false,
21568
+ onKitchenCreated = () => {},
21490
21569
 
21491
21570
  sourcemaps = "inline",
21492
21571
  sourcemapsSourcesProtocol,
@@ -21611,6 +21690,7 @@ const startDevServer = async ({
21611
21690
  serverStopCallbacks,
21612
21691
  serverEventsDispatcher,
21613
21692
  kitchenCache,
21693
+ onKitchenCreated,
21614
21694
 
21615
21695
  sourceDirectoryUrl,
21616
21696
  sourceMainFilePath,
@@ -21626,8 +21706,6 @@ const startDevServer = async ({
21626
21706
  supervisor,
21627
21707
  transpilation,
21628
21708
  clientAutoreload,
21629
- cooldownBetweenFileEvents,
21630
- clientServerEventsConfig,
21631
21709
  cacheControl,
21632
21710
  ribbon,
21633
21711
  sourcemaps,