@bamboocss/vite 1.24.0 → 1.26.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 +129 -11
- package/dist/index.mjs +129 -11
- package/package.json +9 -9
package/dist/index.cjs
CHANGED
|
@@ -795,6 +795,8 @@ const collectRecipeConfigs = (parserResult) => {
|
|
|
795
795
|
};
|
|
796
796
|
/** The generated binding a lowered dynamic axis calls. Lives in `cx`, which pulls no engine. */
|
|
797
797
|
const RECIPE_PICK_HELPER = "cvaPick";
|
|
798
|
+
/** What `recipe.splitVariantProps` calls, reached directly once the call is lowered. */
|
|
799
|
+
const SPLIT_PROPS_HELPER = "splitProps";
|
|
798
800
|
const HELPER = RECIPE_PICK_HELPER;
|
|
799
801
|
/** Marker for a binding the fold must never resolve — declared twice, or unresolvable. */
|
|
800
802
|
const AMBIGUOUS = Object.freeze({
|
|
@@ -802,6 +804,9 @@ const AMBIGUOUS = Object.freeze({
|
|
|
802
804
|
name: "",
|
|
803
805
|
box: void 0
|
|
804
806
|
});
|
|
807
|
+
/** `.size`, or `["x-large"]` when the variant is not a valid identifier. */
|
|
808
|
+
const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
|
|
809
|
+
const propertyAccess = (key) => IDENTIFIER.test(key) ? `.${key}` : `[${JSON.stringify(key)}]`;
|
|
805
810
|
const LITERAL_KINDS = new Set([
|
|
806
811
|
ts_morph.SyntaxKind.StringLiteral,
|
|
807
812
|
ts_morph.SyntaxKind.NoSubstitutionTemplateLiteral,
|
|
@@ -845,7 +850,7 @@ const propertyKey = (nameNode) => {
|
|
|
845
850
|
* there is nothing to match — the host here is any import of the generated css module, which
|
|
846
851
|
* a file defining a recipe necessarily has, since `cva` came from it.
|
|
847
852
|
*/
|
|
848
|
-
const ensureRecipeHelperImport = (call, isBambooCssModule, isGeneratedCssModule, isShadowed) => {
|
|
853
|
+
const ensureRecipeHelperImport = (imported, call, isBambooCssModule, isGeneratedCssModule, isShadowed) => {
|
|
849
854
|
const sourceFile = call.getSourceFile();
|
|
850
855
|
let host;
|
|
851
856
|
for (const declaration of sourceFile.getImportDeclarations()) {
|
|
@@ -853,7 +858,7 @@ const ensureRecipeHelperImport = (call, isBambooCssModule, isGeneratedCssModule,
|
|
|
853
858
|
if (declaration.isTypeOnly()) continue;
|
|
854
859
|
for (const named of declaration.getNamedImports()) {
|
|
855
860
|
if (named.isTypeOnly()) continue;
|
|
856
|
-
if (named.getNameNode().getText() ===
|
|
861
|
+
if (named.getNameNode().getText() === imported) {
|
|
857
862
|
if (!isBambooCssModule(mod)) return void 0;
|
|
858
863
|
const local = (named.getAliasNode() ?? named.getNameNode()).getText();
|
|
859
864
|
return isShadowed(call, local) ? void 0 : { name: local };
|
|
@@ -862,15 +867,15 @@ const ensureRecipeHelperImport = (call, isBambooCssModule, isGeneratedCssModule,
|
|
|
862
867
|
if (!host && isGeneratedCssModule(mod) && declaration.getNamedImports().length > 0) host = declaration;
|
|
863
868
|
}
|
|
864
869
|
if (!host) return void 0;
|
|
865
|
-
if (declaredAtModuleScope(sourceFile).has(
|
|
866
|
-
if (isShadowed(call,
|
|
870
|
+
if (declaredAtModuleScope(sourceFile).has(imported)) return void 0;
|
|
871
|
+
if (isShadowed(call, imported)) return void 0;
|
|
867
872
|
const last = host.getNamedImports().at(-1);
|
|
868
873
|
if (!last) return void 0;
|
|
869
874
|
return {
|
|
870
|
-
name:
|
|
875
|
+
name: imported,
|
|
871
876
|
insert: {
|
|
872
877
|
pos: last.getEnd(),
|
|
873
|
-
names: [
|
|
878
|
+
names: [imported]
|
|
874
879
|
}
|
|
875
880
|
};
|
|
876
881
|
};
|
|
@@ -916,11 +921,28 @@ const lowerRecipeCall = (call, entry, ctx, isInert, resolvedSelection) => {
|
|
|
916
921
|
const effectful = [];
|
|
917
922
|
if (args.length === 1) {
|
|
918
923
|
const arg = args[0];
|
|
919
|
-
if (!arg
|
|
924
|
+
if (!arg) return {
|
|
920
925
|
kind: "decline",
|
|
921
926
|
reason: "dynamic"
|
|
922
927
|
};
|
|
923
|
-
|
|
928
|
+
/**
|
|
929
|
+
* `input(variantProps)` — a selection the build cannot see inside.
|
|
930
|
+
*
|
|
931
|
+
* The classes are still knowable: a recipe emits one per declared variant, so the call is
|
|
932
|
+
* one term per variant reading that binding. This is the shape a wrapper component takes,
|
|
933
|
+
* where the variants are the component's public API and cannot be literals by definition.
|
|
934
|
+
*
|
|
935
|
+
* An identifier only. Each variant reads the binding again, and re-reading anything else —
|
|
936
|
+
* a call, a property access — would evaluate it once per axis instead of once.
|
|
937
|
+
*/
|
|
938
|
+
if (ts_morph.Node.isIdentifier(arg)) {
|
|
939
|
+
const binding = arg.getText();
|
|
940
|
+
for (const key of Object.keys(config.variants ?? {})) dynamicAxes.set(key, `${binding}${propertyAccess(key)}`);
|
|
941
|
+
} else if (!ts_morph.Node.isObjectLiteralExpression(arg)) return {
|
|
942
|
+
kind: "decline",
|
|
943
|
+
reason: "dynamic"
|
|
944
|
+
};
|
|
945
|
+
else for (const property of arg.getProperties()) {
|
|
924
946
|
if (ts_morph.Node.isSpreadAssignment(property)) return {
|
|
925
947
|
kind: "decline",
|
|
926
948
|
reason: "dynamic"
|
|
@@ -1415,7 +1437,7 @@ const hasStyles = (data) => data.length > 0 && data.every((entry) => entry != nu
|
|
|
1415
1437
|
const argumentsAccountedFor = (call, boxNode) => {
|
|
1416
1438
|
if (!ts_morph.Node.isCallExpression(call)) return false;
|
|
1417
1439
|
const args = call.getArguments();
|
|
1418
|
-
if (args.length === 0) return
|
|
1440
|
+
if (args.length === 0) return true;
|
|
1419
1441
|
if (_bamboocss_extractor.box.isArray(boxNode) && boxNode.getNode() === call) {
|
|
1420
1442
|
if (boxNode.value.length !== args.length) return false;
|
|
1421
1443
|
return args.every((arg, index) => accountsForSource(arg, boxNode.value[index]));
|
|
@@ -1540,6 +1562,8 @@ const foldSource = (options) => {
|
|
|
1540
1562
|
* saving real, and it is only correct to claim it once nothing reads the binding.
|
|
1541
1563
|
*/
|
|
1542
1564
|
const recipeCalls = /* @__PURE__ */ new Map();
|
|
1565
|
+
/** Bindings whose `splitVariantProps` was rewritten, so that access no longer reads them. */
|
|
1566
|
+
const loweredSplitProps = /* @__PURE__ */ new Set();
|
|
1543
1567
|
/** Ranges already reported as declined, so one call is never counted twice. */
|
|
1544
1568
|
const reportedRanges = /* @__PURE__ */ new Set();
|
|
1545
1569
|
const importCache = /* @__PURE__ */ new Map();
|
|
@@ -1551,6 +1575,58 @@ const foldSource = (options) => {
|
|
|
1551
1575
|
}
|
|
1552
1576
|
return names;
|
|
1553
1577
|
};
|
|
1578
|
+
/**
|
|
1579
|
+
* Is this binding the variant half of `<recipe>.splitVariantProps(...)`?
|
|
1580
|
+
*
|
|
1581
|
+
* Read off the declaration rather than the type, so it is the same recipe and the same
|
|
1582
|
+
* destructuring position the source actually wrote.
|
|
1583
|
+
*/
|
|
1584
|
+
const isSplitVariantPropsOf = (binding, recipe) => {
|
|
1585
|
+
for (const declaration of binding.getSourceFile().getDescendantsOfKind(ts_morph.SyntaxKind.VariableDeclaration)) {
|
|
1586
|
+
const nameNode = declaration.getNameNode();
|
|
1587
|
+
if (!ts_morph.Node.isArrayBindingPattern(nameNode)) continue;
|
|
1588
|
+
const first = nameNode.getElements()[0];
|
|
1589
|
+
if (!first || !ts_morph.Node.isBindingElement(first)) continue;
|
|
1590
|
+
if (first.getNameNode().getText() !== binding.getText()) continue;
|
|
1591
|
+
const initializer = declaration.getInitializer();
|
|
1592
|
+
if (!initializer || !ts_morph.Node.isCallExpression(initializer)) return false;
|
|
1593
|
+
const callee = initializer.getExpression();
|
|
1594
|
+
if (!ts_morph.Node.isPropertyAccessExpression(callee)) return false;
|
|
1595
|
+
return callee.getName() === "splitVariantProps" && callee.getExpression().getText() === recipe;
|
|
1596
|
+
}
|
|
1597
|
+
return false;
|
|
1598
|
+
};
|
|
1599
|
+
/**
|
|
1600
|
+
* Lower a config recipe call the same way an inline one lowers.
|
|
1601
|
+
*
|
|
1602
|
+
* The config lives in `ctx.recipes` rather than in the module, and the classes are named
|
|
1603
|
+
* from it identically — so this is the same `lowerRecipeCall`, handed the config from a
|
|
1604
|
+
* different place. Slot recipes are excluded by the caller: they resolve to one class per
|
|
1605
|
+
* slot rather than to a string.
|
|
1606
|
+
*/
|
|
1607
|
+
const lowerConfigRecipeCall = (call, name) => {
|
|
1608
|
+
const config = ctx.recipes.getConfig(name);
|
|
1609
|
+
if (!config || config.slots !== void 0) return void 0;
|
|
1610
|
+
const className = config.className;
|
|
1611
|
+
if (!className) return void 0;
|
|
1612
|
+
if (!ts_morph.Node.isCallExpression(call)) return void 0;
|
|
1613
|
+
const argument = call.getArguments()[0];
|
|
1614
|
+
if (!argument || !ts_morph.Node.isIdentifier(argument) || !isSplitVariantPropsOf(argument, name)) return void 0;
|
|
1615
|
+
const lowered = lowerRecipeCall(call, {
|
|
1616
|
+
config,
|
|
1617
|
+
name: className,
|
|
1618
|
+
box: void 0
|
|
1619
|
+
}, ctx, isInertExpression);
|
|
1620
|
+
if (lowered.kind !== "expression") return void 0;
|
|
1621
|
+
const helper = ensureRecipeHelperImport(RECIPE_PICK_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed);
|
|
1622
|
+
if (!helper) return void 0;
|
|
1623
|
+
return {
|
|
1624
|
+
replacement: helper.name === "cvaPick" ? lowered.expression : lowered.expression.replaceAll(`${RECIPE_PICK_HELPER}(`, `${helper.name}(`),
|
|
1625
|
+
className: lowered.staticClasses,
|
|
1626
|
+
classNames: lowered.classNames,
|
|
1627
|
+
insert: helper.insert
|
|
1628
|
+
};
|
|
1629
|
+
};
|
|
1554
1630
|
for (const item of parserResult.toArray()) {
|
|
1555
1631
|
const type = item.type ?? "";
|
|
1556
1632
|
const name = item.name ?? type;
|
|
@@ -1681,7 +1757,7 @@ const foldSource = (options) => {
|
|
|
1681
1757
|
const entry = recipeConfigs.get(name);
|
|
1682
1758
|
const lowered = lowerRecipeCall(call, entry, ctx, isInertExpression, resolvedSelection);
|
|
1683
1759
|
if (lowered.kind === "expression") {
|
|
1684
|
-
const helper = ensureRecipeHelperImport(call, isBambooCssModule, isGeneratedCssModule, isShadowed);
|
|
1760
|
+
const helper = ensureRecipeHelperImport(RECIPE_PICK_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed);
|
|
1685
1761
|
if (helper) {
|
|
1686
1762
|
tally.lowered++;
|
|
1687
1763
|
candidates.push({
|
|
@@ -1792,7 +1868,7 @@ const foldSource = (options) => {
|
|
|
1792
1868
|
});
|
|
1793
1869
|
continue;
|
|
1794
1870
|
}
|
|
1795
|
-
if (!isStaticBox(item.box) || !hasStyles(item.data) || !argumentsAccountedFor(call, item.box)) {
|
|
1871
|
+
if (!(ts_morph.Node.isCallExpression(call) && call.getArguments().length === 0 && item.data.length === 1) && !isStaticBox(item.box) || !hasStyles(item.data) || !argumentsAccountedFor(call, item.box)) {
|
|
1796
1872
|
const partial = partial_ ? tryPartial(item, call, rootName) : void 0;
|
|
1797
1873
|
if (partial) {
|
|
1798
1874
|
candidates.push({
|
|
@@ -1805,6 +1881,18 @@ const foldSource = (options) => {
|
|
|
1805
1881
|
});
|
|
1806
1882
|
continue;
|
|
1807
1883
|
}
|
|
1884
|
+
const lowered = type === "recipe" && !slot ? lowerConfigRecipeCall(call, name) : void 0;
|
|
1885
|
+
if (lowered) {
|
|
1886
|
+
candidates.push({
|
|
1887
|
+
item,
|
|
1888
|
+
call,
|
|
1889
|
+
node: call,
|
|
1890
|
+
start,
|
|
1891
|
+
end,
|
|
1892
|
+
...lowered
|
|
1893
|
+
});
|
|
1894
|
+
continue;
|
|
1895
|
+
}
|
|
1808
1896
|
skipped.push({
|
|
1809
1897
|
name,
|
|
1810
1898
|
reason: "dynamic",
|
|
@@ -1932,6 +2020,36 @@ const foldSource = (options) => {
|
|
|
1932
2020
|
});
|
|
1933
2021
|
collectSourceFiles(item.box, dependencyScan);
|
|
1934
2022
|
}
|
|
2023
|
+
const recipeSourceFile = [...recipeConfigs?.values() ?? []].find((entry) => entry.box)?.box?.getNode?.()?.getSourceFile() ?? candidates[0]?.node.getSourceFile();
|
|
2024
|
+
if (recipeSourceFile) for (const access of recipeSourceFile.getDescendantsOfKind(ts_morph.SyntaxKind.PropertyAccessExpression)) {
|
|
2025
|
+
if (access.getName() !== "splitVariantProps") continue;
|
|
2026
|
+
const target = access.getExpression();
|
|
2027
|
+
if (!ts_morph.Node.isIdentifier(target)) continue;
|
|
2028
|
+
if (isShadowed(access, target.getText())) continue;
|
|
2029
|
+
const local = recipeConfigs?.get(target.getText());
|
|
2030
|
+
const importedConfig = !local && importsFor(recipeSourceFile).has(target.getText()) ? ctx.recipes.getConfig(target.getText()) : void 0;
|
|
2031
|
+
const entry = local && local !== AMBIGUOUS ? local : importedConfig ? {
|
|
2032
|
+
config: importedConfig,
|
|
2033
|
+
name: "",
|
|
2034
|
+
box: void 0
|
|
2035
|
+
} : void 0;
|
|
2036
|
+
if (!entry) continue;
|
|
2037
|
+
const call = access.getParent();
|
|
2038
|
+
if (!ts_morph.Node.isCallExpression(call) || call.getExpression() !== access) continue;
|
|
2039
|
+
const args = call.getArguments();
|
|
2040
|
+
if (args.length !== 1) continue;
|
|
2041
|
+
const start = call.getStart();
|
|
2042
|
+
const end = call.getEnd();
|
|
2043
|
+
if (code.slice(start, end) !== call.getText()) continue;
|
|
2044
|
+
if (collides([[start, end]])) continue;
|
|
2045
|
+
const helper = ensureRecipeHelperImport(SPLIT_PROPS_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed);
|
|
2046
|
+
if (!helper) continue;
|
|
2047
|
+
const keys = Object.keys(entry.config.variants ?? {});
|
|
2048
|
+
magic.overwrite(start, end, `${helper.name}(${args[0].getText()}, ${JSON.stringify(keys)})`);
|
|
2049
|
+
applyInsert(helper.insert);
|
|
2050
|
+
applied.push([start, end]);
|
|
2051
|
+
loweredSplitProps.add(target.getText());
|
|
2052
|
+
}
|
|
1935
2053
|
for (const [binding, tally] of recipeCalls) {
|
|
1936
2054
|
if (!tally.seen || tally.lowered !== tally.seen) continue;
|
|
1937
2055
|
const definition = recipeConfigs?.get(binding)?.box?.getNode?.();
|
package/dist/index.mjs
CHANGED
|
@@ -768,6 +768,8 @@ const collectRecipeConfigs = (parserResult) => {
|
|
|
768
768
|
};
|
|
769
769
|
/** The generated binding a lowered dynamic axis calls. Lives in `cx`, which pulls no engine. */
|
|
770
770
|
const RECIPE_PICK_HELPER = "cvaPick";
|
|
771
|
+
/** What `recipe.splitVariantProps` calls, reached directly once the call is lowered. */
|
|
772
|
+
const SPLIT_PROPS_HELPER = "splitProps";
|
|
771
773
|
const HELPER = RECIPE_PICK_HELPER;
|
|
772
774
|
/** Marker for a binding the fold must never resolve — declared twice, or unresolvable. */
|
|
773
775
|
const AMBIGUOUS = Object.freeze({
|
|
@@ -775,6 +777,9 @@ const AMBIGUOUS = Object.freeze({
|
|
|
775
777
|
name: "",
|
|
776
778
|
box: void 0
|
|
777
779
|
});
|
|
780
|
+
/** `.size`, or `["x-large"]` when the variant is not a valid identifier. */
|
|
781
|
+
const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
|
|
782
|
+
const propertyAccess = (key) => IDENTIFIER.test(key) ? `.${key}` : `[${JSON.stringify(key)}]`;
|
|
778
783
|
const LITERAL_KINDS = new Set([
|
|
779
784
|
SyntaxKind.StringLiteral,
|
|
780
785
|
SyntaxKind.NoSubstitutionTemplateLiteral,
|
|
@@ -818,7 +823,7 @@ const propertyKey = (nameNode) => {
|
|
|
818
823
|
* there is nothing to match — the host here is any import of the generated css module, which
|
|
819
824
|
* a file defining a recipe necessarily has, since `cva` came from it.
|
|
820
825
|
*/
|
|
821
|
-
const ensureRecipeHelperImport = (call, isBambooCssModule, isGeneratedCssModule, isShadowed) => {
|
|
826
|
+
const ensureRecipeHelperImport = (imported, call, isBambooCssModule, isGeneratedCssModule, isShadowed) => {
|
|
822
827
|
const sourceFile = call.getSourceFile();
|
|
823
828
|
let host;
|
|
824
829
|
for (const declaration of sourceFile.getImportDeclarations()) {
|
|
@@ -826,7 +831,7 @@ const ensureRecipeHelperImport = (call, isBambooCssModule, isGeneratedCssModule,
|
|
|
826
831
|
if (declaration.isTypeOnly()) continue;
|
|
827
832
|
for (const named of declaration.getNamedImports()) {
|
|
828
833
|
if (named.isTypeOnly()) continue;
|
|
829
|
-
if (named.getNameNode().getText() ===
|
|
834
|
+
if (named.getNameNode().getText() === imported) {
|
|
830
835
|
if (!isBambooCssModule(mod)) return void 0;
|
|
831
836
|
const local = (named.getAliasNode() ?? named.getNameNode()).getText();
|
|
832
837
|
return isShadowed(call, local) ? void 0 : { name: local };
|
|
@@ -835,15 +840,15 @@ const ensureRecipeHelperImport = (call, isBambooCssModule, isGeneratedCssModule,
|
|
|
835
840
|
if (!host && isGeneratedCssModule(mod) && declaration.getNamedImports().length > 0) host = declaration;
|
|
836
841
|
}
|
|
837
842
|
if (!host) return void 0;
|
|
838
|
-
if (declaredAtModuleScope(sourceFile).has(
|
|
839
|
-
if (isShadowed(call,
|
|
843
|
+
if (declaredAtModuleScope(sourceFile).has(imported)) return void 0;
|
|
844
|
+
if (isShadowed(call, imported)) return void 0;
|
|
840
845
|
const last = host.getNamedImports().at(-1);
|
|
841
846
|
if (!last) return void 0;
|
|
842
847
|
return {
|
|
843
|
-
name:
|
|
848
|
+
name: imported,
|
|
844
849
|
insert: {
|
|
845
850
|
pos: last.getEnd(),
|
|
846
|
-
names: [
|
|
851
|
+
names: [imported]
|
|
847
852
|
}
|
|
848
853
|
};
|
|
849
854
|
};
|
|
@@ -889,11 +894,28 @@ const lowerRecipeCall = (call, entry, ctx, isInert, resolvedSelection) => {
|
|
|
889
894
|
const effectful = [];
|
|
890
895
|
if (args.length === 1) {
|
|
891
896
|
const arg = args[0];
|
|
892
|
-
if (!arg
|
|
897
|
+
if (!arg) return {
|
|
893
898
|
kind: "decline",
|
|
894
899
|
reason: "dynamic"
|
|
895
900
|
};
|
|
896
|
-
|
|
901
|
+
/**
|
|
902
|
+
* `input(variantProps)` — a selection the build cannot see inside.
|
|
903
|
+
*
|
|
904
|
+
* The classes are still knowable: a recipe emits one per declared variant, so the call is
|
|
905
|
+
* one term per variant reading that binding. This is the shape a wrapper component takes,
|
|
906
|
+
* where the variants are the component's public API and cannot be literals by definition.
|
|
907
|
+
*
|
|
908
|
+
* An identifier only. Each variant reads the binding again, and re-reading anything else —
|
|
909
|
+
* a call, a property access — would evaluate it once per axis instead of once.
|
|
910
|
+
*/
|
|
911
|
+
if (Node.isIdentifier(arg)) {
|
|
912
|
+
const binding = arg.getText();
|
|
913
|
+
for (const key of Object.keys(config.variants ?? {})) dynamicAxes.set(key, `${binding}${propertyAccess(key)}`);
|
|
914
|
+
} else if (!Node.isObjectLiteralExpression(arg)) return {
|
|
915
|
+
kind: "decline",
|
|
916
|
+
reason: "dynamic"
|
|
917
|
+
};
|
|
918
|
+
else for (const property of arg.getProperties()) {
|
|
897
919
|
if (Node.isSpreadAssignment(property)) return {
|
|
898
920
|
kind: "decline",
|
|
899
921
|
reason: "dynamic"
|
|
@@ -1388,7 +1410,7 @@ const hasStyles = (data) => data.length > 0 && data.every((entry) => entry != nu
|
|
|
1388
1410
|
const argumentsAccountedFor = (call, boxNode) => {
|
|
1389
1411
|
if (!Node.isCallExpression(call)) return false;
|
|
1390
1412
|
const args = call.getArguments();
|
|
1391
|
-
if (args.length === 0) return
|
|
1413
|
+
if (args.length === 0) return true;
|
|
1392
1414
|
if (box.isArray(boxNode) && boxNode.getNode() === call) {
|
|
1393
1415
|
if (boxNode.value.length !== args.length) return false;
|
|
1394
1416
|
return args.every((arg, index) => accountsForSource(arg, boxNode.value[index]));
|
|
@@ -1513,6 +1535,8 @@ const foldSource = (options) => {
|
|
|
1513
1535
|
* saving real, and it is only correct to claim it once nothing reads the binding.
|
|
1514
1536
|
*/
|
|
1515
1537
|
const recipeCalls = /* @__PURE__ */ new Map();
|
|
1538
|
+
/** Bindings whose `splitVariantProps` was rewritten, so that access no longer reads them. */
|
|
1539
|
+
const loweredSplitProps = /* @__PURE__ */ new Set();
|
|
1516
1540
|
/** Ranges already reported as declined, so one call is never counted twice. */
|
|
1517
1541
|
const reportedRanges = /* @__PURE__ */ new Set();
|
|
1518
1542
|
const importCache = /* @__PURE__ */ new Map();
|
|
@@ -1524,6 +1548,58 @@ const foldSource = (options) => {
|
|
|
1524
1548
|
}
|
|
1525
1549
|
return names;
|
|
1526
1550
|
};
|
|
1551
|
+
/**
|
|
1552
|
+
* Is this binding the variant half of `<recipe>.splitVariantProps(...)`?
|
|
1553
|
+
*
|
|
1554
|
+
* Read off the declaration rather than the type, so it is the same recipe and the same
|
|
1555
|
+
* destructuring position the source actually wrote.
|
|
1556
|
+
*/
|
|
1557
|
+
const isSplitVariantPropsOf = (binding, recipe) => {
|
|
1558
|
+
for (const declaration of binding.getSourceFile().getDescendantsOfKind(SyntaxKind.VariableDeclaration)) {
|
|
1559
|
+
const nameNode = declaration.getNameNode();
|
|
1560
|
+
if (!Node.isArrayBindingPattern(nameNode)) continue;
|
|
1561
|
+
const first = nameNode.getElements()[0];
|
|
1562
|
+
if (!first || !Node.isBindingElement(first)) continue;
|
|
1563
|
+
if (first.getNameNode().getText() !== binding.getText()) continue;
|
|
1564
|
+
const initializer = declaration.getInitializer();
|
|
1565
|
+
if (!initializer || !Node.isCallExpression(initializer)) return false;
|
|
1566
|
+
const callee = initializer.getExpression();
|
|
1567
|
+
if (!Node.isPropertyAccessExpression(callee)) return false;
|
|
1568
|
+
return callee.getName() === "splitVariantProps" && callee.getExpression().getText() === recipe;
|
|
1569
|
+
}
|
|
1570
|
+
return false;
|
|
1571
|
+
};
|
|
1572
|
+
/**
|
|
1573
|
+
* Lower a config recipe call the same way an inline one lowers.
|
|
1574
|
+
*
|
|
1575
|
+
* The config lives in `ctx.recipes` rather than in the module, and the classes are named
|
|
1576
|
+
* from it identically — so this is the same `lowerRecipeCall`, handed the config from a
|
|
1577
|
+
* different place. Slot recipes are excluded by the caller: they resolve to one class per
|
|
1578
|
+
* slot rather than to a string.
|
|
1579
|
+
*/
|
|
1580
|
+
const lowerConfigRecipeCall = (call, name) => {
|
|
1581
|
+
const config = ctx.recipes.getConfig(name);
|
|
1582
|
+
if (!config || config.slots !== void 0) return void 0;
|
|
1583
|
+
const className = config.className;
|
|
1584
|
+
if (!className) return void 0;
|
|
1585
|
+
if (!Node.isCallExpression(call)) return void 0;
|
|
1586
|
+
const argument = call.getArguments()[0];
|
|
1587
|
+
if (!argument || !Node.isIdentifier(argument) || !isSplitVariantPropsOf(argument, name)) return void 0;
|
|
1588
|
+
const lowered = lowerRecipeCall(call, {
|
|
1589
|
+
config,
|
|
1590
|
+
name: className,
|
|
1591
|
+
box: void 0
|
|
1592
|
+
}, ctx, isInertExpression);
|
|
1593
|
+
if (lowered.kind !== "expression") return void 0;
|
|
1594
|
+
const helper = ensureRecipeHelperImport(RECIPE_PICK_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed);
|
|
1595
|
+
if (!helper) return void 0;
|
|
1596
|
+
return {
|
|
1597
|
+
replacement: helper.name === "cvaPick" ? lowered.expression : lowered.expression.replaceAll(`${RECIPE_PICK_HELPER}(`, `${helper.name}(`),
|
|
1598
|
+
className: lowered.staticClasses,
|
|
1599
|
+
classNames: lowered.classNames,
|
|
1600
|
+
insert: helper.insert
|
|
1601
|
+
};
|
|
1602
|
+
};
|
|
1527
1603
|
for (const item of parserResult.toArray()) {
|
|
1528
1604
|
const type = item.type ?? "";
|
|
1529
1605
|
const name = item.name ?? type;
|
|
@@ -1654,7 +1730,7 @@ const foldSource = (options) => {
|
|
|
1654
1730
|
const entry = recipeConfigs.get(name);
|
|
1655
1731
|
const lowered = lowerRecipeCall(call, entry, ctx, isInertExpression, resolvedSelection);
|
|
1656
1732
|
if (lowered.kind === "expression") {
|
|
1657
|
-
const helper = ensureRecipeHelperImport(call, isBambooCssModule, isGeneratedCssModule, isShadowed);
|
|
1733
|
+
const helper = ensureRecipeHelperImport(RECIPE_PICK_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed);
|
|
1658
1734
|
if (helper) {
|
|
1659
1735
|
tally.lowered++;
|
|
1660
1736
|
candidates.push({
|
|
@@ -1765,7 +1841,7 @@ const foldSource = (options) => {
|
|
|
1765
1841
|
});
|
|
1766
1842
|
continue;
|
|
1767
1843
|
}
|
|
1768
|
-
if (!isStaticBox(item.box) || !hasStyles(item.data) || !argumentsAccountedFor(call, item.box)) {
|
|
1844
|
+
if (!(Node.isCallExpression(call) && call.getArguments().length === 0 && item.data.length === 1) && !isStaticBox(item.box) || !hasStyles(item.data) || !argumentsAccountedFor(call, item.box)) {
|
|
1769
1845
|
const partial = partial_ ? tryPartial(item, call, rootName) : void 0;
|
|
1770
1846
|
if (partial) {
|
|
1771
1847
|
candidates.push({
|
|
@@ -1778,6 +1854,18 @@ const foldSource = (options) => {
|
|
|
1778
1854
|
});
|
|
1779
1855
|
continue;
|
|
1780
1856
|
}
|
|
1857
|
+
const lowered = type === "recipe" && !slot ? lowerConfigRecipeCall(call, name) : void 0;
|
|
1858
|
+
if (lowered) {
|
|
1859
|
+
candidates.push({
|
|
1860
|
+
item,
|
|
1861
|
+
call,
|
|
1862
|
+
node: call,
|
|
1863
|
+
start,
|
|
1864
|
+
end,
|
|
1865
|
+
...lowered
|
|
1866
|
+
});
|
|
1867
|
+
continue;
|
|
1868
|
+
}
|
|
1781
1869
|
skipped.push({
|
|
1782
1870
|
name,
|
|
1783
1871
|
reason: "dynamic",
|
|
@@ -1905,6 +1993,36 @@ const foldSource = (options) => {
|
|
|
1905
1993
|
});
|
|
1906
1994
|
collectSourceFiles(item.box, dependencyScan);
|
|
1907
1995
|
}
|
|
1996
|
+
const recipeSourceFile = [...recipeConfigs?.values() ?? []].find((entry) => entry.box)?.box?.getNode?.()?.getSourceFile() ?? candidates[0]?.node.getSourceFile();
|
|
1997
|
+
if (recipeSourceFile) for (const access of recipeSourceFile.getDescendantsOfKind(SyntaxKind.PropertyAccessExpression)) {
|
|
1998
|
+
if (access.getName() !== "splitVariantProps") continue;
|
|
1999
|
+
const target = access.getExpression();
|
|
2000
|
+
if (!Node.isIdentifier(target)) continue;
|
|
2001
|
+
if (isShadowed(access, target.getText())) continue;
|
|
2002
|
+
const local = recipeConfigs?.get(target.getText());
|
|
2003
|
+
const importedConfig = !local && importsFor(recipeSourceFile).has(target.getText()) ? ctx.recipes.getConfig(target.getText()) : void 0;
|
|
2004
|
+
const entry = local && local !== AMBIGUOUS ? local : importedConfig ? {
|
|
2005
|
+
config: importedConfig,
|
|
2006
|
+
name: "",
|
|
2007
|
+
box: void 0
|
|
2008
|
+
} : void 0;
|
|
2009
|
+
if (!entry) continue;
|
|
2010
|
+
const call = access.getParent();
|
|
2011
|
+
if (!Node.isCallExpression(call) || call.getExpression() !== access) continue;
|
|
2012
|
+
const args = call.getArguments();
|
|
2013
|
+
if (args.length !== 1) continue;
|
|
2014
|
+
const start = call.getStart();
|
|
2015
|
+
const end = call.getEnd();
|
|
2016
|
+
if (code.slice(start, end) !== call.getText()) continue;
|
|
2017
|
+
if (collides([[start, end]])) continue;
|
|
2018
|
+
const helper = ensureRecipeHelperImport(SPLIT_PROPS_HELPER, call, isBambooCssModule, isGeneratedCssModule, isShadowed);
|
|
2019
|
+
if (!helper) continue;
|
|
2020
|
+
const keys = Object.keys(entry.config.variants ?? {});
|
|
2021
|
+
magic.overwrite(start, end, `${helper.name}(${args[0].getText()}, ${JSON.stringify(keys)})`);
|
|
2022
|
+
applyInsert(helper.insert);
|
|
2023
|
+
applied.push([start, end]);
|
|
2024
|
+
loweredSplitProps.add(target.getText());
|
|
2025
|
+
}
|
|
1908
2026
|
for (const [binding, tally] of recipeCalls) {
|
|
1909
2027
|
if (!tally.seen || tally.lowered !== tally.seen) continue;
|
|
1910
2028
|
const definition = recipeConfigs?.get(binding)?.box?.getNode?.();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bamboocss/vite",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.26.0",
|
|
4
4
|
"description": "Vite integration for Bamboo CSS",
|
|
5
5
|
"homepage": "https://bamboocss.com",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,18 +37,18 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"magic-string": "0.30.21",
|
|
39
39
|
"ts-morph": "28.0.0",
|
|
40
|
-
"@bamboocss/config": "1.
|
|
41
|
-
"@bamboocss/
|
|
42
|
-
"@bamboocss/
|
|
43
|
-
"@bamboocss/
|
|
44
|
-
"@bamboocss/
|
|
45
|
-
"@bamboocss/shared": "1.
|
|
46
|
-
"@bamboocss/types": "1.
|
|
40
|
+
"@bamboocss/config": "1.26.0",
|
|
41
|
+
"@bamboocss/extractor": "1.26.0",
|
|
42
|
+
"@bamboocss/core": "1.26.0",
|
|
43
|
+
"@bamboocss/node": "1.26.0",
|
|
44
|
+
"@bamboocss/logger": "1.26.0",
|
|
45
|
+
"@bamboocss/shared": "1.26.0",
|
|
46
|
+
"@bamboocss/types": "1.26.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@jridgewell/trace-mapping": "^0.3.31",
|
|
50
50
|
"vite": "7.2.6",
|
|
51
|
-
"@bamboocss/fixture": "1.
|
|
51
|
+
"@bamboocss/fixture": "1.26.0"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
54
|
"vite": ">=5"
|