next-yak 0.0.22 → 0.0.24
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/README.md +27 -0
- package/loaders/__tests__/cssloader.test.ts +59 -59
- package/loaders/__tests__/getCssName.test.ts +170 -0
- package/loaders/__tests__/tsloader.test.ts +19 -19
- package/loaders/babel-yak-plugin.cjs +4 -1
- package/loaders/cssloader.cjs +3 -1
- package/loaders/lib/getCssName.cjs +151 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -172,6 +172,33 @@ module.exports = {
|
|
|
172
172
|
|
|
173
173
|
[Nesting Example (video)](https://github.com/jantimon/next-yak/assets/4113649/33eeeb13-b0cf-499f-a1d3-ba6f51cf4308)
|
|
174
174
|
|
|
175
|
+
## Build Time Constants
|
|
176
|
+
|
|
177
|
+
The downside of dynamic properties is that they require inline style attributes.
|
|
178
|
+
While this is not a problem for most cases, we can't use them for media queries.
|
|
179
|
+
|
|
180
|
+
`next-yak` allows you to define build time constants which can be used in your styles:
|
|
181
|
+
|
|
182
|
+
```jsx
|
|
183
|
+
import { styled } from 'next-yak';
|
|
184
|
+
import { breakpoints, spacings } from './constants.yak';
|
|
185
|
+
|
|
186
|
+
const Container = styled.div`
|
|
187
|
+
padding: ${spacings.md};
|
|
188
|
+
${breakpoints.md} {
|
|
189
|
+
padding: ${spacings.lg};
|
|
190
|
+
}
|
|
191
|
+
`;
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
| Feature | Code | Yak Constant files |
|
|
195
|
+
|---------------- |----------------------------------------------- |---------------------------------------------- |
|
|
196
|
+
| File Extension | `.js`, `.jsx`, `.tsx`, etc. | `.yak.js`, `.yak.jsx`, `.yak.tsx`, etc. |
|
|
197
|
+
| Runs at | Compile time (Bundler) | Runtime (Node or Browser) |
|
|
198
|
+
| Side effects | Allowed | Forbidden |
|
|
199
|
+
| Yak Features | All (`styled`, `css`, ...) | - |
|
|
200
|
+
|
|
201
|
+
|
|
175
202
|
## Motivation
|
|
176
203
|
|
|
177
204
|
Most of the existing CSS-in-JS libraries are either slow or have a complex api. This project tries to find a middle ground between speed and api complexity.
|
|
@@ -40,7 +40,7 @@ const headline = css\`
|
|
|
40
40
|
`
|
|
41
41
|
)
|
|
42
42
|
).toMatchInlineSnapshot(`
|
|
43
|
-
".
|
|
43
|
+
".headline_0 {
|
|
44
44
|
font-size: 2rem;
|
|
45
45
|
font-weight: bold;
|
|
46
46
|
color: red;
|
|
@@ -77,15 +77,15 @@ const headline = css\`
|
|
|
77
77
|
`
|
|
78
78
|
)
|
|
79
79
|
).toMatchInlineSnapshot(`
|
|
80
|
-
".
|
|
80
|
+
".headline_0 {
|
|
81
81
|
font-size: 2rem;
|
|
82
82
|
font-weight: bold;
|
|
83
83
|
color: red;
|
|
84
|
-
&:where(.
|
|
84
|
+
&:where(.headline_1) {
|
|
85
85
|
color: orange;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
&:where(.
|
|
88
|
+
&:where(.headline_2) {
|
|
89
89
|
color: teal;
|
|
90
90
|
}
|
|
91
91
|
&:hover {
|
|
@@ -113,9 +113,9 @@ const headline = css\`
|
|
|
113
113
|
`
|
|
114
114
|
)
|
|
115
115
|
).toMatchInlineSnapshot(`
|
|
116
|
-
".
|
|
116
|
+
".headline_0 {
|
|
117
117
|
/* comment */
|
|
118
|
-
&:where(.
|
|
118
|
+
&:where(.headline_1) {
|
|
119
119
|
color: blue;
|
|
120
120
|
}
|
|
121
121
|
}"
|
|
@@ -138,12 +138,12 @@ const headline = css\`
|
|
|
138
138
|
`
|
|
139
139
|
)
|
|
140
140
|
).toMatchInlineSnapshot(`
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
141
|
+
".headline_0 {
|
|
142
|
+
&:hover {
|
|
143
|
+
color: var(--🦬18fi82j0);
|
|
144
|
+
}
|
|
145
|
+
}"
|
|
146
|
+
`);
|
|
147
147
|
});
|
|
148
148
|
|
|
149
149
|
it("should support attrs on intrinsic elements", async () => {
|
|
@@ -211,14 +211,14 @@ const headline = css\`
|
|
|
211
211
|
`
|
|
212
212
|
)
|
|
213
213
|
).toMatchInlineSnapshot(`
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
214
|
+
".headline_0 {
|
|
215
|
+
transition: color var(--🦬18fi82j0) var(--🦬18fi82j1);
|
|
216
|
+
display: block;
|
|
217
|
+
&:where(.headline_1) {
|
|
218
|
+
color: orange
|
|
219
|
+
}
|
|
220
|
+
}"
|
|
221
|
+
`);
|
|
222
222
|
});
|
|
223
223
|
|
|
224
224
|
it("should replace breakpoint references with actual media queries", async () => {
|
|
@@ -241,18 +241,18 @@ const headline = css\`
|
|
|
241
241
|
`
|
|
242
242
|
)
|
|
243
243
|
).toMatchInlineSnapshot(`
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
transition: color var(--🦬18fi82j0) var(--🦬18fi82j1);
|
|
250
|
-
display: block;
|
|
251
|
-
&:where(._yak_1) {
|
|
252
|
-
color: orange
|
|
244
|
+
".headline_0 {
|
|
245
|
+
color: blue;
|
|
246
|
+
@media (min-width: 640px) {
|
|
247
|
+
color: red;
|
|
253
248
|
}
|
|
254
|
-
|
|
255
|
-
|
|
249
|
+
transition: color var(--🦬18fi82j0) var(--🦬18fi82j1);
|
|
250
|
+
display: block;
|
|
251
|
+
&:where(.headline_1) {
|
|
252
|
+
color: orange
|
|
253
|
+
}
|
|
254
|
+
}"
|
|
255
|
+
`);
|
|
256
256
|
});
|
|
257
257
|
|
|
258
258
|
it("should replace breakpoint references with actual media queries from single quote imports", async () => {
|
|
@@ -275,18 +275,18 @@ const headline = css\`
|
|
|
275
275
|
`
|
|
276
276
|
)
|
|
277
277
|
).toMatchInlineSnapshot(`
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
transition: color var(--🦬18fi82j0) var(--🦬18fi82j1);
|
|
284
|
-
display: block;
|
|
285
|
-
&:where(._yak_1) {
|
|
286
|
-
color: orange
|
|
278
|
+
".headline_0 {
|
|
279
|
+
color: blue;
|
|
280
|
+
@media (min-width: 640px) {
|
|
281
|
+
color: red;
|
|
287
282
|
}
|
|
288
|
-
|
|
289
|
-
|
|
283
|
+
transition: color var(--🦬18fi82j0) var(--🦬18fi82j1);
|
|
284
|
+
display: block;
|
|
285
|
+
&:where(.headline_1) {
|
|
286
|
+
color: orange
|
|
287
|
+
}
|
|
288
|
+
}"
|
|
289
|
+
`);
|
|
290
290
|
});
|
|
291
291
|
|
|
292
292
|
it("should prevent double escaped chars", async () => {
|
|
@@ -311,16 +311,16 @@ const headline = css\`
|
|
|
311
311
|
`
|
|
312
312
|
)
|
|
313
313
|
).toMatchInlineSnapshot(`
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
314
|
+
".headline_0 {
|
|
315
|
+
:before {
|
|
316
|
+
content: \\"\\\\2022\\";
|
|
317
|
+
}
|
|
318
|
+
:after {
|
|
319
|
+
content: \\"\\\\2022\\";
|
|
320
|
+
}
|
|
321
|
+
content: \\"\\\\\\\\\\"
|
|
322
|
+
}"
|
|
323
|
+
`);
|
|
324
324
|
});
|
|
325
325
|
|
|
326
326
|
it("should convert keyframes", async () => {
|
|
@@ -481,16 +481,16 @@ const Component2 = styled.div\`
|
|
|
481
481
|
".Component {
|
|
482
482
|
background-color: red;
|
|
483
483
|
color: white;
|
|
484
|
-
&:where(.
|
|
484
|
+
&:where(.active_0) {
|
|
485
485
|
background-color: blue;
|
|
486
486
|
}
|
|
487
487
|
border: 1px solid black;
|
|
488
488
|
|
|
489
489
|
&:focus {
|
|
490
490
|
background-color: green;
|
|
491
|
-
&:where(.
|
|
491
|
+
&:where(.active_1) {
|
|
492
492
|
background-color: blue;
|
|
493
|
-
&:where(.
|
|
493
|
+
&:where(.active_and_active_2) {
|
|
494
494
|
background-color: brown;
|
|
495
495
|
}
|
|
496
496
|
}
|
|
@@ -535,17 +535,17 @@ const Component = styled.div\`
|
|
|
535
535
|
".Component {
|
|
536
536
|
background-color: red;
|
|
537
537
|
color: white;
|
|
538
|
-
&:where(.
|
|
538
|
+
&:where(.active_0) {
|
|
539
539
|
background-color: blue;
|
|
540
540
|
}
|
|
541
|
-
&:where(.
|
|
541
|
+
&:where(.not_active_1) {
|
|
542
542
|
background-color: var(--🦬18fi82j0);
|
|
543
543
|
}
|
|
544
544
|
border: 1px solid black;
|
|
545
|
-
&:where(.
|
|
545
|
+
&:where(.active_2) {
|
|
546
546
|
color: orange;
|
|
547
547
|
}
|
|
548
|
-
&:where(.
|
|
548
|
+
&:where(.not_active_3) {
|
|
549
549
|
transition: color var(--🦬18fi82j1) var(--🦬18fi82j2);
|
|
550
550
|
}
|
|
551
551
|
}"
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { parse, traverse } from "@babel/core";
|
|
2
|
+
import { NodePath } from "@babel/core";
|
|
3
|
+
import getCssName from "../lib/getCssName.cjs";
|
|
4
|
+
import type { TaggedTemplateExpression } from "@babel/types";
|
|
5
|
+
import { describe, it, expect } from "vitest";
|
|
6
|
+
|
|
7
|
+
function getCssLiteral(code: string) {
|
|
8
|
+
let result: NodePath<TaggedTemplateExpression> | null = null;
|
|
9
|
+
const ast = parse(code);
|
|
10
|
+
if (!ast) {
|
|
11
|
+
throw new Error("Could not parse code");
|
|
12
|
+
}
|
|
13
|
+
traverse(ast, {
|
|
14
|
+
TaggedTemplateExpression(path: NodePath<TaggedTemplateExpression>) {
|
|
15
|
+
if (path.node.tag.type === "Identifier" && path.node.tag.name === "css") {
|
|
16
|
+
result = path;
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
if (!result) {
|
|
21
|
+
throw new Error("Could not find css literal");
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe("getCssName", () => {
|
|
27
|
+
it("should guess the css name from the condition of a logical expression", () => {
|
|
28
|
+
const literal = getCssLiteral(`({$active}) => $active && css\`\``);
|
|
29
|
+
const cssName = getCssName(literal);
|
|
30
|
+
expect(cssName).toBe("active");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should guess the css name from the condition of a ternary expression", () => {
|
|
34
|
+
const literal = getCssLiteral(`({$active}) => $active ? css\`\` : null`);
|
|
35
|
+
const cssName = getCssName(literal);
|
|
36
|
+
expect(cssName).toBe("active");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should guess the css name from the condition of a logical expression with negation", () => {
|
|
40
|
+
const literal = getCssLiteral(`({$active}) => !$active && css\`\``);
|
|
41
|
+
const cssName = getCssName(literal);
|
|
42
|
+
expect(cssName).toBe("not_active");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should guess the css name from the condition of a ternary expression for the else case", () => {
|
|
46
|
+
const literal = getCssLiteral(`({$active}) => $active ? null : css\`\``);
|
|
47
|
+
const cssName = getCssName(literal);
|
|
48
|
+
expect(cssName).toBe("not_active");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should guess the css name from the condition of a logical expression with multiple conditions", () => {
|
|
52
|
+
const literal = getCssLiteral(
|
|
53
|
+
`({$active, $visible}) => $active && $visible && css\`\``
|
|
54
|
+
);
|
|
55
|
+
const cssName = getCssName(literal);
|
|
56
|
+
expect(cssName).toBe("active_and_visible");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should guess the css name from the condition of a ternary expression with multiple conditions", () => {
|
|
60
|
+
const literal = getCssLiteral(
|
|
61
|
+
`({$active, $visible}) => $active ? ($visible ? css\`\` : null) : null`
|
|
62
|
+
);
|
|
63
|
+
const cssName = getCssName(literal);
|
|
64
|
+
expect(cssName).toBe("active_and_visible");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("should guess the css name from the condition of a logical expression with negation and multiple conditions", () => {
|
|
68
|
+
const literal = getCssLiteral(
|
|
69
|
+
`({$active, $visible}) => !$active && $visible && css\`\``
|
|
70
|
+
);
|
|
71
|
+
const cssName = getCssName(literal);
|
|
72
|
+
expect(cssName).toBe("not_active_and_visible");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("should guess the css name from the condition of a ternary expression with negation and multiple conditions", () => {
|
|
76
|
+
const literal = getCssLiteral(
|
|
77
|
+
`({$active, $visible}) => !$active ? ($visible ? css\`\`: null) : null`
|
|
78
|
+
);
|
|
79
|
+
const cssName = getCssName(literal);
|
|
80
|
+
expect(cssName).toBe("not_active_and_visible");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("should guess the css name from the condition of a logical expression with negation and multiple conditions for the else case", () => {
|
|
84
|
+
const literal = getCssLiteral(
|
|
85
|
+
`({$active, $visible}) => $active && !$visible && css\`\``
|
|
86
|
+
);
|
|
87
|
+
const cssName = getCssName(literal);
|
|
88
|
+
expect(cssName).toBe("active_and_not_visible");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("should guess the css name from the condition of a ternary expression with negation and multiple conditions for the else case", () => {
|
|
92
|
+
const literal = getCssLiteral(
|
|
93
|
+
`({$active, $visible}) => $active ? ($visible ? null : css\`\`) : null`
|
|
94
|
+
);
|
|
95
|
+
const cssName = getCssName(literal);
|
|
96
|
+
expect(cssName).toBe("active_and_not_visible");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("should guess the css name from the condition of a nested logical expression", () => {
|
|
100
|
+
const literal = getCssLiteral(
|
|
101
|
+
`({ $visible }) => $visible && (({ $active }) => $active && css\`\`)`
|
|
102
|
+
);
|
|
103
|
+
const cssName = getCssName(literal);
|
|
104
|
+
expect(cssName).toBe("visible_and_active");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("should guess the css name from a complex expression using AND and OR operators", () => {
|
|
108
|
+
const literal = getCssLiteral(
|
|
109
|
+
`({$active, $visible, $enabled}) => ($active || $visible) && $enabled && css\`\``
|
|
110
|
+
);
|
|
111
|
+
const cssName = getCssName(literal);
|
|
112
|
+
expect(cssName).toBe("active_or_visible_and_enabled");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("should guess the css name from nested logical expressions", () => {
|
|
116
|
+
const literal = getCssLiteral(
|
|
117
|
+
`({$active, $visible, $enabled}) => ($active && ($visible || $enabled)) && css\`\``
|
|
118
|
+
);
|
|
119
|
+
const cssName = getCssName(literal);
|
|
120
|
+
expect(cssName).toBe("active_and_visible_or_enabled");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("should guess the css name from an expression with multiple OR conditions", () => {
|
|
124
|
+
const literal = getCssLiteral(
|
|
125
|
+
`({$active, $visible, $enabled}) => $active || $visible || $enabled && css\`\``
|
|
126
|
+
);
|
|
127
|
+
const cssName = getCssName(literal);
|
|
128
|
+
expect(cssName).toBe("active_or_visible_or_enabled");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("should guess the css name from a complex combination of AND and OR operators", () => {
|
|
132
|
+
const literal = getCssLiteral(
|
|
133
|
+
`({$active, $visible, $enabled}) => ($active && $visible) || ($visible && $enabled) && css\`\``
|
|
134
|
+
);
|
|
135
|
+
const cssName = getCssName(literal);
|
|
136
|
+
expect(cssName).toBe("active_and_visible_or_visible_and_enabled");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("should guess the css name from a condition using Boolean() function", () => {
|
|
140
|
+
const literal = getCssLiteral(`({$active}) => Boolean($active) && css\`\``);
|
|
141
|
+
const cssName = getCssName(literal);
|
|
142
|
+
expect(cssName).toBe("active");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("should guess the css name from a condition using double negation", () => {
|
|
146
|
+
const literal = getCssLiteral(`({$active}) => !!$active && css\`\``);
|
|
147
|
+
const cssName = getCssName(literal);
|
|
148
|
+
expect(cssName).toBe("active");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("should guess the css name from the variable name", () => {
|
|
152
|
+
const literal = getCssLiteral(`const Mixin = css\`\``);
|
|
153
|
+
const cssName = getCssName(literal);
|
|
154
|
+
expect(cssName).toBe("Mixin");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("should guess the css name from a single member expression and camelCase it", () => {
|
|
158
|
+
const literal = getCssLiteral(`({theme}) => theme.highContrast && css\`\``);
|
|
159
|
+
const cssName = getCssName(literal);
|
|
160
|
+
expect(cssName).toBe("themeHighContrast");
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("should guess the css name from a nested member expression and camelCase it", () => {
|
|
164
|
+
const literal = getCssLiteral(
|
|
165
|
+
`({$user}) => $user.auth.loggedIn && css\`\``
|
|
166
|
+
);
|
|
167
|
+
const cssName = getCssName(literal);
|
|
168
|
+
expect(cssName).toBe("userAuthLoggedIn");
|
|
169
|
+
});
|
|
170
|
+
});
|
|
@@ -60,7 +60,7 @@ export const Main = () => <h1 className={headline({}).className}>Hello World</h1
|
|
|
60
60
|
import { css } from \\"next-yak\\";
|
|
61
61
|
import __styleYak from \\"./page.yak.module.css!=!./page?./page.yak.module.css\\";
|
|
62
62
|
type x = number;
|
|
63
|
-
const headline = css(__styleYak.
|
|
63
|
+
const headline = css(__styleYak.headline_0);
|
|
64
64
|
export const Main = () => <h1 className={headline({}).className}>Hello World</h1>;"
|
|
65
65
|
`);
|
|
66
66
|
});
|
|
@@ -91,7 +91,7 @@ const headline = css\`
|
|
|
91
91
|
import { css } from \\"next-yak\\";
|
|
92
92
|
import __styleYak from \\"./page.yak.module.css!=!./page?./page.yak.module.css\\";
|
|
93
93
|
const x = Math.random();
|
|
94
|
-
const headline = css(__styleYak.
|
|
94
|
+
const headline = css(__styleYak.headline_0, x > 0.5 && css(__styleYak.headline_1));"
|
|
95
95
|
`);
|
|
96
96
|
});
|
|
97
97
|
|
|
@@ -125,7 +125,7 @@ const FancyButton = styled(Button)\`
|
|
|
125
125
|
import { styled, css } from \\"next-yak\\";
|
|
126
126
|
import __styleYak from \\"./page.yak.module.css!=!./page?./page.yak.module.css\\";
|
|
127
127
|
const x = Math.random();
|
|
128
|
-
const Button = styled.button(__styleYak.Button, x > 0.5 && css(__styleYak.
|
|
128
|
+
const Button = styled.button(__styleYak.Button, x > 0.5 && css(__styleYak.Button_0));
|
|
129
129
|
const FancyButton = styled(Button)(__styleYak.FancyButton);"
|
|
130
130
|
`);
|
|
131
131
|
});
|
|
@@ -162,7 +162,7 @@ const FancyButton = styled(Button)\`
|
|
|
162
162
|
const x = Math.random();
|
|
163
163
|
const Button = styled.button(__styleYak.Button, ({
|
|
164
164
|
theme
|
|
165
|
-
}) => theme.mode === \\"dark\\" && css(__styleYak.
|
|
165
|
+
}) => theme.mode === \\"dark\\" && css(__styleYak.Button_0));
|
|
166
166
|
const FancyButton = styled(Button)(__styleYak.FancyButton);"
|
|
167
167
|
`);
|
|
168
168
|
});
|
|
@@ -240,7 +240,7 @@ const headline = css\`
|
|
|
240
240
|
import { css } from \\"next-yak\\";
|
|
241
241
|
import __styleYak from \\"./page.yak.module.css!=!./page?./page.yak.module.css\\";
|
|
242
242
|
import { easing } from \\"styleguide\\";
|
|
243
|
-
const headline = css(__styleYak.
|
|
243
|
+
const headline = css(__styleYak.headline_0, css(__styleYak.headline_1), css(__styleYak.headline_2), {
|
|
244
244
|
\\"style\\": {
|
|
245
245
|
\\"--\\\\uD83E\\\\uDDAC18fi82j0\\": ({
|
|
246
246
|
i
|
|
@@ -477,12 +477,12 @@ const headline = css\`
|
|
|
477
477
|
`
|
|
478
478
|
)
|
|
479
479
|
).toMatchInlineSnapshot(`
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
`);
|
|
480
|
+
"import styles from \\"./page.module.css\\";
|
|
481
|
+
import { css } from \\"next-yak\\";
|
|
482
|
+
import __styleYak from \\"./page.yak.module.css!=!./page?./page.yak.module.css\\";
|
|
483
|
+
const x = Math.random();
|
|
484
|
+
const headline = css(__styleYak.headline_0, x > 0.5 && css(__styleYak.headline_1));"
|
|
485
|
+
`);
|
|
486
486
|
});
|
|
487
487
|
|
|
488
488
|
it("should show error when a dynamic selector is used after a comma", async () => {
|
|
@@ -552,13 +552,13 @@ const Button = styled.button\`
|
|
|
552
552
|
`
|
|
553
553
|
)
|
|
554
554
|
).toMatchInlineSnapshot(`
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
555
|
+
"import styles from \\"./page.module.css\\";
|
|
556
|
+
import { styled, css } from \\"next-yak\\";
|
|
557
|
+
import __styleYak from \\"./page.yak.module.css!=!./page?./page.yak.module.css\\";
|
|
558
|
+
const Icon = styled.svg(__styleYak.Icon);
|
|
559
|
+
const Button = styled.button(__styleYak.Button, ({
|
|
560
|
+
$primary
|
|
561
|
+
}) => $primary && css(__styleYak.primary_0));"
|
|
562
|
+
`);
|
|
563
563
|
});
|
|
564
564
|
});
|
|
@@ -5,6 +5,7 @@ const murmurhash2_32_gc = require("./lib/hash.cjs");
|
|
|
5
5
|
const { relative, resolve, basename } = require("path");
|
|
6
6
|
const localIdent = require("./lib/localIdent.cjs");
|
|
7
7
|
const getStyledComponentName = require("./lib/getStyledComponentName.cjs");
|
|
8
|
+
const getCssName = require("./lib/getCssName.cjs");
|
|
8
9
|
|
|
9
10
|
/** @typedef {{replaces: Record<string, unknown>, rootContext?: string}} YakBabelPluginOptions */
|
|
10
11
|
/** @typedef {{ css: string | undefined, styled: string | undefined, keyframes: string | undefined }} YakLocalIdentifierNames */
|
|
@@ -246,11 +247,13 @@ module.exports = function (babel, options) {
|
|
|
246
247
|
const variableName =
|
|
247
248
|
styledApi || expressionType === "keyframesLiteral"
|
|
248
249
|
? getStyledComponentName(path)
|
|
250
|
+
: expressionType === "cssLiteral" ?
|
|
251
|
+
getCssName(path)
|
|
249
252
|
: null;
|
|
250
253
|
|
|
251
254
|
const identifier = localIdent(
|
|
252
255
|
variableName || "_yak",
|
|
253
|
-
variableName ? null : this.classNameCount++,
|
|
256
|
+
variableName && expressionType !== "cssLiteral" ? null : this.classNameCount++,
|
|
254
257
|
expressionType === "keyframesLiteral" ? "animation" : "className"
|
|
255
258
|
);
|
|
256
259
|
|
package/loaders/cssloader.cjs
CHANGED
|
@@ -5,6 +5,7 @@ const quasiClassifier = require("./lib/quasiClassifier.cjs");
|
|
|
5
5
|
const localIdent = require("./lib/localIdent.cjs");
|
|
6
6
|
const replaceQuasiExpressionTokens = require("./lib/replaceQuasiExpressionTokens.cjs");
|
|
7
7
|
const getStyledComponentName = require("./lib/getStyledComponentName.cjs");
|
|
8
|
+
const getCssName = require("./lib/getCssName.cjs");
|
|
8
9
|
const murmurhash2_32_gc = require("./lib/hash.cjs");
|
|
9
10
|
const { relative } = require("path");
|
|
10
11
|
|
|
@@ -192,11 +193,12 @@ module.exports = async function cssLoader(source) {
|
|
|
192
193
|
const variableName =
|
|
193
194
|
isStyledLiteral || isStyledCall || isAttrsCall || isKeyFrameLiteral
|
|
194
195
|
? getStyledComponentName(path)
|
|
196
|
+
: isCssLiteral ? getCssName(path)
|
|
195
197
|
: null
|
|
196
198
|
|
|
197
199
|
const literalSelector = localIdent(
|
|
198
200
|
variableName || "_yak",
|
|
199
|
-
variableName ? null : index++,
|
|
201
|
+
variableName && !isCssLiteral ? null : index++,
|
|
200
202
|
isKeyFrameLiteral ? "keyframes" : "selector"
|
|
201
203
|
);
|
|
202
204
|
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/** @typedef {import("@babel/types")} babel */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Extracts the conditions from a path
|
|
7
|
+
* @param {babel.NodePath<babel.types.TaggedTemplateExpression>} path
|
|
8
|
+
*/
|
|
9
|
+
function extractConditions(path) {
|
|
10
|
+
/** @type {string[]} */
|
|
11
|
+
const conditions = [];
|
|
12
|
+
const visitedNodes = new Set();
|
|
13
|
+
/**
|
|
14
|
+
* @param {babel.types.Node} node
|
|
15
|
+
* @param {babel.types.Node} previousNode
|
|
16
|
+
* @param {boolean} isNegated
|
|
17
|
+
*/
|
|
18
|
+
const getConditions = (node, previousNode, isNegated = false) => {
|
|
19
|
+
if (visitedNodes.has(node)) return;
|
|
20
|
+
visitedNodes.add(node);
|
|
21
|
+
// Support for && and || operators e.g. disabled && "disabled"
|
|
22
|
+
if (node.type === "LogicalExpression") {
|
|
23
|
+
if (node.operator === "&&") {
|
|
24
|
+
getConditions(node.right, previousNode, isNegated);
|
|
25
|
+
conditions.push("and");
|
|
26
|
+
getConditions(node.left, previousNode, isNegated);
|
|
27
|
+
} else if (node.operator === "||") {
|
|
28
|
+
getConditions(node.right, previousNode, isNegated);
|
|
29
|
+
conditions.push("or");
|
|
30
|
+
getConditions(node.left, previousNode, isNegated);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Support for ternary operator e.g. disabled ? "disabled" : "enabled"
|
|
34
|
+
else if (node.type === "ConditionalExpression") {
|
|
35
|
+
conditions.push("and");
|
|
36
|
+
getConditions(node.test, previousNode, node.alternate === previousNode);
|
|
37
|
+
}
|
|
38
|
+
// Support for ! operator e.g. !disabled
|
|
39
|
+
else if (node.type === "UnaryExpression" && node.operator === "!") {
|
|
40
|
+
getConditions(node.argument, previousNode, !isNegated);
|
|
41
|
+
}
|
|
42
|
+
// Support for Boolean() function e.g. Boolean(disabled)
|
|
43
|
+
else if (
|
|
44
|
+
node.type === "CallExpression" &&
|
|
45
|
+
node.callee.type === "Identifier" &&
|
|
46
|
+
node.callee.name === "Boolean"
|
|
47
|
+
) {
|
|
48
|
+
getConditions(node.arguments[0], previousNode, isNegated);
|
|
49
|
+
}
|
|
50
|
+
// Get the name of the variable e.g. disabled
|
|
51
|
+
else if (node.type === "Identifier") {
|
|
52
|
+
conditions.push((isNegated ? "not_" : "") + node.name);
|
|
53
|
+
}
|
|
54
|
+
// Get the name of a member expression e.g. props.disabled
|
|
55
|
+
else if (node.type === "MemberExpression") {
|
|
56
|
+
conditions.push(
|
|
57
|
+
(isNegated ? "not_" : "") + getMemberExpressionName(node)
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
/** @type {babel.NodePath | null} */
|
|
62
|
+
let currentPath = path;
|
|
63
|
+
/** @type {babel.NodePath} */
|
|
64
|
+
let previousPath = path;
|
|
65
|
+
while (currentPath) {
|
|
66
|
+
getConditions(currentPath.node, previousPath.node);
|
|
67
|
+
previousPath = currentPath;
|
|
68
|
+
currentPath = currentPath.parentPath;
|
|
69
|
+
}
|
|
70
|
+
if (conditions[0] === "or" || conditions[0] === "and") {
|
|
71
|
+
conditions.shift();
|
|
72
|
+
}
|
|
73
|
+
return conditions.reverse();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Try to get the name of a css component from a literal expression
|
|
78
|
+
*
|
|
79
|
+
* e.g. const mixin = css`...` -> "mixin"
|
|
80
|
+
*
|
|
81
|
+
* @param {babel.NodePath<babel.types.TaggedTemplateExpression>} taggedTemplateExpressionPath
|
|
82
|
+
* @returns {string | null}
|
|
83
|
+
*/
|
|
84
|
+
const getStyledComponentName = (taggedTemplateExpressionPath) => {
|
|
85
|
+
const variableDeclaratorPath = taggedTemplateExpressionPath.findParent(
|
|
86
|
+
(path) => path.isVariableDeclarator()
|
|
87
|
+
);
|
|
88
|
+
if (
|
|
89
|
+
!variableDeclaratorPath ||
|
|
90
|
+
!("id" in variableDeclaratorPath.node) ||
|
|
91
|
+
variableDeclaratorPath.node.id?.type !== "Identifier"
|
|
92
|
+
) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
return variableDeclaratorPath.node.id.name;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Try to get the name of a member expression
|
|
100
|
+
*
|
|
101
|
+
* e.g. props.disabled -> "propsDisabled"
|
|
102
|
+
* e.g. props.user.disabled -> "propsUserDisabled
|
|
103
|
+
*
|
|
104
|
+
* @param {babel.types.MemberExpression} node
|
|
105
|
+
* @returns {string}
|
|
106
|
+
*/
|
|
107
|
+
function getMemberExpressionName(node) {
|
|
108
|
+
console.log(node.object, node.property.type);
|
|
109
|
+
if (
|
|
110
|
+
!node.object ||
|
|
111
|
+
!node.property ||
|
|
112
|
+
(node.object.type !== "Identifier" &&
|
|
113
|
+
node.object.type !== "MemberExpression")
|
|
114
|
+
) {
|
|
115
|
+
return "";
|
|
116
|
+
}
|
|
117
|
+
const objectName =
|
|
118
|
+
node.object.type === "Identifier"
|
|
119
|
+
? node.object.name
|
|
120
|
+
: getMemberExpressionName(node.object);
|
|
121
|
+
const property = node.property;
|
|
122
|
+
let propertyName = "";
|
|
123
|
+
if (property.type === "Identifier") {
|
|
124
|
+
propertyName = property.name;
|
|
125
|
+
} else if (property.type === "StringLiteral") {
|
|
126
|
+
propertyName = property.value;
|
|
127
|
+
}
|
|
128
|
+
if (!propertyName) {
|
|
129
|
+
return "";
|
|
130
|
+
}
|
|
131
|
+
return objectName + propertyName[0].toUpperCase() + propertyName.slice(1);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Try to get the name of a css literal
|
|
136
|
+
*
|
|
137
|
+
* e.g. ({$disabled}) => $disabled && css`...` -> "is_$disabled"
|
|
138
|
+
*
|
|
139
|
+
* @param {babel.NodePath<babel.types.TaggedTemplateExpression>} literal
|
|
140
|
+
* @returns {string}
|
|
141
|
+
*/
|
|
142
|
+
function getCssName(literal) {
|
|
143
|
+
const conditions = extractConditions(literal);
|
|
144
|
+
if (conditions.length === 0) {
|
|
145
|
+
const mixinName = getStyledComponentName(literal);
|
|
146
|
+
return mixinName ? mixinName : "yak";
|
|
147
|
+
}
|
|
148
|
+
return conditions.join("_").replace(/\$/g, "");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
module.exports = getCssName;
|