Skip to content

Commit 7d7b5e9

Browse files
committed
feat: change '@react-native prop' to '@prop'
1 parent 645d1b4 commit 7d7b5e9

File tree

6 files changed

+338
-174
lines changed

6 files changed

+338
-174
lines changed

src/compiler/@prop.test.tsx

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import { compile } from "../compiler";
2+
3+
test("@prop single", () => {
4+
const compiled = compile(`
5+
.test {
6+
color: red;
7+
background-color: blue;
8+
@prop background-color: myBackgroundColor;
9+
}
10+
`);
11+
12+
expect(compiled).toStrictEqual({
13+
s: [
14+
[
15+
"test",
16+
[
17+
{
18+
d: [
19+
{
20+
color: "#f00",
21+
},
22+
["#00f", ["myBackgroundColor"]],
23+
],
24+
s: [1, 1],
25+
},
26+
],
27+
],
28+
],
29+
});
30+
});
31+
32+
test.only("@prop single, nested value", () => {
33+
const compiled = compile(`
34+
.test {
35+
color: red;
36+
background-color: blue;
37+
@prop background-color: myBackgroundColor.nested;
38+
}
39+
`);
40+
41+
expect(compiled).toStrictEqual({
42+
s: [
43+
[
44+
"test",
45+
[
46+
{
47+
d: [
48+
{
49+
color: "#f00",
50+
},
51+
["#00f", ["myBackgroundColor", "nested"]],
52+
],
53+
s: [1, 1],
54+
},
55+
],
56+
],
57+
],
58+
});
59+
});
60+
61+
test("@prop single, top level", () => {
62+
const compiled = compile(`
63+
.test {
64+
color: red;
65+
background-color: blue;
66+
@prop background-color: ^myBackgroundColor;
67+
}
68+
`);
69+
70+
expect(compiled).toStrictEqual({
71+
s: [
72+
[
73+
"test",
74+
[
75+
{
76+
d: [
77+
{
78+
color: "#f00",
79+
},
80+
["#00f", ["^", "myBackgroundColor"]],
81+
],
82+
s: [1, 1],
83+
},
84+
],
85+
],
86+
],
87+
});
88+
});
89+
90+
test("@prop single, top level, nested", () => {
91+
const compiled = compile(`
92+
.test {
93+
color: red;
94+
background-color: blue;
95+
@prop background-color: ^myBackgroundColor.test;
96+
}
97+
`);
98+
99+
expect(compiled).toStrictEqual({
100+
s: [
101+
[
102+
"test",
103+
[
104+
{
105+
d: [
106+
{
107+
color: "#f00",
108+
},
109+
["#00f", ["^", "myBackgroundColor", "test"]],
110+
],
111+
s: [1, 1],
112+
},
113+
],
114+
],
115+
],
116+
});
117+
});
118+
119+
test("@prop single, top level, nested", () => {
120+
const compiled = compile(`
121+
.test {
122+
color: red;
123+
background-color: blue;
124+
@prop background-color: ^myBackgroundColor.test;
125+
}
126+
`);
127+
128+
expect(compiled).toStrictEqual({
129+
s: [
130+
[
131+
"test",
132+
[
133+
{
134+
d: [
135+
{
136+
color: "#f00",
137+
},
138+
["#00f", ["^", "myBackgroundColor", "test"]],
139+
],
140+
s: [1, 1],
141+
},
142+
],
143+
],
144+
],
145+
});
146+
});
147+
148+
test("@prop multiple", () => {
149+
const compiled = compile(`
150+
.test {
151+
color: red;
152+
background-color: blue;
153+
@prop {
154+
background-color: myBackgroundColor;
155+
color: myColor;
156+
}
157+
}
158+
`);
159+
160+
expect(compiled).toStrictEqual({
161+
s: [
162+
[
163+
"test",
164+
[
165+
{
166+
d: [
167+
["#f00", ["myColor"]],
168+
["#00f", ["myBackgroundColor"]],
169+
],
170+
s: [1, 1],
171+
},
172+
],
173+
],
174+
],
175+
});
176+
});

src/compiler/__tests__/compiler.test.tsx

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -13,53 +13,6 @@ test("reads global CSS variables", () => {
1313
});
1414
});
1515

