@bamboocss/vite 1.35.4 → 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,29 +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
- if (skipped.filter((item) => SURVIVES_TO_RUNTIME.has(item.reason) && item.end > item.start).some((item) => nameNode.findReferencesAsNodes().some((ref) => ref.getStart() >= item.start && ref.getStart() < item.end))) continue;
2396
- const survivor = nameNode.findReferencesAsNodes().find((ref) => !applied.some(([from, to]) => ref.getStart() >= from && ref.getStart() < to));
2455
+ const references = localReferencesTo(sourceFile, binding, nameNode);
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;
2457
+ const survivor = references.find((ref) => !applied.some(([from, to]) => ref.getStart() >= from && ref.getStart() < to));
2397
2458
  if (!survivor) continue;
2398
- const survivorFile = survivor.getSourceFile();
2399
- const foreign = survivorFile !== sourceFile;
2400
2459
  skipped.push({
2401
2460
  name: binding,
2402
2461
  reason: "runtime-binding",
2403
- start: foreign ? nameNode.getStart() : survivor.getStart(),
2404
- end: foreign ? nameNode.getEnd() : survivor.getEnd(),
2405
- ...foreign ? { origin: {
2406
- filePath: survivorFile.getFilePath(),
2407
- line: survivor.getStartLineNumber()
2408
- } } : {}
2462
+ start: survivor.getStart(),
2463
+ end: survivor.getEnd()
2409
2464
  });
2410
2465
  }
2411
2466
  const bambooModules = [
@@ -2869,12 +2924,7 @@ const bamboocss = (options = {}) => {
2869
2924
  for (const entry of result.skipped) {
2870
2925
  if (entry.reason === "not-imported" || entry.reason === "overlapping") continue;
2871
2926
  if (entry.name === "cx" && entry.reason === "dynamic") continue;
2872
- addSurvivor(entry.origin ? {
2873
- file: entry.origin.filePath,
2874
- line: entry.origin.line,
2875
- name: entry.name,
2876
- reason: entry.reason
2877
- } : {
2927
+ addSurvivor({
2878
2928
  file: filePath,
2879
2929
  line: lineAt(code, entry.start),
2880
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,29 +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
- if (skipped.filter((item) => SURVIVES_TO_RUNTIME.has(item.reason) && item.end > item.start).some((item) => nameNode.findReferencesAsNodes().some((ref) => ref.getStart() >= item.start && ref.getStart() < item.end))) continue;
2366
- const survivor = nameNode.findReferencesAsNodes().find((ref) => !applied.some(([from, to]) => ref.getStart() >= from && ref.getStart() < to));
2425
+ const references = localReferencesTo(sourceFile, binding, nameNode);
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;
2427
+ const survivor = references.find((ref) => !applied.some(([from, to]) => ref.getStart() >= from && ref.getStart() < to));
2367
2428
  if (!survivor) continue;
2368
- const survivorFile = survivor.getSourceFile();
2369
- const foreign = survivorFile !== sourceFile;
2370
2429
  skipped.push({
2371
2430
  name: binding,
2372
2431
  reason: "runtime-binding",
2373
- start: foreign ? nameNode.getStart() : survivor.getStart(),
2374
- end: foreign ? nameNode.getEnd() : survivor.getEnd(),
2375
- ...foreign ? { origin: {
2376
- filePath: survivorFile.getFilePath(),
2377
- line: survivor.getStartLineNumber()
2378
- } } : {}
2432
+ start: survivor.getStart(),
2433
+ end: survivor.getEnd()
2379
2434
  });
2380
2435
  }
2381
2436
  const bambooModules = [
@@ -2839,12 +2894,7 @@ const bamboocss = (options = {}) => {
2839
2894
  for (const entry of result.skipped) {
2840
2895
  if (entry.reason === "not-imported" || entry.reason === "overlapping") continue;
2841
2896
  if (entry.name === "cx" && entry.reason === "dynamic") continue;
2842
- addSurvivor(entry.origin ? {
2843
- file: entry.origin.filePath,
2844
- line: entry.origin.line,
2845
- name: entry.name,
2846
- reason: entry.reason
2847
- } : {
2897
+ addSurvivor({
2848
2898
  file: filePath,
2849
2899
  line: lineAt(code, entry.start),
2850
2900
  name: entry.name,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bamboocss/vite",
3
- "version": "1.35.4",
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.4",
44
- "@bamboocss/core": "1.35.4",
45
- "@bamboocss/extractor": "1.35.4",
46
- "@bamboocss/logger": "1.35.4",
47
- "@bamboocss/node": "1.35.4",
48
- "@bamboocss/types": "1.35.4",
49
- "@bamboocss/shared": "1.35.4"
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.4"
54
+ "@bamboocss/fixture": "1.36.0"
55
55
  },
56
56
  "peerDependencies": {
57
57
  "vite": ">=5"