@bamboocss/vite 1.35.5 → 1.36.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.
package/dist/index.cjs CHANGED
@@ -635,6 +635,54 @@ const collectModuleScopeNames = (sourceFile) => {
635
635
  }
636
636
  return names;
637
637
  };
638
+ /**
639
+ * Every identifier in this module that reads the binding `name` declared at `declaration`.
640
+ *
641
+ * Replaces `nameNode.findReferencesAsNodes()`, which is a TypeScript *language-service*
642
+ * query. The first such query forces `synchronizeHostData` -> `createProgram`, binding the
643
+ * whole transitive `.d.ts` closure of the project — the exact cost `createTsProject`'s
644
+ * `skipAddingFilesFromTsConfig`, `skipFileDependencyResolution` and `skipLoadingLibFiles`
645
+ * exist to avoid, and which none of them govern. On a 2,278-file app it loaded 24,081 source
646
+ * files and 4.4 GB of AST and symbols, 80% of the heap, and OOMed the build. The retained
647
+ * strings were `googleapis` and `typescript`; none of it can reference a recipe binding.
648
+ *
649
+ * A recipe binding is module-scoped or imported, so every read of it is in this file. A
650
+ * syntactic walk answers the same question over one AST the parser has already built.
651
+ *
652
+ * ## Where this deliberately over-reports
653
+ *
654
+ * Shadowing is not resolved. If any nested scope declares the same name, every matching
655
+ * identifier is returned rather than only the ones that bind to `declaration`. That direction
656
+ * is chosen on purpose: over-reporting fails the build with a diagnostic naming a real line,
657
+ * while under-reporting ships an element whose class has no rule behind it and says nothing.
658
+ * Shadowing a recipe binding is rare; silently shipping unstyled markup is not recoverable.
659
+ */
660
+ const localReferencesTo = (sourceFile, name, declaration) => {
661
+ const references = [];
662
+ for (const identifier of sourceFile.getDescendantsOfKind(ts_morph.SyntaxKind.Identifier)) {
663
+ if (identifier.getText() !== name) continue;
664
+ if (identifier === declaration) continue;
665
+ const parent = identifier.getParent();
666
+ if (!parent) continue;
667
+ if (ts_morph.Node.isPropertyAccessExpression(parent) && parent.getNameNode() === identifier) continue;
668
+ if (ts_morph.Node.isPropertyAssignment(parent) && parent.getNameNode() === identifier) continue;
669
+ if (ts_morph.Node.isBindingElement(parent) && parent.getPropertyNameNode() === identifier) continue;
670
+ if ((ts_morph.Node.isJsxOpeningElement(parent) || ts_morph.Node.isJsxSelfClosingElement(parent) || ts_morph.Node.isJsxClosingElement(parent)) && parent.getTagNameNode() === identifier) continue;
671
+ if (ts_morph.Node.isJsxAttribute(parent) && parent.getNameNode() === identifier) continue;
672
+ if ((ts_morph.Node.isMethodDeclaration(parent) || ts_morph.Node.isPropertyDeclaration(parent) || ts_morph.Node.isGetAccessorDeclaration(parent) || ts_morph.Node.isSetAccessorDeclaration(parent) || ts_morph.Node.isMethodSignature(parent) || ts_morph.Node.isPropertySignature(parent) || ts_morph.Node.isEnumMember(parent)) && parent.getNameNode() === identifier) continue;
673
+ if ((ts_morph.Node.isVariableDeclaration(parent) || ts_morph.Node.isParameterDeclaration(parent) || ts_morph.Node.isFunctionDeclaration(parent) || ts_morph.Node.isClassDeclaration(parent) || ts_morph.Node.isBindingElement(parent)) && parent.getNameNode() === identifier) continue;
674
+ if (ts_morph.Node.isImportSpecifier(parent) || ts_morph.Node.isExportSpecifier(parent)) {
675
+ if (parent.getNameNode() !== identifier) continue;
676
+ if (ts_morph.Node.isExportSpecifier(parent)) {
677
+ references.push(identifier);
678
+ continue;
679
+ }
680
+ continue;
681
+ }
682
+ references.push(identifier);
683
+ }
684
+ return references;
685
+ };
638
686
  //#endregion
639
687
  //#region src/fold-recipe.ts