16-
test.skip("test compiler", () => {
17-
const compiled = compile(`
18-
.test {
19-
@react-native rename {
20-
backgroundColor: myBackgroundColor;
21-
borderWidth: my.borderWidth;
22-
fontSize: ^myFontSize;
23-
borderColor: ^top.level.nested;
24-
}
25-
color: red;
26-
background-color: red;
27-
border-width: 1px;
28-
font-size: 16px;
29-
--test: red;
30-
border-color: blue;
31-
border-color: var(--test)
32-
}
33-
`);
34-
35-
expect(compiled).toStrictEqual({
36-
s: [
37-
[
38-
"test",
39-
[
40-
[
41-
{
42-
s: [1, 1],
43-
d: [
44-
{
45-
color: "#ff0000",
46-
},
47-
["#ff0000", ["style", "myBackgroundColor"]],
48-
[1, ["style", "my", "borderWidth"]],
49-
[16, ["myFontSize"]],
50-
["#0000ff", ["top", "level", "nested"]],
51-
[[{}, "var", ["test"]], ["top", "level", "nested"], 1],
52-
],
53-
dv: 1,
54-
v: [["test", "red"]],
55-
},
56-
],
57-
],
58-
],
59-
],
60-
});
61-
});
62-
6316
test.skip("removes unused CSS variables", () => {
6417
const compiled = compile(`
6518
.test {

src/compiler/atRules.ts

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import type {
2+
DeclarationBlock,
3+
ParsedComponent,
4+
Rule,
5+
TokenOrValue,
6+
} from "lightningcss";
7+
import type { CompilerCollection, StyleRuleMapping } from "./compiler.types";
8+
import { splitByDelimiter } from "./split-by-delimiter";
9+
import { toRNProperty } from "./selectors";
10+
11+
export interface PropAtRule {
12+
type: "unknown";
13+
value: {
14+
name: "prop";
15+
prelude: Extract<TokenOrValue, { type: "token" }>[];
16+
block: Extract<TokenOrValue, { type: "token" }>[] | null;
17+
};
18+
}
19+
20+
/***********************************************
21+
* @react-native *
22+
***********************************************/
23+
24+
export interface ReactNativeAtRule {
25+
type: "custom";
26+
value: {
27+
name: "react-native";
28+
prelude: null | Extract<ParsedComponent, { type: "repeated" }>;
29+
body: {
30+
type: "declaration-list";
31+
value: Pick<DeclarationBlock, "declarations">;
32+
};
33+
};
34+
}
35+
36+
export function maybeMutateReactNativeOptions(
37+
rule: Rule | ReactNativeAtRule,
38+
collection: CompilerCollection,
39+
) {
40+
if (rule.type !== "custom" || rule.value?.name !== "react-native") {
41+
return;
42+
}
43+
44+
const { declarations } = rule.value.body.value;
45+
if (!declarations) return;
46+
47+
for (const declaration of declarations) {
48+
if (declaration.property !== "custom") continue;
49+
50+
switch (declaration.value.name) {
51+
case "preserve-variables": {
52+
declaration.value.value.forEach((token) => {
53+
if (token.type !== "dashed-ident") {
54+
return;
55+
}
56+
collection.varUsageCount.set(token.value, 1);
57+
});
58+
break;
59+
}
60+
default:
61+
break;
62+
}
63+
}
64+
}
65+
66+
/***********************************************
67+
* @prop *
68+
***********************************************/
69+
70+
function isPropAtRule(rule: Rule | PropAtRule): rule is PropAtRule {
71+
return rule.type === "unknown" && rule.value.name === "prop";
72+
}
73+
74+
export function parsePropAtRule(rules?: (Rule | PropAtRule)[]) {
75+
const mapping: StyleRuleMapping = {};
76+
77+
if (!rules) return mapping;
78+
79+
for (const rule of rules) {
80+
if (!isPropAtRule(rule)) continue;
81+
82+
if (rule.value.prelude.length > 0) {
83+
const prelude = rule.value.prelude.filter((item) => {
84+
return item.value.type !== "white-space";
85+
});
86+
87+
propAtRuleBlock(prelude, mapping);
88+
} else if (rule.value.block) {
89+
// Remove all whitespace tokens
90+
const blocks = rule.value.block.filter((item) => {
91+
return item.value.type !== "white-space";
92+
});
93+
94+
// Separate each rule, delimited by a semicolon
95+
const blockRules = splitByDelimiter(blocks, (item) => {
96+
return item.value.type === "semicolon";
97+
});
98+
99+
for (const block of blockRules) {
100+
propAtRuleBlock(block, mapping);
101+
}
102+
}
103+
}
104+
105+
return mapping;
106+
}
107+
108+
function propAtRuleBlock(
109+
token: Extract<TokenOrValue, { type: "token" }>[],
110+
mapping: StyleRuleMapping = {},
111+
): StyleRuleMapping {
112+
const [from, to] = splitByDelimiter(token, (item) => {
113+
return item.value.type === "colon";
114+
});
115+
116+
if (!from || from.length !== 1 || !to) {
117+
return mapping;
118+
}
119+
120+
const fromToken = from[0];
121+
if (!fromToken || fromToken.value.type !== "ident") {
122+
return mapping;
123+
}
124+
125+
mapping[toRNProperty(fromToken.value.value)] = to.flatMap((item, index) => {
126+
switch (item.value.type) {
127+
case "delim":
128+
return index === 0 && item.value.value === "^" ? ["^"] : [];
129+
case "ident":
130+
return [toRNProperty(item.value.value)];
131+
default:
132+
return [];
133+
}
134+
});
135+
136+
return mapping;
137+
}

0 commit comments

Comments
 (0)