next-yak 0.0.28 → 0.0.29

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.
@@ -289,6 +289,40 @@ const headline = css\`
289
289
  `);
290
290
  });
291
291
 
292
+ it("should replace breakpoint references with actual media queries when using square brackets", async () => {
293
+ expect(
294
+ await cssloader.call(
295
+ loaderContext,
296
+ `
297
+ import { css } from "next-yak";
298
+ import { queries } from '@/theme.yak';
299
+
300
+ const headline = css\`
301
+ color: blue;
302
+ \${queries["sm"]} {
303
+ color: red;
304
+ }
305
+ transition: color \${duration} \${easing};
306
+ display: block;
307
+ \${css\`color: orange\`}
308
+ \`;
309
+ `
310
+ )
311
+ ).toMatchInlineSnapshot(`
312
+ ".headline_0 {
313
+ color: blue;
314
+ @media (min-width: 640px) {
315
+ color: red;
316
+ }
317
+ transition: color var(--🦬18fi82j0) var(--🦬18fi82j1);
318
+ display: block;
319
+ &:where(.headline_1) {
320
+ color: orange
321
+ }
322
+ }"
323
+ `);
324
+ });
325
+
292
326
  it("should prevent double escaped chars", async () => {
293
327
  // in styled-components \\ is replaced with \
294
328
  // this test verifies that yak provides the same behavior
@@ -25,43 +25,22 @@
25
25
  * @param {import("@babel/types")} t
26
26
  */
27
27
  module.exports = function replaceTokensInQuasiExpressions(quasi, replacer, t) {
28
- for (let i = 0; i < quasi.expressions.length; i++) {
28
+ // Iterate over the expressions in reverse order
29
+ // so removing items won't affect the index of the next item
30
+ for (let i = quasi.expressions.length - 1; i >= 0; i--) {
29
31
  const expression = quasi.expressions[i];
30
- // replace direct identifiers e.g. ${query}
31
- if (t.isIdentifier(expression)) {
32
- const replacement = replacer(expression.name);
33
- if (replacement === false) {
34
- continue;
35
- }
36
- replaceExpressionAndMergeQuasis(quasi, i, replacement);
37
- i--;
38
- }
39
- // replace member expressions e.g. ${query.xs}
40
- // replace deeply nested member expressions e.g. ${query.xs.min}
41
- else if (
42
- t.isMemberExpression(expression) &&
43
- t.isIdentifier(expression.object)
44
- ) {
45
- /** @type {any} */
46
- let replacement = replacer(expression.object.name);
47
- if (replacement === false) {
48
- continue;
49
- }
50
- /** @type {import("@babel/types").Expression} */
51
- let object = expression;
52
- while (t.isMemberExpression(object)) {
53
- if (!t.isIdentifier(object.property)) {
54
- break;
55
- }
56
- if (typeof replacement !== "object" || replacement === null) {
57
- break;
58
- }
59
- replacement = replacement[object.property.name];
60
- object = object.object;
61
- }
62
- replaceExpressionAndMergeQuasis(quasi, i, replacement);
63
- i--;
64
- }
32
+ // find the value to replace the expression with
33
+ const replacement = getReplacement(expression, replacer, t);
34
+ // if it is a nested value, find the value of the expression
35
+ // e.g. x.y.z -> find the value of z
36
+ const replacementValue = replacement && getReplacementValueForExpression(
37
+ expression,
38
+ replacement,
39
+ t
40
+ );
41
+ if (replacementValue !== false) {
42
+ replaceExpressionAndMergeQuasis(quasi, i, replacementValue);
43
+ }
65
44
  }
66
45
  };
67
46
 
@@ -85,3 +64,98 @@ function replaceExpressionAndMergeQuasis(quasi, expressionIndex, replacement) {
85
64
  stringReplacement + quasi.quasis[expressionIndex + 1].value.cooked;
86
65
  quasi.quasis.splice(expressionIndex + 1, 1);
87
66
  }
67
+
68
+ /**
69
+ * Find the replacement for the expression
70
+ *
71
+ * searches for:
72
+ * - `x` -> x
73
+ * - `x.y` -> x
74
+ * - `x[0]` -> x
75
+ * - `x()` -> x
76
+ * - `x.y()` -> x
77
+ *
78
+ * @param {import("@babel/types").Expression | import("@babel/types").TSType} expression
79
+ * @param {(name: string) => unknown} replacer
80
+ * @param {import("@babel/types")} t
81
+ */
82
+ function getReplacement(expression, replacer, t) {
83
+ if (t.isIdentifier(expression)) {
84
+ return replacer(expression.name);
85
+ }
86
+ if (t.isMemberExpression(expression) && t.isIdentifier(expression.object)) {
87
+ return replacer(expression.object.name);
88
+ }
89
+ if (t.isCallExpression(expression) && t.isIdentifier(expression.callee)) {
90
+ return replacer(expression.callee.name);
91
+ }
92
+ if (
93
+ t.isCallExpression(expression) &&
94
+ t.isMemberExpression(expression.callee) &&
95
+ t.isIdentifier(expression.callee.object)
96
+ ) {
97
+ return replacer(expression.callee.object.name);
98
+ }
99
+ return false;
100
+ }
101
+
102
+ /**
103
+ * The value for an expression can be a simple identifier e.g.
104
+ * import { x } from "demo.yak";
105
+ * console.log(x);
106
+ *
107
+ * However it could also be a nested value e.g.
108
+ * import { x } from "demo.yak";
109
+ * console.log(x.persons[0].hobbies["art"].name);
110
+ *
111
+ * This function recursively searches for the value of the expression
112
+ * or returns false if the value is not found
113
+ *
114
+ * @param {import("@babel/types").Expression | import("@babel/types").TSType} expression
115
+ * @param {any} value
116
+ * @param {import("@babel/types")} t
117
+ */
118
+ function getReplacementValueForExpression(expression, value, t) {
119
+ if (value === null || value === undefined || typeof value === "boolean") {
120
+ return false;
121
+ }
122
+ if (t.isIdentifier(expression)) {
123
+ if (typeof value === "string" || typeof value === "number") {
124
+ return value;
125
+ }
126
+ }
127
+
128
+ if (typeof value === "object" && t.isMemberExpression(expression)) {
129
+ if (expression.computed) {
130
+ // e.g. x[0]
131
+ if (t.isNumericLiteral(expression.property)) {
132
+ return getReplacementValueForExpression(
133
+ expression.object,
134
+ value[expression.property.value],
135
+ t
136
+ );
137
+ }
138
+ // e.g. x["0"]
139
+ else if (t.isStringLiteral(expression.property)) {
140
+ return getReplacementValueForExpression(
141
+ expression.object,
142
+ value[expression.property.value],
143
+ t
144
+ );
145
+ } else {
146
+ // right now we don't support dynamic property names
147
+ // e.g. x[y]
148
+ return false;
149
+ }
150
+ }
151
+ // e.g. x.y
152
+ else if (t.isIdentifier(expression.property)) {
153
+ return getReplacementValueForExpression(
154
+ expression.object,
155
+ value[expression.property.name],
156
+ t
157
+ );
158
+ }
159
+ }
160
+ return false;
161
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-yak",
3
- "version": "0.0.28",
3
+ "version": "0.0.29",
4
4
  "type": "module",
5
5
  "types": "./dist/",
6
6
  "exports": {