Skip to content

Commit 15475e8

Browse files
authored
fix: remove lightningcss as a peerDep. Use the version provided by expo (#183)
1 parent d11bf3f commit 15475e8

File tree

7 files changed

+174
-50
lines changed

7 files changed

+174
-50
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,6 @@
193193
},
194194
"peerDependencies": {
195195
"@expo/metro-config": ">=0.21.8",
196-
"lightningcss": ">=1.27.0",
197196
"react": "19.1.0",
198197
"react-native": "0.81.1"
199198
},

src/__tests__/compiler/@prop.test.tsx

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,45 @@ test("@prop target (nested @media)", () => {
2525
{
2626
d: [["#00f", ["test"]]],
2727
v: [["__rn-css-color", "#00f"]],
28-
s: [2, 1],
28+
s: [3, 1],
29+
},
30+
],
31+
],
32+
],
33+
});
34+
35+
render(<View testID={testID} className="my-class" />);
36+
const component = screen.getByTestId(testID);
37+
38+
expect(component.props).toStrictEqual({
39+
testID,
40+
children: undefined,
41+
test: "#00f",
42+
style: {},
43+
});
44+
});
45+
46+
test("@prop target (nested @media and nested declarations)", () => {
47+
const compiled = registerCSS(`
48+
.my-class {
49+
@prop test;
50+
@media all {
51+
& {
52+
color: #00f;
53+
}
54+
}
55+
}
56+
`);
57+
58+
expect(compiled.stylesheet()).toStrictEqual({
59+
s: [
60+
[
61+
"my-class",
62+
[
63+
{
64+
d: [["#00f", ["test"]]],
65+
v: [["__rn-css-color", "#00f"]],
66+
s: [3, 1],
2967
},
3068
],
3169
],

src/__tests__/compiler/compiler.test.tsx

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,11 @@ test("hello world", () => {
2828

2929
test("reads global CSS variables", () => {
3030
const compiled = compile(
31-
`
32-
@layer theme {
33-
:root, :host {
34-
--color-red-500: oklch(63.7% 0.237 25.331);
35-
}
36-
}`,
31+
`@layer theme {
32+
:root, :host {
33+
--color-red-500: oklch(63.7% 0.237 25.331);
34+
}
35+
}`,
3736
{
3837
inlineVariables: false,
3938
},
@@ -44,6 +43,40 @@ test("reads global CSS variables", () => {
4443
});
4544
});
4645

46+
test(":root CSS variables with media queries", () => {
47+
const compiled = compile(
48+
`:root {
49+
@media ios {
50+
& {
51+
--my-var: System;
52+
}
53+
}
54+
55+
@media android {
56+
& {
57+
--my-var: SystemAndroid;
58+
}
59+
}
60+
}
61+
`,
62+
{
63+
inlineVariables: false,
64+
},
65+
);
66+
67+
expect(compiled.stylesheet()).toStrictEqual({
68+
vr: [
69+
[
70+
"my-var",
71+
[
72+
["SystemAndroid", [["=", "platform", "android"]]],
73+
["System", [["=", "platform", "ios"]]],
74+
],
75+
],
76+
],
77+
});
78+
});
79+
4780
test.skip("removes unused CSS variables", () => {
4881
const compiled = compile(`
4982
.test {
@@ -326,7 +359,7 @@ test("media query nested in rules", () => {
326359
},
327360
],
328361
m: [[">=", "width", 600]],
329-
s: [2, 1],
362+
s: [3, 1],
330363
v: [["__rn-css-color", "#00f"]],
331364
},
332365
{
@@ -335,12 +368,12 @@ test("media query nested in rules", () => {
335368
[">=", "width", 600],
336369
[">=", "width", 400],
337370
],
338-
s: [3, 1],
371+
s: [5, 1],
339372
},
340373
{
341374
d: [{ backgroundColor: "#ff0" }],
342375
m: [[">=", "width", 100]],
343-
s: [4, 1],
376+
s: [7, 1],
344377
},
345378
],
346379
],

src/compiler/compiler.ts

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ import { inspect } from "node:util";
33

44
import { debug } from "debug";
55
import {
6-
Features,
7-
transform as lightningcss,
86
type ContainerRule,
97
type MediaQuery as CSSMediaQuery,
108
type CustomAtRules,
@@ -17,12 +15,14 @@ import { maybeMutateReactNativeOptions, parsePropAtRule } from "./atRules";
1715
import type {
1816
CompilerOptions,
1917
ContainerQuery,
18+
StyleRuleMapping,
2019
UniqueVarInfo,
2120
} from "./compiler.types";
2221
import { parseContainerCondition } from "./container-query";
2322
import { parseDeclaration, round } from "./declarations";
2423
import { inlineVariables } from "./inline-variables";
2524
import { extractKeyFrames } from "./keyframes";
25+
import { lightningcssLoader } from "./lightningcss-loader";
2626
import { parseMediaQuery } from "./media-query";
2727
import { StylesheetBuilder } from "./stylesheet";
2828
import { supportsConditionValid } from "./supports";
@@ -58,6 +58,8 @@ export function compile(code: Buffer | string, options: CompilerOptions = {}) {
5858

5959
const builder = new StylesheetBuilder(options);
6060

61+
const { lightningcss, Features } = lightningcssLoader();
62+
6163
logger(`Lightningcss first pass`);
6264

6365
/**
@@ -182,7 +184,11 @@ export function compile(code: Buffer | string, options: CompilerOptions = {}) {
182184
/**
183185
* Extracts style declarations and animations from a given CSS rule, based on its type.
184186
*/
185-
function extractRule(rule: Rule, builder: StylesheetBuilder) {
187+
function extractRule(
188+
rule: Rule,
189+
builder: StylesheetBuilder,
190+
mapping: StyleRuleMapping = {},
191+
) {
186192
// Check the rule's type to determine which extraction function to call
187193
switch (rule.type) {
188194
case "keyframes": {
@@ -192,12 +198,12 @@ function extractRule(rule: Rule, builder: StylesheetBuilder) {
192198
}
193199
case "container": {
194200
// If the rule is a container, extract it with the `extractedContainer` function
195-
extractContainer(rule.value, builder);
201+
extractContainer(rule.value, builder, mapping);
196202
break;
197203
}
198204
case "media": {
199205
// If the rule is a media query, extract it with the `extractMedia` function
200-
extractMedia(rule.value, builder);
206+
extractMedia(rule.value, builder, mapping);
201207
break;
202208
}
203209
case "nested-declarations": {
@@ -227,21 +233,21 @@ function extractRule(rule: Rule, builder: StylesheetBuilder) {
227233
const value = rule.value;
228234

229235
const declarationBlock = value.declarations;
230-
const mapping = parsePropAtRule(value.rules);
236+
mapping = { ...mapping, ...parsePropAtRule(value.rules) };
231237

232238
// If the rule is a style declaration, extract it with the `getExtractedStyle` function and store it in the `declarations` map
233239
builder = builder.fork("style", value.selectors);
234240

235241
if (declarationBlock) {
236-
if (declarationBlock.declarations) {
242+
if (declarationBlock.declarations?.length) {
237243
builder.newRule(mapping);
238244
for (const declaration of declarationBlock.declarations) {
239245
parseDeclaration(declaration, builder);
240246
}
241247
builder.applyRuleToSelectors();
242248
}
243249

244-
if (declarationBlock.importantDeclarations) {
250+
if (declarationBlock.importantDeclarations?.length) {
245251
builder.newRule(mapping, { important: true });
246252
for (const declaration of declarationBlock.importantDeclarations) {
247253
parseDeclaration(declaration, builder);
@@ -252,21 +258,21 @@ function extractRule(rule: Rule, builder: StylesheetBuilder) {
252258

253259
if (value.rules) {
254260
for (const nestedRule of value.rules) {
255-
extractRule(nestedRule, builder);
261+
extractRule(nestedRule, builder, mapping);
256262
}
257263
}
258264

259265
break;
260266
}
261267
case "layer-block":
262268
for (const layerRule of rule.value.rules) {
263-
extractRule(layerRule, builder);
269+
extractRule(layerRule, builder, mapping);
264270
}
265271
break;
266272
case "supports":
267273
if (supportsConditionValid(rule.value.condition)) {
268274
for (const layerRule of rule.value.rules) {
269-
extractRule(layerRule, builder);
275+
extractRule(layerRule, builder, mapping);
270276
}
271277
}
272278
break;
@@ -303,7 +309,11 @@ function extractRule(rule: Rule, builder: StylesheetBuilder) {
303309
*
304310
* @returns undefined if no screen media queries are found in the mediaRule, else it returns the extracted styles.
305311
*/
306-
function extractMedia(mediaRule: MediaRule, builder: StylesheetBuilder) {
312+
function extractMedia(
313+
mediaRule: MediaRule,
314+
builder: StylesheetBuilder,
315+
mapping: StyleRuleMapping,
316+
) {
307317
builder = builder.fork("media");
308318

309319
// Initialize an empty array to store screen media queries
@@ -336,7 +346,7 @@ function extractMedia(mediaRule: MediaRule, builder: StylesheetBuilder) {
336346

337347
// Iterate over all rules in the mediaRule and extract their styles using the updated CompilerCollection
338348
for (const rule of mediaRule.rules) {
339-
extractRule(rule, builder);
349+
extractRule(rule, builder, mapping);
340350
}
341351
}
342352

@@ -348,6 +358,7 @@ function extractMedia(mediaRule: MediaRule, builder: StylesheetBuilder) {
348358
function extractContainer(
349359
containerRule: ContainerRule,
350360
builder: StylesheetBuilder,
361+
mapping: StyleRuleMapping,
351362
) {
352363
builder = builder.fork("container");
353364

@@ -363,6 +374,6 @@ function extractContainer(
363374
builder.addContainerQuery(query);
364375

365376
for (const rule of containerRule.rules) {
366-
extractRule(rule, builder);
377+
extractRule(rule, builder, mapping);
367378
}
368379
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
export function lightningcssLoader() {
2+
let lightningcssPath: string | undefined;
3+
4+
// Try to resolve the path to lightningcss from the @expo/metro-config package
5+
// lightningcss is a direct dependency of @expo/metro-config
6+
try {
7+
lightningcssPath = require.resolve("lightningcss", {
8+
paths: [
9+
require
10+
.resolve("@expo/metro-config/package.json")
11+
.replace("/package.json", ""),
12+
],
13+
});
14+
} catch {
15+
// Intentionally left empty
16+
}
17+
18+
// If @expo/metro-config is not being used (non-metro bundler?), try and resolve it directly
19+
try {
20+
lightningcssPath ??= require.resolve("lightningcss");
21+
} catch {
22+
// Intentionally left empty
23+
}
24+
25+
if (!lightningcssPath) {
26+
throw new Error(
27+
"react-native-css was unable to determine the path to lightningcss",
28+
);
29+
}
30+
31+
// eslint-disable-next-line @typescript-eslint/no-require-imports
32+
const { transform: lightningcss, Features } = require(
33+
lightningcssPath,
34+
) as typeof import("lightningcss");
35+
36+
return {
37+
lightningcss,
38+
Features,
39+
};
40+
}

src/compiler/selector-builder.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -557,9 +557,13 @@ function getMediaQuery(
557557
return mediaQuery;
558558
}
559559

560-
function isRootVariableSelector([first, second]: Selector) {
560+
function isRootVariableSelector([first, ...rest]: Selector) {
561+
rest = rest.filter((item) => item.type !== "nesting");
561562
return (
562-
first && !second && first.type === "pseudo-class" && first.kind === "root"
563+
first &&
564+
rest.length === 0 &&
565+
first.type === "pseudo-class" &&
566+
first.kind === "root"
563567
);
564568
}
565569

0 commit comments

Comments
 (0)