@bamboocss/vite 1.37.9 → 1.37.11

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,10 +751,33 @@ 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 localReferencesTo = (sourceFile, name, declaration) => {
755
- const references = [];
754
+ /**
755
+ * Every identifier in the module, grouped by the name it spells.
756
+ *
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.
767
+ */
768
+ const identifierIndex = (sourceFile) => {
769
+ const index = /* @__PURE__ */ new Map();
756
770
  for (const identifier of sourceFile.getDescendantsOfKind(ts_morph.SyntaxKind.Identifier)) {
757
- if (identifier.getText() !== name) continue;
771
+ const text = identifier.getText();
772
+ const known = index.get(text);
773
+ if (known) known.push(identifier);
774
+ else index.set(text, [identifier]);
775
+ }
776
+ return index;
777
+ };
778
+ const localReferencesTo = (index, name, declaration) => {
779
+ const references = [];
780
+ for (const identifier of index.get(name) ?? []) {
758
781
  if (identifier === declaration) continue;
759
782
  const parent = identifier.getParent();
760
783
  if (!parent) continue;
@@ -2539,6 +2562,7 @@ const foldSource = (options) => {
2539
2562
  const sourceFile = ownSourceFile ?? candidates[0]?.node.getSourceFile() ?? recipeDefinitions[0]?.call.getSourceFile();
2540
2563
  if (!sourceFile) return;
2541
2564
  const importedRecipeBindings = new Set(parserResult.importedRecipes?.keys() ?? []);
2565
+ const identifiers = identifierIndex(sourceFile);
2542
2566
  for (const binding of new Set([...recipeConfigs.keys(), ...importedRecipeBindings])) {
2543
2567
  const entry = recipeConfigs.get(binding);
2544
2568
  if (entry === AMBIGUOUS) continue;
@@ -2546,7 +2570,7 @@ const foldSource = (options) => {
2546
2570
  const definition = entry?.box?.getNode?.();
2547
2571
  const nameNode = definition?.getSourceFile() === sourceFile ? definition?.getFirstAncestorByKind(ts_morph.SyntaxKind.VariableDeclaration)?.getNameNode() : importSpecifierFor(sourceFile, binding);
2548
2572
  if (!nameNode || !ts_morph.Node.isIdentifier(nameNode)) continue;
2549
- const references = localReferencesTo(sourceFile, binding, nameNode);
2573
+ const references = localReferencesTo(identifiers, binding, nameNode);
2550
2574
  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;
2551
2575
  const survivor = references.find((ref) => !applied.some(([from, to]) => ref.getStart() >= from && ref.getStart() < to));
2552
2576
  if (!survivor) continue;
@@ -2839,18 +2863,24 @@ const bamboocss = (options = {}) => {
2839
2863
  skipped: /* @__PURE__ */ new Map()
2840
2864
  };
2841
2865
  const staticSession = createStaticCompilationSession();
2842
- const survivors = [];
2843
- const survivorKeys = /* @__PURE__ */ new Set();
2866
+ /**
2867
+ * Indexed by file, because the only bulk operation on it is "forget this one's".
2868
+ *
2869
+ * A flat array meant every transform scanned every survivor and then rebuilt the dedupe key
2870
+ * set from scratch — O(modules x survivors) across a build, and worst exactly when a build is
2871
+ * already failing and the user is iterating on it. One project had 736 of them across 9,461
2872
+ * modules, which is seven million string builds to discard.
2873
+ */
2874
+ const survivorsByFile = /* @__PURE__ */ new Map();
2875
+ const allSurvivors = () => [...survivorsByFile.values()].flat();
2844
2876
  const addSurvivor = (entry) => {
2845
- const key = `${entry.file}:${entry.line}:${entry.name}:${entry.reason}`;
2846
- if (survivorKeys.has(key)) return;
2847
- survivorKeys.add(key);
2848
- survivors.push(entry);
2877
+ const forFile = survivorsByFile.get(entry.file) ?? [];
2878
+ if (forFile.some((seen) => seen.line === entry.line && seen.name === entry.name && seen.reason === entry.reason)) return;
2879
+ forFile.push(entry);
2880
+ survivorsByFile.set(entry.file, forFile);
2849
2881
  };
2850
2882
  const clearSurvivorsFor = (file) => {
2851
- for (let index = survivors.length - 1; index >= 0; index--) if (survivors[index]?.file === file) survivors.splice(index, 1);
2852
- survivorKeys.clear();
2853
- for (const entry of survivors) survivorKeys.add(`${entry.file}:${entry.line}:${entry.name}:${entry.reason}`);
2883
+ survivorsByFile.delete(file);
2854
2884
  };
2855
2885
  const createSurvivorError = (entries) => {
2856
2886
  const byFile = /* @__PURE__ */ new Map();
@@ -2909,8 +2939,7 @@ const bamboocss = (options = {}) => {
2909
2939
  totals.files = 0;
2910
2940
  totals.filesWithFolds = 0;
2911
2941
  totals.skipped.clear();
2912
- survivors.length = 0;
2913
- survivorKeys.clear();
2942
+ survivorsByFile.clear();
2914
2943
  recipeConfigCache.clear();
2915
2944
  resetStaticCompilationSession(staticSession);
2916
2945
  }
@@ -3013,7 +3042,8 @@ const bamboocss = (options = {}) => {
3013
3042
  }
3014
3043
  if (reportSkipped && result.skipped.length) _bamboocss_logger.logger.info("vite:transform", formatSkipped(filePath, result.skipped));
3015
3044
  for (const dependency of result.dependencies) this.addWatchFile?.(dependency);
3016
- if (command === "serve" && survivors.some((entry) => entry.file === filePath)) throw createSurvivorError(survivors.filter((entry) => entry.file === filePath));
3045
+ const forFile = survivorsByFile.get(filePath);
3046
+ if (command === "serve" && forFile?.length) throw createSurvivorError(forFile);
3017
3047
  if (!result.folded.length) return null;
3018
3048
  _bamboocss_logger.logger.debug("vite:transform", `Compiled ${result.folded.length} call(s) in ${filePath}`);
3019
3049
  return {
@@ -3022,6 +3052,7 @@ const bamboocss = (options = {}) => {
3022
3052
  };
3023
3053
  },
3024
3054
  buildEnd() {
3055
+ const survivors = allSurvivors();
3025
3056
  if (survivors.length) throw createSurvivorError(survivors);
3026
3057
  if (typeof this.getModuleInfo === "function") {
3027
3058
  if (!staticSession.cssLoaded) throw new Error(`bamboocss: compiled class values were produced, but ${JSON.stringify(VIRTUAL_CSS_ID)} was not imported. Add \`import ${JSON.stringify(VIRTUAL_CSS_ID)}\` once, from a JavaScript or TypeScript module in the application entry graph.\n\nIt has to be a JS import. \`@import\` from a stylesheet does not reach it: the id names a virtual module resolved by this plugin, and Vite resolves CSS \`@import\` before plugin resolution, so it fails as an unresolvable path. A project that ships one preloaded stylesheet imports this from its entry module instead, and lets Vite emit the CSS asset.`);
package/dist/index.mjs CHANGED
@@ -721,10 +721,33 @@ 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 localReferencesTo = (sourceFile, name, declaration) => {
725
- const references = [];
724
+ /**
725
+ * Every identifier in the module, grouped by the name it spells.
726
+ *
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.
737
+ */
738
+ const identifierIndex = (sourceFile) => {
739
+ const index = /* @__PURE__ */ new Map();
726
740
  for (const identifier of sourceFile.getDescendantsOfKind(SyntaxKind.Identifier)) {
727
- if (identifier.getText() !== name) continue;
741
+ const text = identifier.getText();
742
+ const known = index.get(text);
743
+ if (known) known.push(identifier);
744
+ else index.set(text, [identifier]);
745
+ }
746
+ return index;
747
+ };
748
+ const localReferencesTo = (index, name, declaration) => {
749
+ const references = [];
750
+ for (const identifier of index.get(name) ?? []) {
728
751
  if (identifier === declaration) continue;
729
752
  const parent = identifier.getParent();
730
753
  if (!parent) continue;
@@ -2509,6 +2532,7 @@ const foldSource = (options) => {
2509
2532
  const sourceFile = ownSourceFile ?? candidates[0]?.node.getSourceFile() ?? recipeDefinitions[0]?.call.getSourceFile();
2510
2533
  if (!sourceFile) return;
2511
2534
  const importedRecipeBindings = new Set(parserResult.importedRecipes?.keys() ?? []);
2535
+ const identifiers = identifierIndex(sourceFile);
2512
2536
  for (const binding of new Set([...recipeConfigs.keys(), ...importedRecipeBindings])) {
2513
2537
  const entry = recipeConfigs.get(binding);
2514
2538
  if (entry === AMBIGUOUS) continue;
@@ -2516,7 +2540,7 @@ const foldSource = (options) => {
2516
2540
  const definition = entry?.box?.getNode?.();
2517
2541
  const nameNode = definition?.getSourceFile() === sourceFile ? definition?.getFirstAncestorByKind(SyntaxKind.VariableDeclaration)?.getNameNode() : importSpecifierFor(sourceFile, binding);
2518
2542
  if (!nameNode || !Node.isIdentifier(nameNode)) continue;
2519
- const references = localReferencesTo(sourceFile, binding, nameNode);
2543
+ const references = localReferencesTo(identifiers, binding, nameNode);
2520
2544
  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;
2521
2545
  const survivor = references.find((ref) => !applied.some(([from, to]) => ref.getStart() >= from && ref.getStart() < to));
2522
2546
  if (!survivor) continue;
@@ -2809,18 +2833,24 @@ const bamboocss = (options = {}) => {
2809
2833
  skipped: /* @__PURE__ */ new Map()
2810
2834
  };
2811
2835
  const staticSession = createStaticCompilationSession();
2812
- const survivors = [];
2813
- const survivorKeys = /* @__PURE__ */ new Set();
2836
+ /**
2837
+ * Indexed by file, because the only bulk operation on it is "forget this one's".
2838
+ *
2839
+ * A flat array meant every transform scanned every survivor and then rebuilt the dedupe key
2840
+ * set from scratch — O(modules x survivors) across a build, and worst exactly when a build is
2841
+ * already failing and the user is iterating on it. One project had 736 of them across 9,461
2842
+ * modules, which is seven million string builds to discard.
2843
+ */
2844
+ const survivorsByFile = /* @__PURE__ */ new Map();
2845
+ const allSurvivors = () => [...survivorsByFile.values()].flat();
2814
2846
  const addSurvivor = (entry) => {
2815
- const key = `${entry.file}:${entry.line}:${entry.name}:${entry.reason}`;
2816
- if (survivorKeys.has(key)) return;
2817
- survivorKeys.add(key);
2818
- survivors.push(entry);
2847
+ const forFile = survivorsByFile.get(entry.file) ?? [];
2848
+ if (forFile.some((seen) => seen.line === entry.line && seen.name === entry.name && seen.reason === entry.reason)) return;
2849
+ forFile.push(entry);
2850
+ survivorsByFile.set(entry.file, forFile);
2819
2851
  };
2820
2852
  const clearSurvivorsFor = (file) => {
2821
- for (let index = survivors.length - 1; index >= 0; index--) if (survivors[index]?.file === file) survivors.splice(index, 1);
2822
- survivorKeys.clear();
2823
- for (const entry of survivors) survivorKeys.add(`${entry.file}:${entry.line}:${entry.name}:${entry.reason}`);
2853
+ survivorsByFile.delete(file);
2824
2854
  };
2825
2855
  const createSurvivorError = (entries) => {
2826
2856
  const byFile = /* @__PURE__ */ new Map();
@@ -2879,8 +2909,7 @@ const bamboocss = (options = {}) => {
2879
2909
  totals.files = 0;
2880
2910
  totals.filesWithFolds = 0;
2881
2911
  totals.skipped.clear();
2882
- survivors.length = 0;
2883
- survivorKeys.clear();
2912
+ survivorsByFile.clear();
2884
2913
  recipeConfigCache.clear();
2885
2914
  resetStaticCompilationSession(staticSession);
2886
2915
  }
@@ -2983,7 +3012,8 @@ const bamboocss = (options = {}) => {
2983
3012
  }
2984
3013
  if (reportSkipped && result.skipped.length) logger.info("vite:transform", formatSkipped(filePath, result.skipped));
2985
3014
  for (const dependency of result.dependencies) this.addWatchFile?.(dependency);
2986
- if (command === "serve" && survivors.some((entry) => entry.file === filePath)) throw createSurvivorError(survivors.filter((entry) => entry.file === filePath));
3015
+ const forFile = survivorsByFile.get(filePath);
3016
+ if (command === "serve" && forFile?.length) throw createSurvivorError(forFile);
2987
3017
  if (!result.folded.length) return null;
2988
3018
  logger.debug("vite:transform", `Compiled ${result.folded.length} call(s) in ${filePath}`);
2989
3019
  return {
@@ -2992,6 +3022,7 @@ const bamboocss = (options = {}) => {
2992
3022
  };
2993
3023
  },
2994
3024
  buildEnd() {
3025
+ const survivors = allSurvivors();
2995
3026
  if (survivors.length) throw createSurvivorError(survivors);
2996
3027
  if (typeof this.getModuleInfo === "function") {
2997
3028
  if (!staticSession.cssLoaded) throw new Error(`bamboocss: compiled class values were produced, but ${JSON.stringify(VIRTUAL_CSS_ID)} was not imported. Add \`import ${JSON.stringify(VIRTUAL_CSS_ID)}\` once, from a JavaScript or TypeScript module in the application entry graph.\n\nIt has to be a JS import. \`@import\` from a stylesheet does not reach it: the id names a virtual module resolved by this plugin, and Vite resolves CSS \`@import\` before plugin resolution, so it fails as an unresolvable path. A project that ships one preloaded stylesheet imports this from its entry module instead, and lets Vite emit the CSS asset.`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bamboocss/vite",
3
- "version": "1.37.9",
3
+ "version": "1.37.11",
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.9",
44
- "@bamboocss/core": "1.37.9",
45
- "@bamboocss/extractor": "1.37.9",
46
- "@bamboocss/logger": "1.37.9",
47
- "@bamboocss/node": "1.37.9",
48
- "@bamboocss/types": "1.37.9",
49
- "@bamboocss/shared": "1.37.9"
43
+ "@bamboocss/logger": "1.37.11",
44
+ "@bamboocss/node": "1.37.11",
45
+ "@bamboocss/extractor": "1.37.11",
46
+ "@bamboocss/core": "1.37.11",
47
+ "@bamboocss/config": "1.37.11",
48
+ "@bamboocss/shared": "1.37.11",
49
+ "@bamboocss/types": "1.37.11"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@jridgewell/trace-mapping": "^0.3.31",
53
53
  "vite": "7.2.6",
54
- "@bamboocss/fixture": "1.37.9"
54
+ "@bamboocss/fixture": "1.37.11"
55
55
  },
56
56
  "peerDependencies": {
57
57
  "vite": ">=5"