next-yak 0.0.22 → 0.0.23

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.
@@ -40,7 +40,7 @@ const headline = css\`
40
40
  `
41
41
  )
42
42
  ).toMatchInlineSnapshot(`
43
- "._yak_0 {
43
+ ".yak_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
- "._yak_0 {
80
+ ".yak_0 {
81
81
  font-size: 2rem;
82
82
  font-weight: bold;
83
83
  color: red;
84
- &:where(._yak_1) {
84
+ &:where(.yak_1) {
85
85
  color: orange;
86
86
  }
87
87
 
88
- &:where(._yak_2) {
88
+ &:where(.yak_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
- "._yak_0 {
116
+ ".yak_0 {
117
117
  /* comment */
118
- &:where(._yak_1) {
118
+ &:where(.yak_1) {
119
119
  color: blue;
120
120
  }
121
121
  }"
@@ -138,12 +138,12 @@ const headline = css\`
138
138
  `
139
139
  )
140
140
  ).toMatchInlineSnapshot(`
141
- "._yak_0 {
142
- &:hover {
143
- color: var(--🦬18fi82j0);
144
- }
145
- }"
146
- `);
141
+ ".yak_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
- "._yak_0 {
215
- transition: color var(--🦬18fi82j0) var(--🦬18fi82j1);
216
- display: block;
217
- &:where(._yak_1) {
218
- color: orange
219
- }
220
- }"
221
- `);
214
+ ".yak_0 {
215
+ transition: color var(--🦬18fi82j0) var(--🦬18fi82j1);
216
+ display: block;
217
+ &:where(.yak_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
- "._yak_0 {
245
- color: blue;
246
- @media (min-width: 640px) {
247
- color: red;
248
- }
249
- transition: color var(--🦬18fi82j0) var(--🦬18fi82j1);
250
- display: block;
251
- &:where(._yak_1) {
252
- color: orange
244
+ ".yak_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(.yak_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
- "._yak_0 {
279
- color: blue;
280
- @media (min-width: 640px) {
281
- color: red;
282
- }
283
- transition: color var(--🦬18fi82j0) var(--🦬18fi82j1);
284
- display: block;
285
- &:where(._yak_1) {
286
- color: orange
278
+ ".yak_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(.yak_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
- "._yak_0 {
315
- :before {
316
- content: \\"\\\\2022\\";
317
- }
318
- :after {
319
- content: \\"\\\\2022\\";
320
- }
321
- content: \\"\\\\\\\\\\"
322
- }"
323
- `);
314
+ ".yak_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(._yak_0) {
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(._yak_1) {
491
+ &:where(.active_1) {
492
492
  background-color: blue;
493
- &:where(._yak_2) {
493
+ &:where(.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(._yak_0) {
538
+ &:where(.active_0) {
539
539
  background-color: blue;
540
540
  }
541
- &:where(._yak_1) {
541
+ &:where(.not_active_1) {
542
542
  background-color: var(--🦬18fi82j0);
543
543
  }
544
544
  border: 1px solid black;
545
- &:where(._yak_2) {
545
+ &:where(.active_2) {
546
546
  color: orange;
547
547
  }
548
- &:where(._yak_3) {
548
+ &:where(.not_active_3) {
549
549
  transition: color var(--🦬18fi82j1) var(--🦬18fi82j2);
550
550
  }
551
551
  }"
@@ -0,0 +1,83 @@
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 extractConditionsWithBabel(code: string) {
8
+ let result: string = "";
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 = getCssName(path);
17
+ }
18
+ },
19
+ });
20
+ return result;
21
+ }
22
+
23
+ describe("getCssName", () => {
24
+ it("should guess the css name from the condition of a logical expression", () => {
25
+ const code = `({$active}) => $active && css\`\``;
26
+ const cssName = extractConditionsWithBabel(code);
27
+ expect(cssName).toBe("active");
28
+ });
29
+
30
+ it("should guess the css name from the condition of a ternary expression", () => {
31
+ const code = `({$active}) => $active ? css\`\` : null`;
32
+ const cssName = extractConditionsWithBabel(code);
33
+ expect(cssName).toBe("active");
34
+ });
35
+
36
+ it("should guess the css name from the condition of a logical expression with negation", () => {
37
+ const code = `({$active}) => !$active && css\`\``;
38
+ const cssName = extractConditionsWithBabel(code);
39
+ expect(cssName).toBe("not_active");
40
+ });
41
+
42
+ it("should guess the css name from the condition of a ternary expression for the else case", () => {
43
+ const code = `({$active}) => $active ? null : css\`\``;
44
+ const cssName = extractConditionsWithBabel(code);
45
+ expect(cssName).toBe("not_active");
46
+ });
47
+
48
+ it("should guess the css name from the condition of a logical expression with multiple conditions", () => {
49
+ const code = `({$active, $visible}) => $active && $visible && css\`\``;
50
+ const cssName = extractConditionsWithBabel(code);
51
+ expect(cssName).toBe("active_and_visible");
52
+ });
53
+
54
+ it("should guess the css name from the condition of a ternary expression with multiple conditions", () => {
55
+ const code = `({$active, $visible}) => $active ? ($visible ? css\`\` : null) : null`;
56
+ const cssName = extractConditionsWithBabel(code);
57
+ expect(cssName).toBe("active_and_visible");
58
+ });
59
+
60
+ it("should guess the css name from the condition of a logical expression with negation and multiple conditions", () => {
61
+ const code = `({$active, $visible}) => !$active && $visible && css\`\``;
62
+ const cssName = extractConditionsWithBabel(code);
63
+ expect(cssName).toBe("not_active_and_visible");
64
+ });
65
+
66
+ it("should guess the css name from the condition of a ternary expression with negation and multiple conditions", () => {
67
+ const code = `({$active, $visible}) => !$active ? ($visible ? css\`\`: null) : null`;
68
+ const cssName = extractConditionsWithBabel(code);
69
+ expect(cssName).toBe("not_active_and_visible");
70
+ });
71
+
72
+ it("should guess the css name from the condition of a logical expression with negation and multiple conditions for the else case", () => {
73
+ const code = `({$active, $visible}) => $active && !$visible && css\`\``;
74
+ const cssName = extractConditionsWithBabel(code);
75
+ expect(cssName).toBe("active_and_not_visible");
76
+ });
77
+
78
+ it("should guess the css name from the condition of a ternary expression with negation and multiple conditions for the else case", () => {
79
+ const code = `({$active, $visible}) => $active ? ($visible ? null : css\`\`) : null`;
80
+ const cssName = extractConditionsWithBabel(code);
81
+ expect(cssName).toBe("active_and_not_visible");
82
+ });
83
+ });
@@ -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._yak_0);
63
+ const headline = css(__styleYak.yak_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._yak_0, x > 0.5 && css(__styleYak._yak_1));"
94
+ const headline = css(__styleYak.yak_0, x > 0.5 && css(__styleYak.yak_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._yak_0));
128
+ const Button = styled.button(__styleYak.Button, x > 0.5 && css(__styleYak.yak_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._yak_0));
165
+ }) => theme.mode === \\"dark\\" && css(__styleYak.yak_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._yak_0, css(__styleYak._yak_1), css(__styleYak._yak_2), {
243
+ const headline = css(__styleYak.yak_0, css(__styleYak.yak_1), css(__styleYak.yak_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
- "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._yak_0, x > 0.5 && css(__styleYak._yak_1));"
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.yak_0, x > 0.5 && css(__styleYak.yak_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
- "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._yak_0));"
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
 
@@ -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,131 @@
1
+ // @ts-check
2
+
3
+ /** @typedef {import("@babel/types")} babel */
4
+
5
+ /**
6
+ * Try to get the name of a css literal
7
+ *
8
+ * e.g. ({$disabled}) => $disabled && css`...` -> "is_$disabled"
9
+ *
10
+ * @param {babel.NodePath<babel.types.TaggedTemplateExpression>} path
11
+ * @returns {string}
12
+ */
13
+ function getCssName(path) {
14
+ const conditions = extractConditions(path);
15
+ if (conditions.length === 0) {
16
+ return "yak";
17
+ }
18
+ return conditions
19
+ .map((condition) => {
20
+ if (condition === "&&") {
21
+ return "and";
22
+ }
23
+ if (condition === "||") {
24
+ return "or";
25
+ }
26
+ return condition;
27
+ })
28
+ .join("_")
29
+ .replace(/\$/g, "")
30
+ .replace(/!/g, "not_");
31
+ }
32
+
33
+ /**
34
+ * Extracts the conditions from a given path.
35
+ *
36
+ * @param {babel.NodePath} path - The path to extract conditions from.
37
+ */
38
+ function extractConditions(path) {
39
+ const conditions = [];
40
+ let currentPath = path.parentPath;
41
+ let child = path.node;
42
+ while (
43
+ currentPath &&
44
+ (currentPath.isLogicalExpression() || currentPath.isConditionalExpression())
45
+ ) {
46
+ if (currentPath.isConditionalExpression() && conditions.length > 0) {
47
+ conditions.push(operatorToWord("&&", false));
48
+ }
49
+ let left = currentPath.isLogicalExpression()
50
+ ? currentPath.node.left
51
+ : currentPath.node.test;
52
+ const negated = currentPath.isConditionalExpression() && currentPath.node.alternate === child;
53
+ const leftName = extractIdentifier(left, negated);
54
+ if (leftName) {
55
+ conditions.push(leftName);
56
+ }
57
+ while (left && left.type === "LogicalExpression") {
58
+ if (left.type === "LogicalExpression" && left.right !== path.node) {
59
+ const rightName = extractIdentifier(left.right, negated);
60
+ if (rightName) {
61
+ conditions.push(rightName);
62
+ conditions.push(operatorToWord(left.operator, negated));
63
+ }
64
+ }
65
+ left = left.left;
66
+ const leftName = extractIdentifier(left, negated);
67
+ if (leftName) {
68
+ conditions.push(leftName);
69
+ }
70
+ }
71
+ child = currentPath.node;
72
+ currentPath = currentPath.parentPath;
73
+ }
74
+ return conditions.reverse();
75
+ }
76
+
77
+ /**
78
+ * Extracts the identifier from a given node.
79
+ * @param {babel.types.Node} node - The node to extract the identifier from.
80
+ * @param {boolean} negated - Whether the node is negated.
81
+ */
82
+ function extractIdentifier(node, negated) {
83
+ if (node.type === "Identifier") {
84
+ return prepend(node.name, "!", negated);
85
+ }
86
+ if (node.type === "MemberExpression" && node.object.type === "Identifier" && node.property.type === "Identifier") {
87
+ return prepend(node.object.name + node.property.name[0].toUpperCase() + node.property.name.slice(1), "!", negated);
88
+ }
89
+ if (node.type === "UnaryExpression" && node.argument.type === "Identifier") {
90
+ return prepend(node.argument.name, "!", !negated);
91
+ }
92
+ if (
93
+ node.type === "UnaryExpression" &&
94
+ node.argument.type === "UnaryExpression" &&
95
+ node.argument.argument.type === "Identifier"
96
+ ) {
97
+ return prepend(node.argument.argument.name, "!", negated);
98
+ }
99
+ return null;
100
+ }
101
+
102
+
103
+ /**
104
+ * Negates the operator if negated is true.
105
+ *
106
+ * @param {string} operator
107
+ * @param {boolean} negated
108
+
109
+ */
110
+ function operatorToWord(operator, negated) {
111
+ switch (operator) {
112
+ case "&&":
113
+ return negated ? "or" : "and";
114
+ case "||":
115
+ return negated ? "and" : "or";
116
+ default:
117
+ return operator;
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Prepends a prefix to a string if active is true.
123
+ * @param {string} str - The string to prepend the prefix to.
124
+ * @param {string} prefix - The prefix to prepend.
125
+ * @param {boolean} active - Whether to prepend the prefix.
126
+ */
127
+ function prepend(str, prefix, active) {
128
+ return active ? prefix + str : str;
129
+ }
130
+
131
+ module.exports = getCssName;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-yak",
3
- "version": "0.0.22",
3
+ "version": "0.0.23",
4
4
  "type": "module",
5
5
  "types": "./dist/",
6
6
  "exports": {