@bamboocss/vite 1.37.10 → 1.37.12

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.
package/dist/index.cjs CHANGED
@@ -751,16 +751,21 @@ const collectModuleScopeNames = (sourceFile) => {
751
751
  * while under-reporting ships an element whose class has no rule behind it and says nothing.
752
752
  * Shadowing a recipe binding is rare; silently shipping unstyled markup is not recoverable.
753
753
  */
754
- const identifiersByName = /* @__PURE__ */ new WeakMap();
755
754
  /**
756
755
  * Every identifier in the module, grouped by the name it spells.
757
756
  *
758
- * Walking the whole tree per binding made this O(bindings x identifiers): a module declaring
759
- * ten recipes walked its identifiers ten times. One walk answers for all of them, and the
760
- * result is cached against the source text like the module-scope names beside it, so a
761
- * re-transform of unchanged text reuses it.
757
+ * Built once per pass and handed to each lookup, because walking the whole tree per binding
758
+ * made that O(bindings x identifiers): a module declaring ten recipes walked its identifiers
759
+ * ten times.
760
+ *
761
+ * Deliberately *not* memoized across passes, unlike the module-scope names beside it. That
762
+ * cache holds strings, which outlive anything; this one holds nodes, and a node does not
763
+ * survive its source file being replaced — `addSourceFile` overwrites, which forgets every
764
+ * node previously taken from it. Keying on the source text does not help, because identical
765
+ * text re-parsed is a fresh tree: the cache hits and returns nodes that throw
766
+ * `Attempted to get information from a node that was removed or forgotten` on the next read.
762
767
  */
763
- const identifierIndex = (sourceFile) => byText(identifiersByName, sourceFile, () => {
768
+ const identifierIndex = (sourceFile) => {
764
769
  const index = /* @__PURE__ */ new Map();
765
770
  for (const identifier of sourceFile.getDescendantsOfKind(ts_morph.SyntaxKind.Identifier)) {
766
771
  const text = identifier.getText();
@@ -769,10 +774,10 @@ const identifierIndex = (sourceFile) => byText(identifiersByName, sourceFile, ()
769
774
  else index.set(text, [identifier]);
770
775
  }
771
776
  return index;
772
- });
773
- const localReferencesTo = (sourceFile, name, declaration) => {
777
+ };
778
+ const localReferencesTo = (index, name, declaration) => {
774
779
  const references = [];
775
- for (const identifier of identifierIndex(sourceFile).get(name) ?? []) {
780
+ for (const identifier of index.get(name) ?? []) {
776
781
  if (identifier === declaration) continue;
777
782
  const parent = identifier.getParent();
778
783
  if (!parent) continue;
@@ -2557,6 +2562,21 @@ const foldSource = (options) => {
2557
2562
  const sourceFile = ownSourceFile ?? candidates[0]?.node.getSourceFile() ?? recipeDefinitions[0]?.call.getSourceFile();
2558
2563
  if (!sourceFile) return;
2559
2564
  const importedRecipeBindings = new Set(parserResult.importedRecipes?.keys() ?? []);
2565
+ /**
2566
+ * Every identifier in the module, grouped by the name it spells — built at most once per
2567
+ * pass, and only when something below has a name to look up.
2568
+ *
2569
+ * One index answers for every binding, rather than a walk per binding. Built here rather
2570
+ * than cached across passes: it holds nodes, and a node does not outlive its source file
2571
+ * being replaced.
2572
+ *
2573
+ * Deferred because most modules in an app neither declare nor import a recipe, and the
2574
+ * walk is not cheap — `getDescendantsOfKind` wraps every identifier in the file in a
2575
+ * ts-morph node to answer a question those modules never ask. It was 11% of a 6,307-file
2576
+ * build, most of it spent producing an index nothing read.
2577
+ */
2578
+ let identifiers;
2579
+ const identifiersByName = () => identifiers ??= identifierIndex(sourceFile);
2560
2580
  for (const binding of new Set([...recipeConfigs.keys(), ...importedRecipeBindings])) {
2561
2581
  const entry = recipeConfigs.get(binding);
2562
2582
  if (entry === AMBIGUOUS) continue;
@@ -2564,7 +2584,7 @@ const foldSource = (options) => {
2564
2584
  const definition = entry?.box?.getNode?.();
2565
2585
  const nameNode = definition?.getSourceFile() === sourceFile ? definition?.getFirstAncestorByKind(ts_morph.SyntaxKind.VariableDeclaration)?.getNameNode() : importSpecifierFor(sourceFile, binding);
2566
2586
  if (!nameNode || !ts_morph.Node.isIdentifier(nameNode)) continue;
2567
- const references = localReferencesTo(sourceFile, binding, nameNode);
2587
+ const references = localReferencesTo(identifiersByName(), binding, nameNode);
2568
2588
  if (skipped.filter((item) => SURVIVES_TO_RUNTIME.has(item.reason) && item.end > item.start).some((item) => references.some((ref) => ref.getStart() >= item.start && ref.getStart() < item.end))) continue;
2569
2589
  const survivor = references.find((ref) => !applied.some(([from, to]) => ref.getStart() >= from && ref.getStart() < to));
2570
2590
  if (!survivor) continue;
@@ -2665,25 +2685,24 @@ const foldSource = (options) => {
2665
2685
  }
2666
2686
  if (watched.size === 0) return;
2667
2687
  const declined = skipped.filter((entry) => SURVIVES_TO_RUNTIME.has(entry.reason) && entry.end > entry.start).map((entry) => [entry.start, entry.end]);
2668
- const reported = /* @__PURE__ */ new Set();
2669
- for (const identifier of sourceFile.getDescendantsOfKind(ts_morph.SyntaxKind.Identifier)) {
2670
- const local = identifier.getText();
2671
- const imported = watched.get(local);
2672
- if (imported === void 0 || reported.has(local)) continue;
2688
+ const survivors = [];
2689
+ for (const [local, imported] of watched) for (const identifier of identifiersByName().get(local) ?? []) {
2673
2690
  const start = identifier.getStart();
2674
2691
  if (identifier.getFirstAncestorByKind(ts_morph.SyntaxKind.ImportDeclaration)) continue;
2675
2692
  if (applied.some(([from, to]) => start >= from && start < to)) continue;
2676
2693
  if (declined.some(([from, to]) => start >= from && start < to)) continue;
2677
2694
  if (!isValueReference(identifier)) continue;
2678
2695
  if (isShadowed(identifier, local)) continue;
2679
- reported.add(local);
2680
- skipped.push({
2696
+ survivors.push({
2681
2697
  name: imported,
2682
2698
  reason: "runtime-binding",
2683
2699
  start,
2684
2700
  end: identifier.getEnd()
2685
2701
  });
2702
+ break;
2686
2703
  }
2704
+ survivors.sort((a, b) => a.start - b.start);
2705
+ skipped.push(...survivors);
2687
2706
  }
2688
2707
  if (reportSurvivors) reportRuntimeBindings();
2689
2708
  if (folded.length === 0) return {
package/dist/index.mjs CHANGED
@@ -721,16 +721,21 @@ const collectModuleScopeNames = (sourceFile) => {
721
721
  * while under-reporting ships an element whose class has no rule behind it and says nothing.
722
722
  * Shadowing a recipe binding is rare; silently shipping unstyled markup is not recoverable.
723
723
  */
724
- const identifiersByName = /* @__PURE__ */ new WeakMap();
725
724
  /**
726
725
  * Every identifier in the module, grouped by the name it spells.
727
726
  *
728
- * Walking the whole tree per binding made this O(bindings x identifiers): a module declaring
729
- * ten recipes walked its identifiers ten times. One walk answers for all of them, and the
730
- * result is cached against the source text like the module-scope names beside it, so a
731
- * re-transform of unchanged text reuses it.
727
+ * Built once per pass and handed to each lookup, because walking the whole tree per binding
728
+ * made that O(bindings x identifiers): a module declaring ten recipes walked its identifiers
729
+ * ten times.
730
+ *
731
+ * Deliberately *not* memoized across passes, unlike the module-scope names beside it. That
732
+ * cache holds strings, which outlive anything; this one holds nodes, and a node does not
733
+ * survive its source file being replaced — `addSourceFile` overwrites, which forgets every
734
+ * node previously taken from it. Keying on the source text does not help, because identical
735
+ * text re-parsed is a fresh tree: the cache hits and returns nodes that throw
736
+ * `Attempted to get information from a node that was removed or forgotten` on the next read.
732
737
  */
733
- const identifierIndex = (sourceFile) => byText(identifiersByName, sourceFile, () => {
738
+ const identifierIndex = (sourceFile) => {
734
739
  const index = /* @__PURE__ */ new Map();
735
740
  for (const identifier of sourceFile.getDescendantsOfKind(SyntaxKind.Identifier)) {
736
741
  const text = identifier.getText();
@@ -739,10 +744,10 @@ const identifierIndex = (sourceFile) => byText(identifiersByName, sourceFile, ()
739
744
  else index.set(text, [identifier]);
740
745
  }
741
746
  return index;
742
- });
743
- const localReferencesTo = (sourceFile, name, declaration) => {
747
+ };
748
+ const localReferencesTo = (index, name, declaration) => {
744
749
  const references = [];
745
- for (const identifier of identifierIndex(sourceFile).get(name) ?? []) {
750
+ for (const identifier of index.get(name) ?? []) {
746
751
  if (identifier === declaration) continue;
747
752
  const parent = identifier.getParent();
748
753
  if (!parent) continue;
@@ -2527,6 +2532,21 @@ const foldSource = (options) => {
2527
2532
  const sourceFile = ownSourceFile ?? candidates[0]?.node.getSourceFile() ?? recipeDefinitions[0]?.call.getSourceFile();
2528
2533
  if (!sourceFile) return;
2529
2534
  const importedRecipeBindings = new Set(parserResult.importedRecipes?.keys() ?? []);
2535
+ /**
2536
+ * Every identifier in the module, grouped by the name it spells — built at most once per
2537
+ * pass, and only when something below has a name to look up.
2538
+ *
2539
+ * One index answers for every binding, rather than a walk per binding. Built here rather
2540
+ * than cached across passes: it holds nodes, and a node does not outlive its source file
2541
+ * being replaced.
2542
+ *
2543
+ * Deferred because most modules in an app neither declare nor import a recipe, and the
2544
+ * walk is not cheap — `getDescendantsOfKind` wraps every identifier in the file in a
2545
+ * ts-morph node to answer a question those modules never ask. It was 11% of a 6,307-file
2546
+ * build, most of it spent producing an index nothing read.
2547
+ */
2548
+ let identifiers;
2549
+ const identifiersByName = () => identifiers ??= identifierIndex(sourceFile);
2530
2550
  for (const binding of new Set([...recipeConfigs.keys(), ...importedRecipeBindings])) {
2531
2551
  const entry = recipeConfigs.get(binding);
2532
2552
  if (entry === AMBIGUOUS) continue;
@@ -2534,7 +2554,7 @@ const foldSource = (options) => {
2534
2554
  const definition = entry?.box?.getNode?.();
2535
2555
  const nameNode = definition?.getSourceFile() === sourceFile ? definition?.getFirstAncestorByKind(SyntaxKind.VariableDeclaration)?.getNameNode() : importSpecifierFor(sourceFile, binding);
2536
2556
  if (!nameNode || !Node.isIdentifier(nameNode)) continue;
2537
- const references = localReferencesTo(sourceFile, binding, nameNode);
2557
+ const references = localReferencesTo(identifiersByName(), binding, nameNode);
2538
2558
  if (skipped.filter((item) => SURVIVES_TO_RUNTIME.has(item.reason) && item.end > item.start).some((item) => references.some((ref) => ref.getStart() >= item.start && ref.getStart() < item.end))) continue;
2539
2559
  const survivor = references.find((ref) => !applied.some(([from, to]) => ref.getStart() >= from && ref.getStart() < to));
2540
2560
  if (!survivor) continue;
@@ -2635,25 +2655,24 @@ const foldSource = (options) => {
2635
2655
  }
2636
2656
  if (watched.size === 0) return;
2637
2657
  const declined = skipped.filter((entry) => SURVIVES_TO_RUNTIME.has(entry.reason) && entry.end > entry.start).map((entry) => [entry.start, entry.end]);
2638
- const reported = /* @__PURE__ */ new Set();
2639
- for (const identifier of sourceFile.getDescendantsOfKind(SyntaxKind.Identifier)) {
2640
- const local = identifier.getText();
2641
- const imported = watched.get(local);
2642
- if (imported === void 0 || reported.has(local)) continue;
2658
+ const survivors = [];
2659
+ for (const [local, imported] of watched) for (const identifier of identifiersByName().get(local) ?? []) {
2643
2660
  const start = identifier.getStart();
2644
2661
  if (identifier.getFirstAncestorByKind(SyntaxKind.ImportDeclaration)) continue;
2645
2662
  if (applied.some(([from, to]) => start >= from && start < to)) continue;
2646
2663
  if (declined.some(([from, to]) => start >= from && start < to)) continue;
2647
2664
  if (!isValueReference(identifier)) continue;
2648
2665
  if (isShadowed(identifier, local)) continue;
2649
- reported.add(local);
2650
- skipped.push({
2666
+ survivors.push({
2651
2667
  name: imported,
2652
2668
  reason: "runtime-binding",
2653
2669
  start,
2654
2670
  end: identifier.getEnd()
2655
2671
  });
2672
+ break;
2656
2673
  }
2674
+ survivors.sort((a, b) => a.start - b.start);
2675
+ skipped.push(...survivors);
2657
2676
  }
2658
2677
  if (reportSurvivors) reportRuntimeBindings();
2659
2678
  if (folded.length === 0) return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bamboocss/vite",
3
- "version": "1.37.10",
3
+ "version": "1.37.12",
4
4
  "description": "Vite integration for Bamboo CSS",
5
5
  "homepage": "https://bamboocss.com",
6
6
  "license": "MIT",
@@ -40,18 +40,18 @@
40
40
  "postcss": "8.5.26",
41
41
  "postcss-selector-parser": "7.1.5",
42
42
  "ts-morph": "28.0.0",
43
- "@bamboocss/config": "1.37.10",
44
- "@bamboocss/core": "1.37.10",
45
- "@bamboocss/extractor": "1.37.10",
46
- "@bamboocss/node": "1.37.10",
47
- "@bamboocss/logger": "1.37.10",
48
- "@bamboocss/types": "1.37.10",
49
- "@bamboocss/shared": "1.37.10"
43
+ "@bamboocss/config": "1.37.12",
44
+ "@bamboocss/core": "1.37.12",
45
+ "@bamboocss/extractor": "1.37.12",
46
+ "@bamboocss/logger": "1.37.12",
47
+ "@bamboocss/node": "1.37.12",
48
+ "@bamboocss/shared": "1.37.12",
49
+ "@bamboocss/types": "1.37.12"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@jridgewell/trace-mapping": "^0.3.31",
53
53
  "vite": "7.2.6",
54
- "@bamboocss/fixture": "1.37.10"
54
+ "@bamboocss/fixture": "1.37.12"
55
55
  },
56
56
  "peerDependencies": {
57
57
  "vite": ">=5"