next-yak 0.0.29 → 0.0.30

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.
@@ -13,6 +13,18 @@ const loaderContext = {
13
13
  xl: "@media (min-width: 1280px)",
14
14
  xxl: "@media (min-width: 1536px)",
15
15
  },
16
+ spacing: {
17
+ 0.5: "4px",
18
+ 1: "8px",
19
+ 2: "16px",
20
+ 4: "32px",
21
+ },
22
+ typography: {
23
+ "letter spacing": "0.05em",
24
+ primary: {
25
+ "font weight": 800,
26
+ },
27
+ },
16
28
  };
17
29
  },
18
30
  getOptions: () => ({
@@ -500,7 +512,7 @@ const Component = styled.div\`
500
512
  background-color: brown;
501
513
  \`}
502
514
  \`}
503
-
515
+
504
516
  border: 2px solid pink;
505
517
  }
506
518
  \`;
@@ -608,4 +620,33 @@ const Component = styled.div\`
608
620
  }"
609
621
  `);
610
622
  });
623
+
624
+ it("should replace all array like constants", async () => {
625
+ expect(
626
+ await cssloader.call(
627
+ loaderContext,
628
+ `
629
+ import { css } from "next-yak";
630
+ import { queries, spacing, typography } from "@/theme.yak";
631
+
632
+ const headline = css\`
633
+ \${queries["xl"]} {
634
+ color: red;
635
+ }
636
+ margin: -\${spacing[2]};
637
+ font-weight: \${typography.primary["font weight"]};
638
+ letter-spacing: \${typography["letter spacing"]};
639
+ \``
640
+ )
641
+ ).toMatchInlineSnapshot(`
642
+ ".headline_0 {
643
+ @media (min-width: 1280px) {
644
+ color: red;
645
+ }
646
+ margin: -16px;
647
+ font-weight: 800;
648
+ letter-spacing: 0.05em;
649
+ }"
650
+ `);
651
+ });
611
652
  });
@@ -29,16 +29,18 @@ module.exports = function replaceTokensInQuasiExpressions(quasi, replacer, t) {
29
29
  // so removing items won't affect the index of the next item
30
30
  for (let i = quasi.expressions.length - 1; i >= 0; i--) {
31
31
  const expression = quasi.expressions[i];
32
- // find the value to replace the expression with
33
- const replacement = getReplacement(expression, replacer, t);
32
+ // break the expression into parts
33
+ // e.g. x.y.z -> ["x", "y", "z"]
34
+ const parts = getExpressionParts(expression, t);
35
+ // find the replacement for the expression
36
+ const replacement = parts && replacer(parts[0]);
34
37
  // if it is a nested value, find the value of the expression
35
38
  // e.g. x.y.z -> find the value of z
36
- const replacementValue = replacement && getReplacementValueForExpression(
37
- expression,
39
+ const replacementValue = replacement && getReplacementValue(
38
40
  replacement,
39
- t
41
+ parts
40
42
  );
41
- if (replacementValue !== false) {
43
+ if (replacementValue !== false && replacementValue !== null) {
42
44
  replaceExpressionAndMergeQuasis(quasi, i, replacementValue);
43
45
  }
44
46
  }
@@ -69,93 +71,68 @@ function replaceExpressionAndMergeQuasis(quasi, expressionIndex, replacement) {
69
71
  * Find the replacement for the expression
70
72
  *
71
73
  * searches for:
72
- * - `x` -> x
73
- * - `x.y` -> x
74
- * - `x[0]` -> x
75
- * - `x()` -> x
76
- * - `x.y()` -> x
74
+ * - `x` -> ["x"]
75
+ * - `x.y` -> ["x", "y"]
76
+ * - `x[0]` -> ["x", 0]
77
+ * - `x()` -> ["x"]
78
+ * - `x.y()` -> ["x", "y"]
79
+ * - (1 + 2) -> null
77
80
  *
78
81
  * @param {import("@babel/types").Expression | import("@babel/types").TSType} expression
79
- * @param {(name: string) => unknown} replacer
80
82
  * @param {import("@babel/types")} t
81
83
  */
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);
84
+ function getExpressionParts(expression, t) {
85
+ let currentExpression = expression;
86
+ /** @type {string[]} */
87
+ const tokens = [];
88
+ while (currentExpression) {
89
+ // e.g. x
90
+ if (t.isIdentifier(currentExpression)) {
91
+ tokens.unshift(currentExpression.name);
92
+ break;
93
+ }
94
+ // e.g. x.y
95
+ if (t.isMemberExpression(currentExpression)) {
96
+ if (currentExpression.computed === false && t.isIdentifier(currentExpression.property)) {
97
+ tokens.unshift(currentExpression.property.name);
98
+ } else if (t.isStringLiteral(currentExpression.property)) {
99
+ tokens.unshift(currentExpression.property.value);
100
+ } else if (t.isNumericLiteral(currentExpression.property)) {
101
+ tokens.unshift(String(currentExpression.property.value));
102
+ } else {
103
+ return null;
104
+ }
105
+ currentExpression = currentExpression.object;
106
+ } else if (t.isCallExpression(currentExpression)) {
107
+ if (!t.isExpression(currentExpression.callee)) {
108
+ return null;
109
+ }
110
+ currentExpression = currentExpression.callee;
111
+ } else {
112
+ return null;
113
+ }
98
114
  }
99
- return false;
115
+ return tokens;
100
116
  }
101
117
 
102
118
  /**
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
119
+ * Get the value of the replacement
120
+ *
121
+ * e.g. for `replacement.x.y[0]` and `replacement = { x: { y: [42] } }`
122
+ * parts = ["replacement", "x", "y", 0]
123
+ * --> 42
124
+ *
125
+ * @param {any} replacement
126
+ * @param {string[]} parts
117
127
  */
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
- );
128
+ function getReplacementValue(replacement, parts) {
129
+ let currentReplacement = replacement;
130
+ for (let i = 1; i < parts.length; i++) {
131
+ const part = parts[i];
132
+ if (currentReplacement == null || typeof currentReplacement !== "object") {
133
+ return false;
158
134
  }
135
+ currentReplacement = currentReplacement[part];
159
136
  }
160
- return false;
161
- }
137
+ return currentReplacement;
138
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-yak",
3
- "version": "0.0.29",
3
+ "version": "0.0.30",
4
4
  "type": "module",
5
5
  "types": "./dist/",
6
6
  "exports": {