Skip to content

Commit 5becd9d

Browse files
committed
feat: inline css variables
1 parent cef8ff6 commit 5becd9d

File tree

12 files changed

+242
-40
lines changed

12 files changed

+242
-40
lines changed

src/__tests__/compiler/compiler.test.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,11 +236,16 @@ test.skip("animations", () => {
236236
});
237237

238238
test("breaks apart comma separated variables", () => {
239-
const compiled = compile(`
239+
const compiled = compile(
240+
`
240241
:root {
241242
--test: blue, green;
242243
}
243-
`);
244+
`,
245+
{
246+
inlineVariables: false,
247+
},
248+
);
244249

245250
expect(compiled.stylesheet()).toStrictEqual({
246251
vr: [["test", [[["blue", "green"]]]]],

src/__tests__/native/text-shadow.test.ios.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,18 @@ import { registerCSS, testID } from "react-native-css/jest";
55
describe("text-shadow", () => {
66
test("<offsetX> <offsetY>", () => {
77
registerCSS(
8-
`.my-class { --my-var: 10px 10px; text-shadow: var(--my-var); }`,
8+
`.my-class {
9+
--my-var: 10px 10px;
10+
text-shadow: var(--my-var);
11+
}`,
912
);
1013

1114
render(<Text testID={testID} className="my-class" />);
1215

1316
expect(screen.getByTestId(testID).props.style).toStrictEqual({
14-
textShadowColor: "black",
17+
textShadowColor: {
18+
semantic: ["label", "labelColor"],
19+
},
1520
textShadowOffset: {
1621
height: 10,
1722
width: 10,
@@ -28,7 +33,7 @@ describe("text-shadow", () => {
2833
render(<Text testID={testID} className="my-class" />);
2934

3035
expect(screen.getByTestId(testID).props.style).toStrictEqual({
31-
textShadowColor: "red",
36+
textShadowColor: "#f00",
3237
textShadowOffset: {
3338
height: 10,
3439
width: 10,
@@ -45,7 +50,7 @@ describe("text-shadow", () => {
4550
render(<Text testID={testID} className="my-class" />);
4651

4752
expect(screen.getByTestId(testID).props.style).toStrictEqual({
48-
textShadowColor: "red",
53+
textShadowColor: "#f00",
4954
textShadowOffset: {
5055
height: 10,
5156
width: 10,

src/__tests__/native/transform.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ describe("scale", () => {
5656
).getByTestId(testID);
5757

5858
expect(component.props.style).toStrictEqual({
59-
transform: [{ scale: "2%" }],
59+
transform: [{ scaleX: "2%" }, { scaleY: "2%" }],
6060
});
6161
});
6262

src/__tests__/native/variables.test.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ test("inline variable", () => {
2020
});
2121
});
2222

23-
test("combined inline variable", () => {
23+
test("combined inline variables", () => {
2424
registerCSS(`
2525
.my-class-1 { width: var(--my-var); }
2626
.my-class-2 { --my-var: 10px; }
@@ -166,13 +166,13 @@ test("can apply and set new variables", () => {
166166
);
167167

168168
expect(screen.getByTestId(testIDs.one).props.style).toStrictEqual({
169-
color: "red",
169+
color: "#f00",
170170
});
171171
expect(screen.getByTestId(testIDs.two).props.style).toStrictEqual({
172-
color: "red",
172+
color: "#f00",
173173
});
174174
expect(screen.getByTestId(testIDs.three).props.style).toStrictEqual({
175-
color: "green",
175+
color: "#008000",
176176
});
177177
});
178178

@@ -199,7 +199,7 @@ test("variables will be inherited", () => {
199199
);
200200

201201
expect(screen.getByTestId(testIDs.three).props.style).toStrictEqual({
202-
color: "green",
202+
color: "#008000",
203203
});
204204
});
205205

@@ -212,7 +212,7 @@ test("useUnsafeVariable", () => {
212212
render(<View testID={testID} className="test" />);
213213
const component = screen.getByTestId(testID);
214214

215-
expect(component.props.style).toStrictEqual({ color: "red" });
215+
expect(component.props.style).toStrictEqual({ color: "#f00" });
216216
});
217217

218218
test("ratio values", () => {
@@ -224,7 +224,7 @@ test("ratio values", () => {
224224
render(<View testID={testID} className="test" />);
225225
const component = screen.getByTestId(testID);
226226

227-
expect(component.props.style).toStrictEqual({ aspectRatio: "16 / 9" });
227+
expect(component.props.style).toStrictEqual({ aspectRatio: "16/9" });
228228
});
229229

230230
test("VariableContextProvider", () => {

src/__tests__/vendor/tailwind/interactivity.test.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,12 @@ describe("Interactivity - Caret Color", () => {
6060
});
6161
test("caret-current", async () => {
6262
expect(await renderCurrentTest()).toStrictEqual({
63-
props: { style: {} },
63+
props: {
64+
cursorColor: {
65+
semantic: ["label", "labelColor"],
66+
},
67+
style: {},
68+
},
6469
});
6570
});
6671
test("caret-white", async () => {

src/__tests__/vendor/tailwind/typography.test.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,11 @@ describe("Typography - Text Color", () => {
313313
test("text-current", async () => {
314314
expect(await renderCurrentTest()).toStrictEqual({
315315
props: {
316-
style: {},
316+
style: {
317+
color: {
318+
semantic: ["label", "labelColor"],
319+
},
320+
},
317321
},
318322
});
319323
});

src/compiler/compiler.ts

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,14 @@ import {
1414
} from "lightningcss";
1515

1616
import { maybeMutateReactNativeOptions, parsePropAtRule } from "./atRules";
17-
import type { CompilerOptions, ContainerQuery } from "./compiler.types";
17+
import type {
18+
CompilerOptions,
19+
ContainerQuery,
20+
UniqueVarInfo,
21+
} from "./compiler.types";
1822
import { parseContainerCondition } from "./container-query";
1923
import { parseDeclaration, round } from "./declarations";
24+
import { inlineVariables } from "./inline-variables";
2025
import { extractKeyFrames } from "./keyframes";
2126
import { parseMediaQuery } from "./media-query";
2227
import { StylesheetBuilder } from "./stylesheet";
@@ -64,22 +69,59 @@ export function compile(code: Buffer | string, options: CompilerOptions = {}) {
6469
*
6570
* Due to the above issue, we run lightningcss twice
6671
*/
72+
73+
const vars = new Map<string, UniqueVarInfo>();
74+
75+
const firstPassVisitor: Visitor<CustomAtRules> = {};
76+
77+
if (options.inlineRem !== false) {
78+
firstPassVisitor.Length = (length) => {
79+
if (length.unit !== "rem" || options.inlineRem === false) {
80+
return length;
81+
}
82+
83+
return {
84+
unit: "px",
85+
value: round(length.value * (options.inlineRem ?? 14)),
86+
};
87+
};
88+
}
89+
90+
if (options.inlineVariables !== false) {
91+
const exclusionList: string[] = options.inlineVariables?.exclude ?? [];
92+
93+
firstPassVisitor.Declaration = (decl) => {
94+
if (
95+
decl.property === "custom" &&
96+
decl.value.name.startsWith("--") &&
97+
!exclusionList.includes(decl.value.name)
98+
) {
99+
const entry = vars.get(decl.value.name) ?? {
100+
count: 0,
101+
value: [
102+
...decl.value.value,
103+
{ type: "token", value: { type: "white-space", value: " " } },
104+
],
105+
};
106+
entry.count++;
107+
vars.set(decl.value.name, entry);
108+
}
109+
};
110+
firstPassVisitor.StyleSheetExit = (sheet) => {
111+
for (const [name, info] of vars) {
112+
if (info.count !== 1) {
113+
vars.delete(name);
114+
}
115+
}
116+
return inlineVariables(sheet, vars);
117+
};
118+
}
119+
67120
const { code: firstPass } = lightningcss({
68121
code: typeof code === "string" ? new TextEncoder().encode(code) : code,
69122
include: Features.DoublePositionGradients | Features.ColorFunction,
70123
exclude: Features.VendorPrefixes,
71-
visitor: {
72-
Length(length) {
73-
if (length.unit !== "rem" || options.inlineRem === false) {
74-
return length;
75-
}
76-
77-
return {
78-
unit: "px",
79-
value: round(length.value * (options.inlineRem ?? 14)),
80-
};
81-
},
82-
},
124+
visitor: firstPassVisitor,
83125
filename: options.filename ?? "style.css",
84126
projectRoot: options.projectRoot ?? process.cwd(),
85127
});

src/compiler/compiler.types.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
/* eslint-disable */
22
import type { Debugger } from "debug";
3-
import type { MediaFeatureNameFor_MediaFeatureId } from "lightningcss";
3+
import type {
4+
MediaFeatureNameFor_MediaFeatureId,
5+
TokenOrValue,
6+
} from "lightningcss";
47

58
import { VAR_SYMBOL } from "../runtime/native/reactivity";
69

710
export interface CompilerOptions {
811
filename?: string;
912
projectRoot?: string;
1013
inlineRem?: number | false;
14+
inlineVariables?: false | InlineVariableOptions;
1115
selectorPrefix?: string;
1216
stylesheetOrder?: number;
1317
features?: FeatureFlagRecord;
@@ -16,6 +20,10 @@ export interface CompilerOptions {
1620
colorPrecision?: number;
1721
}
1822

23+
export interface InlineVariableOptions {
24+
exclude?: `--${string}`[];
25+
}
26+
1927
/**
2028
* A `react-native-css` StyleSheet
2129
*/
@@ -151,6 +159,8 @@ export type InlineVariable = {
151159
[key: string]: unknown | undefined;
152160
};
153161

162+
export type UniqueVarInfo = { count: number; value: TokenOrValue[] };
163+
154164
/****************************** Animations ******************************/
155165

156166
export type Animation = [string, AnimationKeyframes[]];

src/compiler/declarations.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import type {
3535
Translate,
3636
UnresolvedColor,
3737
} from "lightningcss";
38+
import { isStyleFunction } from "react-native-css/runtime/utils";
3839

3940
import type {
4041
StyleDescriptor,
@@ -966,7 +967,13 @@ export function parseUnparsedDeclaration(
966967
}
967968

968969
if (property === "color") {
969-
builder.addDescriptor("--__rn-css-current-color", value);
970+
if (
971+
!isStyleFunction(value) ||
972+
value[1] !== "var" ||
973+
value[2] !== "-css-color"
974+
) {
975+
builder.addDescriptor("--__rn-css-color", value);
976+
}
970977
}
971978
}
972979
}
@@ -1125,7 +1132,7 @@ export function parseUnparsed(
11251132
} else if (tokenOrValue === "false") {
11261133
return false;
11271134
} else if (tokenOrValue === "currentcolor") {
1128-
return [{}, "var", "__rn-css-current-color"] as const;
1135+
return [{}, "var", "__rn-css-color"] as const;
11291136
} else {
11301137
return tokenOrValue;
11311138
}
@@ -1255,7 +1262,7 @@ export function parseUnparsed(
12551262
builder.addWarning("value", value);
12561263
return;
12571264
} else if (value === "currentcolor") {
1258-
return [{}, "var", "__rn-css-current-color"] as const;
1265+
return [{}, "var", "__rn-css-color"] as const;
12591266
}
12601267

12611268
if (value === "true") {
@@ -1579,10 +1586,15 @@ export function parseFontColorDeclaration(
15791586
) {
15801587
parseColorDeclaration(declaration, builder);
15811588

1582-
builder.addDescriptor(
1583-
"--__rn-css-color",
1584-
parseColor(declaration.value, builder),
1585-
);
1589+
if (
1590+
typeof declaration.value !== "object" ||
1591+
declaration.value.type !== "currentcolor"
1592+
) {
1593+
builder.addDescriptor(
1594+
"--__rn-css-color",
1595+
parseColor(declaration.value, builder),
1596+
);
1597+
}
15861598
}
15871599

15881600
export function parseColorDeclaration(
@@ -1609,7 +1621,7 @@ export function parseColor(cssColor: CssColor, builder: StylesheetBuilder) {
16091621

16101622
switch (cssColor.type) {
16111623
case "currentcolor":
1612-
return [{}, "var", "__rn-css-current-color"] as const;
1624+
return [{}, "var", "__rn-css-color"] as const;
16131625
case "light-dark": {
16141626
const extraRule: StyleRule = {
16151627
s: [],
@@ -1982,11 +1994,11 @@ export function parseTextShadow(
19821994
parseColor(textShadow.color, builder),
19831995
);
19841996
builder.addDescriptor(
1985-
"textShadowOffset.width",
1997+
"*.textShadowOffset.width",
19861998
parseLength(textShadow.xOffset, builder),
19871999
);
19882000
builder.addDescriptor(
1989-
"textShadowOffset.height",
2001+
"*.textShadowOffset.height",
19902002
parseLength(textShadow.yOffset, builder),
19912003
);
19922004
builder.addDescriptor(

0 commit comments

Comments
 (0)