next-yak 0.0.26 → 0.0.27

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 CHANGED
@@ -16,7 +16,8 @@
16
16
  - **Standard CSS Syntax**: Write styles in familiar, easy-to-use CSS
17
17
  - **Integrates with Atomic CSS**: Easily combines with atomic CSS frameworks like Tailwind CSS for more design options
18
18
 
19
- [Preview (Video)](https://github.com/jantimon/next-yak/assets/4113649/5383f60c-3bab-4aba-906c-d66070b6116c)
19
+ [Preview (Video)](https://github.com/jantimon/next-yak/assets/4113649/f5a220fc-2a0f-46be-a8e7-c855f7faa337
20
+ )
20
21
 
21
22
  ## Installation
22
23
 
@@ -263,7 +264,7 @@ const Container = styled.div`
263
264
  | Yak Features | All (`styled`, `css`, ...) | 🚫 |
264
265
 
265
266
 
266
- [Build time constants (video)](https://github.com/jantimon/next-yak/assets/4113649/6bdc44df-2996-40a3-9255-4b9ed0df464a)
267
+ [Build time constants (video)](https://github.com/jantimon/next-yak/assets/4113649/2c83246c-a03b-4c57-8814-32a7248983ac)
267
268
 
268
269
  ## Todos:
269
270
 
@@ -228,7 +228,7 @@ import { css } from "next-yak";
228
228
  import { easing } from "styleguide";
229
229
 
230
230
  const headline = css\`
231
- transition: color \${({i}) => i * 100 + "ms"} \${easing};
231
+ transition: color \${({i}) => i * 100 + "ms"} \${({easing}) => easing};
232
232
  display: block;
233
233
  \${css\`color: orange\`}
234
234
  \${css\`color: blue\`}
@@ -245,7 +245,9 @@ const headline = css\`
245
245
  \\"--\\\\uD83E\\\\uDDAC18fi82j0\\": ({
246
246
  i
247
247
  }) => i * 100 + \\"ms\\",
248
- \\"--\\\\uD83E\\\\uDDAC18fi82j1\\": easing
248
+ \\"--\\\\uD83E\\\\uDDAC18fi82j1\\": ({
249
+ easing
250
+ }) => easing
249
251
  }
250
252
  });"
251
253
  `);
@@ -269,7 +271,7 @@ const fadeIn = keyframes\`
269
271
  \`
270
272
 
271
273
  const FadeInButton = styled.button\`
272
- animation: 1s \${fadeIn} ease-out;
274
+ animation: 1s \${() => fadeIn} ease-out;
273
275
  \`
274
276
  `
275
277
  )
@@ -280,7 +282,7 @@ const FadeInButton = styled.button\`
280
282
  const fadeIn = keyframes(__styleYak.fadeIn);
281
283
  const FadeInButton = styled.button(__styleYak.FadeInButton, {
282
284
  \\"style\\": {
283
- \\"--\\\\uD83E\\\\uDDAC18fi82j0\\": fadeIn
285
+ \\"--\\\\uD83E\\\\uDDAC18fi82j0\\": () => fadeIn
284
286
  }
285
287
  });"
286
288
  `);
@@ -459,6 +461,198 @@ const Icon = styled.div\`
459
461
  `);
460
462
  });
461
463
 