640
688
  /**
@@ -2383,30 +2431,36 @@ const foldSource = (options) => {
2383
2431
  * also allowed to remain when it joins an arbitrary external class; only fully analyzable
2384
2432
  * arguments receive Bamboo's semantic composition guarantee.
2385
2433
  */
2434
+ /** The specifier this module imported `binding` through, if it did. */
2435
+ function importSpecifierFor(sourceFile, binding) {
2436
+ for (const declaration of sourceFile.getImportDeclarations()) {
2437
+ if (declaration.isTypeOnly()) continue;
2438
+ for (const named of declaration.getNamedImports()) {
2439
+ if (named.isTypeOnly()) continue;
2440
+ if ((named.getAliasNode() ?? named.getNameNode()).getText() === binding) return named.getAliasNode() ?? named.getNameNode();
2441
+ }
2442
+ }
2443
+ }
2386
2444
  function reportRuntimeBindings() {
2387
2445
  const sourceFile = ownSourceFile ?? candidates[0]?.node.getSourceFile() ?? recipeDefinitions[0]?.call.getSourceFile();
2388
2446
  if (!sourceFile) return;
2389
- for (const [binding, entry] of recipeConfigs) {
2447
+ const importedRecipeBindings = new Set(parserResult.importedRecipes?.keys() ?? []);
2448
+ for (const binding of new Set([...recipeConfigs.keys(), ...importedRecipeBindings])) {
2449
+ const entry = recipeConfigs.get(binding);
2390
2450
  if (entry === AMBIGUOUS) continue;
2391
- const definition = entry.box?.getNode?.();
2392
- if (!definition || definition.getSourceFile() !== sourceFile) continue;
2393
- const nameNode = definition.getFirstAncestorByKind(ts_morph.SyntaxKind.VariableDeclaration)?.getNameNode();
2451
+ if (!entry && !importedRecipeBindings.has(binding)) continue;
2452
+ const definition = entry?.box?.getNode?.();
2453
+ const nameNode = definition?.getSourceFile() === sourceFile ? definition?.getFirstAncestorByKind(ts_morph.SyntaxKind.VariableDeclaration)?.getNameNode() : importSpecifierFor(sourceFile, binding);
2394
2454
  if (!nameNode || !ts_morph.Node.isIdentifier(nameNode)) continue;
2395
- const references = nameNode.findReferencesAsNodes();
2455
+ const references = localReferencesTo(sourceFile, binding, nameNode);
2396
2456
  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;
2397
2457
  const survivor = references.find((ref) => !applied.some(([from, to]) => ref.getStart() >= from && ref.getStart() < to));
2398
2458
  if (!survivor) continue;
2399
- const survivorFile = survivor.getSourceFile();
2400
- const foreign = survivorFile !== sourceFile;
2401
2459
  skipped.push({
2402
2460
  name: binding,
2403
2461
  reason: "runtime-binding",
2404
- start: foreign ? nameNode.getStart() : survivor.getStart(),
2405
- end: foreign ? nameNode.getEnd() : survivor.getEnd(),
2406
- ...foreign ? { origin: {
2407
- filePath: survivorFile.getFilePath(),
2408
- line: survivor.getStartLineNumber()
2409
- } } : {}
2462
+ start: survivor.getStart(),
2463
+ end: survivor.getEnd()
2410
2464
  });
2411
2465
  }
2412
2466
  const bambooModules = [
@@ -2870,12 +2924,7 @@ const bamboocss = (options = {}) => {
2870
2924
  for (const entry of result.skipped) {
2871
2925
  if (entry.reason === "not-imported" || entry.reason === "overlapping") continue;
2872
2926
  if (entry.name === "cx" && entry.reason === "dynamic") continue;
2873
- addSurvivor(entry.origin ? {
2874
- file: entry.origin.filePath,
2875
- line: entry.origin.line,
2876
- name: entry.name,
2877
- reason: entry.reason
2878
- } : {
2927
+ addSurvivor({
2879
2928
  file: filePath,
2880
2929
  line: lineAt(code, entry.start),
2881
2930
  name: entry.name,
package/dist/index.mjs CHANGED
@@ -605,6 +605,54 @@ const collectModuleScopeNames = (sourceFile) => {
605
605
  }
606
606
  return names;
607
607
  };
608
+ /**
609
+ * Every identifier in this module that reads the binding `name` declared at `declaration`.
610
+ *
611
+ * Replaces `nameNode.findReferencesAsNodes()`, which is a TypeScript *language-service*
612
+ * query. The first such query forces `synchronizeHostData` -> `createProgram`, binding the
613
+ * whole transitive `.d.ts` closure of the project — the exact cost `createTsProject`'s
614
+ * `skipAddingFilesFromTsConfig`, `skipFileDependencyResolution` and `skipLoadingLibFiles`
615
+ * exist to avoid, and which none of them govern. On a 2,278-file app it loaded 24,081 source
616
+ * files and 4.4 GB of AST and symbols, 80% of the heap, and OOMed the build. The retained
617
+ * strings were `googleapis` and `typescript`; none of it can reference a recipe binding.
618
+ *
619
+ * A recipe binding is module-scoped or imported, so every read of it is in this file. A
620
+ * syntactic walk answers the same question over one AST the parser has already built.
621
+ *
622
+ * ## Where this deliberately over-reports
623
+ *
624
+ * Shadowing is not resolved. If any nested scope declares the same name, every matching
625
+ * identifier is returned rather than only the ones that bind to `declaration`. That direction
626
+ * is chosen on purpose: over-reporting fails the build with a diagnostic naming a real line,
627
+ * while under-reporting ships an element whose class has no rule behind it and says nothing.
628
+ * Shadowing a recipe binding is rare; silently shipping unstyled markup is not recoverable.
629
+ */
630
+ const localReferencesTo = (sourceFile, name, declaration) => {
631
+ const references = [];
632
+ for (const identifier of sourceFile.getDescendantsOfKind(SyntaxKind.Identifier)) {
633
+ if (identifier.getText() !== name) continue;
634
+ if (identifier === declaration) continue;
635
+ const parent = identifier.getParent();
636
+ if (!parent) continue;
637
+ if (Node.isPropertyAccessExpression(parent) && parent.getNameNode() === identifier) continue;
638
+ if (Node.isPropertyAssignment(parent) && parent.getNameNode() === identifier) continue;
639
+ if (Node.isBindingElement(parent) && parent.getPropertyNameNode() === identifier) continue;
640
+ if ((Node.isJsxOpeningElement(parent) || Node.isJsxSelfClosingElement(parent) || Node.isJsxClosingElement(parent)) && parent.getTagNameNode() === identifier) continue;
641
+ if (Node.isJsxAttribute(parent) && parent.getNameNode() === identifier) continue;
642
+ if ((Node.isMethodDeclaration(parent) || Node.isPropertyDeclaration(parent) || Node.isGetAccessorDeclaration(parent) || Node.isSetAccessorDeclaration(parent) || Node.isMethodSignature(parent) || Node.isPropertySignature(parent) || Node.isEnumMember(parent)) && parent.getNameNode() === identifier) continue;
643
+ if ((Node.isVariableDeclaration(parent) || Node.isParameterDeclaration(parent) || Node.isFunctionDeclaration(parent) || Node.isClassDeclaration(parent) || Node.isBindingElement(parent)) && parent.getNameNode() === identifier) continue;
644
+ if (Node.isImportSpecifier(parent) || Node.isExportSpecifier(parent)) {
645
+ if (parent.getNameNode() !== identifier) continue;
646
+ if (Node.isExportSpecifier(parent)) {
647
+ references.push(identifier);
648
+ continue;
649
+ }
650
+ continue;
651
+ }
652
+ references.push(identifier);
653
+ }
654
+ return references;
655
+ };
608
656
  //#endregion
609
657
  //#region src/fold-recipe.ts
610
658
  /**
@@ -2353,30 +2401,36 @@ const foldSource = (options) => {
2353
2401
  * also allowed to remain when it joins an arbitrary external class; only fully analyzable
2354
2402
  * arguments receive Bamboo's semantic composition guarantee.
2355
2403
  */
2404
+ /** The specifier this module imported `binding` through, if it did. */
2405
+ function importSpecifierFor(sourceFile, binding) {
2406
+ for (const declaration of sourceFile.getImportDeclarations()) {
2407
+ if (declaration.isTypeOnly()) continue;
2408
+ for (const named of declaration.getNamedImports()) {
2409
+ if (named.isTypeOnly()) continue;
2410
+ if ((named.getAliasNode() ?? named.getNameNode()).getText() === binding) return named.getAliasNode() ?? named.getNameNode();
2411
+ }
2412
+ }
2413
+ }
2356
2414
  function reportRuntimeBindings() {
2357
2415
  const sourceFile = ownSourceFile ?? candidates[0]?.node.getSourceFile() ?? recipeDefinitions[0]?.call.getSourceFile();
2358
2416
  if (!sourceFile) return;
2359
- for (const [binding, entry] of recipeConfigs) {
2417
+ const importedRecipeBindings = new Set(parserResult.importedRecipes?.keys() ?? []);
2418
+ for (const binding of new Set([...recipeConfigs.keys(), ...importedRecipeBindings])) {
2419
+ const entry = recipeConfigs.get(binding);
2360
2420
  if (entry === AMBIGUOUS) continue;
2361
- const definition = entry.box?.getNode?.();
2362
- if (!definition || definition.getSourceFile() !== sourceFile) continue;
2363
- const nameNode = definition.getFirstAncestorByKind(SyntaxKind.VariableDeclaration)?.getNameNode();
2421
+ if (!entry && !importedRecipeBindings.has(binding)) continue;
2422
+ const definition = entry?.box?.getNode?.();
2423
+ const nameNode = definition?.getSourceFile() === sourceFile ? definition?.getFirstAncestorByKind(SyntaxKind.VariableDeclaration)?.getNameNode() : importSpecifierFor(sourceFile, binding);
2364
2424
  if (!nameNode || !Node.isIdentifier(nameNode)) continue;
2365
- const references = nameNode.findReferencesAsNodes();
2425
+ const references = localReferencesTo(sourceFile, binding, nameNode);
2366
2426
  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;
2367
2427
  const survivor = references.find((ref) => !applied.some(([from, to]) => ref.getStart() >= from && ref.getStart() < to));
2368
2428
  if (!survivor) continue;
2369
- const survivorFile = survivor.getSourceFile();
2370
- const foreign = survivorFile !== sourceFile;
2371
2429
  skipped.push({
2372
2430
  name: binding,
2373
2431
  reason: "runtime-binding",
2374
- start: foreign ? nameNode.getStart() : survivor.getStart(),
2375
- end: foreign ? nameNode.getEnd() : survivor.getEnd(),
2376
- ...foreign ? { origin: {
2377
- filePath: survivorFile.getFilePath(),
2378
- line: survivor.getStartLineNumber()
2379
- } } : {}
2432
+ start: survivor.getStart(),
2433
+ end: survivor.getEnd()
2380
2434
  });
2381
2435
  }
2382
2436
  const bambooModules = [
@@ -2840,12 +2894,7 @@ const bamboocss = (options = {}) => {
2840
2894
  for (const entry of result.skipped) {
2841
2895
  if (entry.reason === "not-imported" || entry.reason === "overlapping") continue;
2842
2896
  if (entry.name === "cx" && entry.reason === "dynamic") continue;
2843
- addSurvivor(entry.origin ? {
2844
- file: entry.origin.filePath,
2845
- line: entry.origin.line,
2846
- name: entry.name,
2847
- reason: entry.reason
2848
- } : {
2897
+ addSurvivor({
2849
2898
  file: filePath,
2850
2899
  line: lineAt(code, entry.start),
2851
2900
  name: entry.name,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bamboocss/vite",
3
- "version": "1.35.5",
3
+ "version": "1.36.0",
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.35.5",
44
- "@bamboocss/core": "1.35.5",
45
- "@bamboocss/extractor": "1.35.5",
46
- "@bamboocss/shared": "1.35.5",
47
- "@bamboocss/logger": "1.35.5",
48
- "@bamboocss/node": "1.35.5",
49
- "@bamboocss/types": "1.35.5"
43
+ "@bamboocss/config": "1.36.0",
44
+ "@bamboocss/core": "1.36.0",
45
+ "@bamboocss/extractor": "1.36.0",
46
+ "@bamboocss/logger": "1.36.0",
47
+ "@bamboocss/node": "1.36.0",
48
+ "@bamboocss/shared": "1.36.0",
49
+ "@bamboocss/types": "1.36.0"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@jridgewell/trace-mapping": "^0.3.31",
53
53
  "vite": "7.2.6",
54
- "@bamboocss/fixture": "1.35.5"
54
+ "@bamboocss/fixture": "1.36.0"
55
55
  },
56
56
  "peerDependencies": {
57
57
  "vite": ">=5"