464
+ it("should show error when using a runtime value from top level", async () => {
465
+ await expect(() =>
466
+ tsloader.call(
467
+ loaderContext,
468
+ `
469
+ import { styled, css } from "next-yak";
470
+
471
+ const red = new Token("#E50914");
472
+ const headline = css\`
473
+ color: \${red};
474
+ \`
475
+ `
476
+ )
477
+ ).rejects.toThrowErrorMatchingInlineSnapshot(`
478
+ "/some/special/path/page.tsx: line 6: Possible constant used as runtime value for a css variable
479
+ Please move the constant to a .yak import or use an arrow function
480
+ e.g.:
481
+ | import { primaryColor } from './foo.yak'
482
+ | const MyStyledDiv = styled.div\`color: \${primaryColor};\`
483
+ found: \${red}"
484
+ `);
485
+ });
486
+
487
+ it("should show error when using a runtime value from top level in a nested literal", async () => {
488
+ await expect(() =>
489
+ tsloader.call(
490
+ loaderContext,
491
+ `
492
+ import { styled, css } from "next-yak";
493
+
494
+ const $red = "#E50914";
495
+ const Button = styled.button\`
496
+ \${({ $primary, $digits }) => {
497
+ return $primary && css\`
498
+ background-color: #4CAF50;
499
+ color: \${$red};
500
+ \`}}
501
+ \`
502
+ `
503
+ )
504
+ ).rejects.toThrowErrorMatchingInlineSnapshot(`
505
+ "/some/special/path/page.tsx: line 9: Possible constant used as runtime value for a css variable
506
+ Please move the constant to a .yak import or use an arrow function
507
+ e.g.:
508
+ | import { primaryColor } from './foo.yak'
509
+ | const MyStyledDiv = styled.div\`color: \${primaryColor};\`
510
+ found: \${$red}"
511
+ `);
512
+ });
513
+
514
+ it("should show error when calling a runtime value from top level in a nested literal", async () => {
515
+ await expect(() =>
516
+ tsloader.call(
517
+ loaderContext,
518
+ `
519
+ import { styled, css } from "next-yak";
520
+ import { red } from "./colors";
521
+
522
+ const Button = styled.button\`
523
+ \${({ $primary, $digits }) => {
524
+ return $primary && css\`
525
+ background-color: #4CAF50;
526
+ color: \${red()};
527
+ \`}}
528
+ \`
529
+ `
530
+ )
531
+ ).rejects.toThrowErrorMatchingInlineSnapshot(`
532
+ "/some/special/path/page.tsx: line 9: Possible constant used as runtime value for a css variable
533
+ Please move the constant to a .yak import or use an arrow function
534
+ e.g.:
535
+ | import { primaryColor } from './foo.yak'
536
+ | const MyStyledDiv = styled.div\`color: \${primaryColor};\`
537
+ found: \${red()}"
538
+ `);
539
+ });
540
+
541
+ it("should show error when calling a nested runtime value from top level in a nested literal", async () => {
542
+ await expect(() =>
543
+ tsloader.call(
544
+ loaderContext,
545
+ `
546
+ import { styled, css } from "next-yak";
547
+ import { colors } from "./colors";
548
+
549
+ const Button = styled.button\`
550
+ \${({ $primary, $digits }) => {
551
+ return $primary && css\`
552
+ background-color: #4CAF50;
553
+ color: \${colors.red()};
554
+ \`}}
555
+ \`
556
+ `
557
+ )
558
+ ).rejects.toThrowErrorMatchingInlineSnapshot(`
559
+ "/some/special/path/page.tsx: line 9: Possible constant used as runtime value for a css variable
560
+ Please move the constant to a .yak import or use an arrow function
561
+ e.g.:
562
+ | import { primaryColor } from './foo.yak'
563
+ | const MyStyledDiv = styled.div\`color: \${primaryColor};\`
564
+ found: \${colors.red()}"
565
+ `);
566
+ });
567
+
568
+ it("should show error when using a runtime value from another module in a nested literal", async () => {
569
+ await expect(() =>
570
+ tsloader.call(
571
+ loaderContext,
572
+ `
573
+ import { styled, css } from "next-yak";
574
+ import $red from "./colors";
575
+
576
+ const Button = styled.button\`
577
+ \${({ $primary, $digits }) => {
578
+ return $primary && css\`
579
+ background-color: #4CAF50;
580
+ color: \${$red};
581
+ \`}}
582
+ \`
583
+ `
584
+ )
585
+ ).rejects.toThrowErrorMatchingInlineSnapshot(`
586
+ "/some/special/path/page.tsx: line 9: Possible constant used as runtime value for a css variable
587
+ Please move the constant to a .yak import or use an arrow function
588
+ e.g.:
589
+ | import { primaryColor } from './foo.yak'
590
+ | const MyStyledDiv = styled.div\`color: \${primaryColor};\`
591
+ found: \${$red}"
592
+ `);
593
+ });
594
+
595
+ it("should show error when using a runtime object value from top level in a nested literal", async () => {
596
+ await expect(() =>
597
+ tsloader.call(
598
+ loaderContext,
599
+ `
600
+ import { styled, css } from "next-yak";
601
+
602
+ const colors = { red: "#E50914" };
603
+ const Button = styled.button\`
604
+ \${({ $primary, $digits }) => {
605
+ return $primary && css\`
606
+ background-color: #4CAF50;
607
+ color: \${colors.red};
608
+ \`}}
609
+ \`
610
+ `
611
+ )
612
+ ).rejects.toThrowErrorMatchingInlineSnapshot(`
613
+ "/some/special/path/page.tsx: line 9: Possible constant used as runtime value for a css variable
614
+ Please move the constant to a .yak import or use an arrow function
615
+ e.g.:
616
+ | import { primaryColor } from './foo.yak'
617
+ | const MyStyledDiv = styled.div\`color: \${primaryColor};\`
618
+ found: \${colors.red}"
619
+ `);
620
+ });
621
+
622
+ it("should show no error when using a scoped value", async () => {
623
+ await expect(
624
+ await tsloader.call(
625
+ loaderContext,
626
+ `
627
+ import { styled, css } from "next-yak";
628
+
629
+ const Button = styled.button\`
630
+ \${({ $primary, $digits }) => {
631
+ const indent = $digits * 10 + "px";
632
+ return $primary && css\`
633
+ background-color: #4CAF50;
634
+ text-indent: \${indent};
635
+ \`}}
636
+ \`
637
+ `
638
+ )
639
+ ).toMatchInlineSnapshot(`
640
+ "import { styled, css } from \\"next-yak\\";
641
+ import __styleYak from \\"./page.yak.module.css!=!./page?./page.yak.module.css\\";
642
+ const Button = styled.button(__styleYak.Button, ({
643
+ $primary,
644
+ $digits
645
+ }) => {
646
+ const indent = $digits * 10 + \\"px\\";
647
+ return $primary && css(__styleYak.primary_0, {
648
+ \\"style\\": {
649
+ \\"--\\\\uD83E\\\\uDDAC18fi82j0\\": indent
650
+ }
651
+ });
652
+ });"
653
+ `);
654
+ });
655
+
462
656
  it("should ignores empty chunks if they include only a comment", async () => {
463
657
  expect(
464
658
  await tsloader.call(
@@ -21,6 +21,7 @@ const getCssName = require("./lib/getCssName.cjs");
21
21
  * localVarNames: YakLocalIdentifierNames,
22
22
  * isImportedInCurrentFile: boolean,
23
23
  * classNameCount: number,
24
+ * topLevelConstBindings: Set<string>,
24
25
  * varIndex: number,
25
26
  * variableNameToStyledCall: Map<string, {
26
27
  * wasAdded: boolean,
@@ -122,8 +123,33 @@ module.exports = function (babel, options) {
122
123
  this.classNameCount = 0;
123
124
  this.varIndex = 0;
124
125
  this.variableNameToStyledCall = new Map();
126
+ this.topLevelConstBindings = new Set();
125
127
  },
126
128
  visitor: {
129
+ Program(path, state) {
130
+ const bindings = Object.entries(path.scope.bindings);
131
+ const constBindings = bindings.filter(([_name, binding]) => {
132
+ if (binding.kind === "module") {
133
+ // it is unclear if this is a const binding
134
+ // however to be safe, all module bindings are considered consts
135
+ return true;
136
+ }
137
+ return (
138
+ // let and var might change but this would be very strange react code
139
+ // as states must use useState or useExternalStore
140
+ (binding.kind === "let" ||
141
+ binding.kind === "var" ||
142
+ binding.kind === "const") &&
143
+ // don't consider functions or arrow functions as constants
144
+ (!("init" in binding.path.node) ||
145
+ (!t.isFunctionDeclaration(binding.path.node.init) &&
146
+ !t.isArrowFunctionExpression(binding.path.node.init)))
147
+ );
148
+ });
149
+ constBindings.forEach(([name]) => {
150
+ this.topLevelConstBindings.add(name);
151
+ });
152
+ },
127
153
  /**
128
154
  * Store the name of the imported 'css' and 'styled' variables e.g.:
129
155
  * - `import { css, styled } from 'next-yak'` -> { css: 'css', styled: 'styled' }
@@ -326,6 +352,39 @@ module.exports = function (babel, options) {
326
352
  (type.empty && wasInsideCssValue))
327
353
  ) {
328
354
  wasInsideCssValue = true;
355
+ // to prevent overuse of css variables, we only allow expressions
356
+ // for css variables for arrow function expressions
357
+ if (
358
+ // e.g. styled.div`color: ${x};`
359
+ (t.isIdentifier(expression) &&
360
+ this.topLevelConstBindings.has(expression.name)) ||
361
+ // e.g. styled.div`color: ${x.y};`
362
+ (t.isMemberExpression(expression) &&
363
+ t.isIdentifier(expression.object) &&
364
+ this.topLevelConstBindings.has(expression.object.name)) ||
365
+ // e.g. styled.div`color: ${x()};`
366
+ (t.isCallExpression(expression) &&
367
+ // x()
368
+ ((t.isIdentifier(expression.callee) &&
369
+ this.topLevelConstBindings.has(expression.callee.name)) ||
370
+ // x.y()
371
+ (t.isMemberExpression(expression.callee) &&
372
+ t.isIdentifier(expression.callee.object) &&
373
+ this.topLevelConstBindings.has(
374
+ expression.callee.object.name
375
+ ))))
376
+ ) {
377
+ throw new InvalidPositionError(
378
+ "Possible constant used as runtime value for a css variable\n" +
379
+ "Please move the constant to a .yak import or use an arrow function\n" +
380
+ "e.g.:\n" +
381
+ "| import { primaryColor } from './foo.yak'\n" +
382
+ "| const MyStyledDiv = styled.div`color: ${primaryColor};`",
383
+ expression,
384
+ this.file
385
+ );
386
+ }
387
+
329
388
  if (!cssVariablesInlineStyle) {
330
389
  cssVariablesInlineStyle = t.objectExpression([]);
331
390
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-yak",
3
- "version": "0.0.26",
3
+ "version": "0.0.27",
4
4
  "type": "module",
5
5
  "types": "./dist/",
6
6
  "exports": {
@@ -30,22 +30,6 @@
30
30
  }
31
31
  }
32
32
  },
33
- "scripts": {
34
- "prepublishOnly": "npm run build && npm run test && npm run test:types:code && npm run test:types:test",
35
- "build": "pnpm run --filter . \"/^build:/\"",
36
- "build:runtime": "tsup runtime/index.ts --target es2022 --clean --external react --external next-yak/context --format cjs,esm --minify --sourcemap --out-dir dist",
37
- "build:runtime:types": "tsup runtime/index.ts --target es2022 --clean --external react --dts-only --format cjs,esm --minify --sourcemap --out-dir dist",
38
- "build:static": "tsup static/index.ts --target es2022 --clean --external react --external next-yak/context --format cjs,esm --minify --sourcemap --out-dir dist/static",
39
- "build:static:types": "tsup static/index.ts --target es2022 --clean --external react --dts-only --format cjs,esm --minify --sourcemap --out-dir dist/static",
40
- "build:baseContext": "tsup runtime/context/baseContext.tsx --target es2022 --external react --dts --format cjs,esm --sourcemap --out-dir dist/context/",
41
- "build:context:client": "tsup runtime/context/index.tsx --target es2022 --external react --external next-yak/context/baseContext --dts --format cjs,esm --sourcemap --out-dir dist/context/",
42
- "build:context:server": "tsup runtime/context/index.server.tsx --target es2022 --external react --external next-yak/context/baseContext --external ./index.js --format cjs,esm --sourcemap --out-dir dist/context/",
43
- "build:withYak": "tsup withYak/index.ts --target es2022 --dts --format cjs --sourcemap --out-dir withYak --external ../loaders/tsloader.cjs --external ../loaders/cssloader.cjs",
44
- "test": "vitest run",
45
- "test:types:code": "tsc -p tsconfig.json",
46
- "test:types:test": "tsc -p ./runtime/__tests__/tsconfig.json",
47
- "test:watch": "vitest --watch"
48
- },
49
33
  "dependencies": {
50
34
  "@babel/core": "7.23.2",
51
35
  "@babel/plugin-syntax-typescript": "7.22.5",
@@ -74,5 +58,20 @@
74
58
  "loaders",
75
59
  "runtime",
76
60
  "withYak"
77
- ]
78
- }
61
+ ],
62
+ "scripts": {
63
+ "build": "pnpm run --filter . \"/^build:/\"",
64
+ "build:runtime": "tsup runtime/index.ts --target es2022 --clean --external react --external next-yak/context --format cjs,esm --minify --sourcemap --out-dir dist",
65
+ "build:runtime:types": "tsup runtime/index.ts --target es2022 --clean --external react --dts-only --format cjs,esm --minify --sourcemap --out-dir dist",
66
+ "build:static": "tsup static/index.ts --target es2022 --clean --external react --external next-yak/context --format cjs,esm --minify --sourcemap --out-dir dist/static",
67
+ "build:static:types": "tsup static/index.ts --target es2022 --clean --external react --dts-only --format cjs,esm --minify --sourcemap --out-dir dist/static",
68
+ "build:baseContext": "tsup runtime/context/baseContext.tsx --target es2022 --external react --dts --format cjs,esm --sourcemap --out-dir dist/context/",
69
+ "build:context:client": "tsup runtime/context/index.tsx --target es2022 --external react --external next-yak/context/baseContext --dts --format cjs,esm --sourcemap --out-dir dist/context/",
70
+ "build:context:server": "tsup runtime/context/index.server.tsx --target es2022 --external react --external next-yak/context/baseContext --external ./index.js --format cjs,esm --sourcemap --out-dir dist/context/",
71
+ "build:withYak": "tsup withYak/index.ts --target es2022 --dts --format cjs --sourcemap --out-dir withYak --external ../loaders/tsloader.cjs --external ../loaders/cssloader.cjs",
72
+ "test": "vitest run",
73
+ "test:types:code": "tsc -p tsconfig.json",
74
+ "test:types:test": "tsc -p ./runtime/__tests__/tsconfig.json",
75
+ "test:watch": "vitest --watch"
76
+ }
77
+ }