next-style 2.2.1 → 2.2.3
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 +474 -21
- package/dist/index.cjs +24 -24
- package/dist/index.d.ts +204 -1
- package/dist/index.js +21 -21
- package/dist/postcss-plugin/index.cjs +100 -70
- package/dist/postcss-plugin/index.d.ts +1 -6
- package/dist/postcss-plugin/index.js +100 -70
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
Write styles in TypeScript. Ship pure CSS. Zero overhead.
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/next-style)
|
|
10
|
+
[](https://www.npmjs.com/package/next-style)
|
|
11
|
+
[](https://www.typescriptlang.org/)
|
|
10
12
|
[](https://opensource.org/licenses/MIT)
|
|
11
13
|
[](https://turbo.build/pack)
|
|
12
14
|
|
|
@@ -14,8 +16,51 @@ Write styles in TypeScript. Ship pure CSS. Zero overhead.
|
|
|
14
16
|
|
|
15
17
|
---
|
|
16
18
|
|
|
19
|
+
## Overview
|
|
20
|
+
|
|
17
21
|
**next-style** extracts all styles at build time through a PostCSS plugin — no style injection, no hydration cost, no runtime. The compiled CSS lands in your `globals.css` exactly once.
|
|
18
22
|
|
|
23
|
+
## Table of Contents
|
|
24
|
+
|
|
25
|
+
- [Overview](#overview)
|
|
26
|
+
- [Quick Start](#quick-start)
|
|
27
|
+
- [Features](#features)
|
|
28
|
+
- [Installation](#installation)
|
|
29
|
+
- [Setup](#setup)
|
|
30
|
+
- [API](#api)
|
|
31
|
+
- [Responsive Design](#responsive-design)
|
|
32
|
+
- [TypeScript](#typescript)
|
|
33
|
+
- [Advanced](#advanced)
|
|
34
|
+
- [Performance](#performance)
|
|
35
|
+
- [Best Practices](#best-practices--common-patterns)
|
|
36
|
+
- [Troubleshooting](#troubleshooting)
|
|
37
|
+
- [Browser Support](#browser-support)
|
|
38
|
+
- [How It Works](#how-it-works)
|
|
39
|
+
- [Contributing](#contributing)
|
|
40
|
+
- [License](#license)
|
|
41
|
+
|
|
42
|
+
## Quick Start
|
|
43
|
+
|
|
44
|
+
1. **Install the package:**
|
|
45
|
+
```bash
|
|
46
|
+
npm install next-style
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
2. **Configure PostCSS** (`postcss.config.js`):
|
|
50
|
+
```js
|
|
51
|
+
export default {
|
|
52
|
+
plugins: {
|
|
53
|
+
"next-style/plugin": {},
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
3. **Import in `globals.css`:**
|
|
59
|
+
```css
|
|
60
|
+
@import "next-style";
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
4. **Use in your components:**
|
|
19
64
|
```tsx
|
|
20
65
|
import { css } from "next-style"
|
|
21
66
|
|
|
@@ -24,6 +69,60 @@ const button = css({
|
|
|
24
69
|
borderRadius: "6px",
|
|
25
70
|
backgroundColor: "#7F77DD",
|
|
26
71
|
":hover": { backgroundColor: "#534AB7" },
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
export function Button() {
|
|
75
|
+
return <button className={button}>Click me</button>
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
That's it! No providers, wrappers, or complex configuration. All styles are extracted at build time.
|
|
80
|
+
|
|
81
|
+
### App Router & Server Components
|
|
82
|
+
|
|
83
|
+
next-style works seamlessly with Next.js 13+ App Router and Server Components:
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
// app/components/Button.tsx (Server Component)
|
|
87
|
+
import { css } from "next-style"
|
|
88
|
+
|
|
89
|
+
const button = css({ /* styles */ })
|
|
90
|
+
|
|
91
|
+
export function Button() {
|
|
92
|
+
return <button className={button}>Click</button>
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
// app/components/Counter.tsx (Client Component)
|
|
98
|
+
'use client'
|
|
99
|
+
|
|
100
|
+
import { css } from "next-style"
|
|
101
|
+
import { useState } from "react"
|
|
102
|
+
|
|
103
|
+
const counter = css({ /* styles */ })
|
|
104
|
+
|
|
105
|
+
export function Counter() {
|
|
106
|
+
const [count, setCount] = useState(0)
|
|
107
|
+
return <div className={counter}>{count}</div>
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Both work identically — the `css()` call is evaluated at build time regardless of component type.
|
|
112
|
+
|
|
113
|
+
## Example
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
import { css } from "next-style"
|
|
117
|
+
|
|
118
|
+
const button = css({
|
|
119
|
+
padding: "8px 16px",
|
|
120
|
+
borderRadius: "6px",
|
|
121
|
+
backgroundColor: "#7F77DD",
|
|
122
|
+
cursor: "pointer",
|
|
123
|
+
transition: "background-color 0.2s",
|
|
124
|
+
":hover": { backgroundColor: "#534AB7" },
|
|
125
|
+
":active": { transform: "scale(0.98)" },
|
|
27
126
|
"@md": { padding: "10px 20px" },
|
|
28
127
|
})
|
|
29
128
|
|
|
@@ -37,16 +136,26 @@ export function Button() {
|
|
|
37
136
|
| | |
|
|
38
137
|
|---|---|
|
|
39
138
|
| ⚡ **Zero runtime** | All styles extracted at build time — 0 bytes of style JS shipped |
|
|
40
|
-
| 🔷 **Turbopack ready** | Works out of the box with Next.js
|
|
139
|
+
| 🔷 **Turbopack ready** | Works out of the box with Next.js 16+ and Turbopack |
|
|
41
140
|
| 🔒 **Fully typed** | Every CSS property and value typed via [csstype](https://github.com/frenic/csstype) |
|
|
42
141
|
| 📱 **Responsive first** | Shorthand breakpoints (`@sm` → `@2xl`) sorted mobile-first automatically |
|
|
43
142
|
| ♻️ **Deduplication** | Identical style objects always hash to the same class name |
|
|
44
143
|
| 🌍 **Global styles** | `global()` for resets, base typography, and third-party overrides |
|
|
45
144
|
| 🎞️ **Keyframes** | Declare `@keyframes` inline next to the style that uses them |
|
|
46
145
|
| 📦 **Tiny** | ~2 KB minified + gzipped |
|
|
146
|
+
| 🚀 **Fast builds** | Negligible build-time overhead — simple hashing and string concatenation |
|
|
147
|
+
| 🎨 **CSS-in-JS power** | All CSS features: pseudo-classes, container queries, `@supports`, `@layer`, CSS variables |
|
|
47
148
|
|
|
48
149
|
Full support for pseudo-classes, pseudo-elements, media queries, container queries, `@supports`, `@layer`, and CSS variables.
|
|
49
150
|
|
|
151
|
+
### Next.js Version Support
|
|
152
|
+
|
|
153
|
+
| Next.js Version | Supported |
|
|
154
|
+
|-----------------|-----------|
|
|
155
|
+
| 16+ | ✅ Full support with Turbopack |
|
|
156
|
+
| 15.x | ✅ Supported |
|
|
157
|
+
| < 15.0 | ❌ Not supported |
|
|
158
|
+
|
|
50
159
|
## Installation
|
|
51
160
|
|
|
52
161
|
```bash
|
|
@@ -60,7 +169,11 @@ pnpm add next-style
|
|
|
60
169
|
bun add next-style
|
|
61
170
|
```
|
|
62
171
|
|
|
63
|
-
|
|
172
|
+
**Peer dependencies required:**
|
|
173
|
+
- `next >= 15.0.0`
|
|
174
|
+
- `postcss >= 8.0.0`
|
|
175
|
+
|
|
176
|
+
> Most Next.js projects already include PostCSS. If you're unsure, check your `package.json` or run `npm list postcss`.
|
|
64
177
|
|
|
65
178
|
## Setup
|
|
66
179
|
|
|
@@ -246,6 +359,7 @@ All CSS properties and values are typed via [csstype](https://github.com/frenic/
|
|
|
246
359
|
```tsx
|
|
247
360
|
import { css, type CSSObject } from "next-style"
|
|
248
361
|
|
|
362
|
+
// Type a reusable style object before passing it to css()
|
|
249
363
|
const base: CSSObject = {
|
|
250
364
|
fontSize: "16px", // ✅
|
|
251
365
|
colour: "red", // ❌ TypeScript error: unknown property
|
|
@@ -262,7 +376,48 @@ const el = css(base)
|
|
|
262
376
|
| `CSSObject` | Full style object — properties, at-rules, and pseudos |
|
|
263
377
|
| `CSSProperties` | CSS properties only, no at-rules or pseudos |
|
|
264
378
|
|
|
265
|
-
##
|
|
379
|
+
## Advanced
|
|
380
|
+
|
|
381
|
+
### `createTransformer`
|
|
382
|
+
|
|
383
|
+
For build tooling, SWC/Babel plugins, and test harnesses that need an isolated style collector independent of the global runtime:
|
|
384
|
+
|
|
385
|
+
```ts
|
|
386
|
+
import { createTransformer } from "next-style"
|
|
387
|
+
|
|
388
|
+
const { collector, transformCssCall } = createTransformer()
|
|
389
|
+
|
|
390
|
+
const className = transformCssCall({ color: "red", fontSize: "16px" })
|
|
391
|
+
// → "ns-abc123"
|
|
392
|
+
|
|
393
|
+
const css = collector.getAllStyles()
|
|
394
|
+
// → ".ns-abc123 { color: red; font-size: 16px; }"
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### `StyleCollector`
|
|
398
|
+
|
|
399
|
+
The class powering both the runtime and `createTransformer`. Exposed for custom integrations:
|
|
400
|
+
|
|
401
|
+
```ts
|
|
402
|
+
import { StyleCollector } from "next-style"
|
|
403
|
+
|
|
404
|
+
const collector = new StyleCollector()
|
|
405
|
+
collector.addStyle({ color: "red" }) // → "ns-abc123"
|
|
406
|
+
collector.addGlobalStyle("body", { margin: "0" })
|
|
407
|
+
collector.getAllStyles() // full CSS string
|
|
408
|
+
collector.flush("/custom/path/styles.css") // write to disk
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### How the PostCSS bridge works
|
|
412
|
+
|
|
413
|
+
Because PostCSS runs in a separate process from the module graph, in-memory style collectors cannot be shared. next-style solves this with a file-based bridge:
|
|
414
|
+
|
|
415
|
+
1. Every `css()` / `global()` call writes compiled CSS to `node_modules/.cache/next-style.css`
|
|
416
|
+
2. The PostCSS plugin reads that file and replaces `@import "next-style"` with its contents
|
|
417
|
+
|
|
418
|
+
This is why `@import "next-style"` must appear in `globals.css` — it's the injection point.
|
|
419
|
+
|
|
420
|
+
### CSS Variables
|
|
266
421
|
|
|
267
422
|
next-style pairs naturally with CSS custom properties for design tokens:
|
|
268
423
|
|
|
@@ -286,15 +441,6 @@ const card = css({
|
|
|
286
441
|
})
|
|
287
442
|
```
|
|
288
443
|
|
|
289
|
-
## How it works
|
|
290
|
-
|
|
291
|
-
Because PostCSS runs in a separate process from the module graph, next-style uses a file-based bridge:
|
|
292
|
-
|
|
293
|
-
1. Every `css()` / `global()` call registers styles into an in-memory collector during module evaluation
|
|
294
|
-
2. The PostCSS plugin reads the collector and replaces `@import "next-style"` with the compiled CSS
|
|
295
|
-
|
|
296
|
-
This is why `@import "next-style"` must appear in `globals.css` — it's the injection point.
|
|
297
|
-
|
|
298
444
|
## CSS output order
|
|
299
445
|
|
|
300
446
|
Styles are emitted in this order to ensure correct cascade:
|
|
@@ -317,22 +463,329 @@ Styles are emitted in this order to ensure correct cascade:
|
|
|
317
463
|
|
|
318
464
|
Because styles are extracted at build time, there is no style recalculation, no `<style>` injection, and no FOUC. The output is a single static CSS file.
|
|
319
465
|
|
|
466
|
+
## Best Practices & Common Patterns
|
|
467
|
+
|
|
468
|
+
### Reusable Style Objects
|
|
469
|
+
|
|
470
|
+
Create reusable style definitions by storing them in separate files:
|
|
471
|
+
|
|
472
|
+
```tsx
|
|
473
|
+
// styles/components.ts
|
|
474
|
+
import { type CSSObject } from "next-style"
|
|
475
|
+
|
|
476
|
+
export const buttonBase: CSSObject = {
|
|
477
|
+
padding: "8px 16px",
|
|
478
|
+
borderRadius: "6px",
|
|
479
|
+
fontWeight: 500,
|
|
480
|
+
cursor: "pointer",
|
|
481
|
+
transition: "all 0.2s",
|
|
482
|
+
":active": { transform: "scale(0.98)" },
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
export const buttonPrimary: CSSObject = {
|
|
486
|
+
...buttonBase,
|
|
487
|
+
backgroundColor: "#7F77DD",
|
|
488
|
+
color: "white",
|
|
489
|
+
":hover": { backgroundColor: "#534AB7" },
|
|
490
|
+
}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
```tsx
|
|
494
|
+
// components/Button.tsx
|
|
495
|
+
import { css } from "next-style"
|
|
496
|
+
import { buttonPrimary } from "@/styles/components"
|
|
497
|
+
|
|
498
|
+
export function Button({ children }: { children: React.ReactNode }) {
|
|
499
|
+
return <button className={css(buttonPrimary)}>{children}</button>
|
|
500
|
+
}
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
### Design Tokens with CSS Variables
|
|
504
|
+
|
|
505
|
+
Centralize your design system using CSS variables:
|
|
506
|
+
|
|
507
|
+
```tsx
|
|
508
|
+
// app/globals.ts
|
|
509
|
+
import { global } from "next-style"
|
|
510
|
+
|
|
511
|
+
global({
|
|
512
|
+
":root": {
|
|
513
|
+
// Colors
|
|
514
|
+
"--color-primary": "#7F77DD",
|
|
515
|
+
"--color-secondary": "#534AB7",
|
|
516
|
+
"--color-text": "#1a1a1a",
|
|
517
|
+
"--color-bg": "#ffffff",
|
|
518
|
+
|
|
519
|
+
// Spacing
|
|
520
|
+
"--spacing-xs": "4px",
|
|
521
|
+
"--spacing-sm": "8px",
|
|
522
|
+
"--spacing-md": "16px",
|
|
523
|
+
"--spacing-lg": "24px",
|
|
524
|
+
"--spacing-xl": "32px",
|
|
525
|
+
|
|
526
|
+
// Radius
|
|
527
|
+
"--radius-sm": "4px",
|
|
528
|
+
"--radius-md": "8px",
|
|
529
|
+
"--radius-lg": "12px",
|
|
530
|
+
},
|
|
531
|
+
"body": {
|
|
532
|
+
backgroundColor: "var(--color-bg)",
|
|
533
|
+
color: "var(--color-text)",
|
|
534
|
+
},
|
|
535
|
+
})
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
```tsx
|
|
539
|
+
// Use in components
|
|
540
|
+
const card = css({
|
|
541
|
+
backgroundColor: "var(--color-bg)",
|
|
542
|
+
padding: "var(--spacing-md)",
|
|
543
|
+
borderRadius: "var(--radius-md)",
|
|
544
|
+
})
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
### Variant Patterns
|
|
548
|
+
|
|
549
|
+
Create component variants by conditionally merging styles:
|
|
550
|
+
|
|
551
|
+
```tsx
|
|
552
|
+
import { css, type CSSObject } from "next-style"
|
|
553
|
+
|
|
554
|
+
interface ButtonProps {
|
|
555
|
+
variant?: "primary" | "secondary" | "ghost"
|
|
556
|
+
size?: "sm" | "md" | "lg"
|
|
557
|
+
children: React.ReactNode
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
export function Button({ variant = "primary", size = "md", children }: ButtonProps) {
|
|
561
|
+
const baseStyles: CSSObject = {
|
|
562
|
+
fontWeight: 500,
|
|
563
|
+
borderRadius: "6px",
|
|
564
|
+
cursor: "pointer",
|
|
565
|
+
transition: "all 0.2s",
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const variantStyles: Record<string, CSSObject> = {
|
|
569
|
+
primary: {
|
|
570
|
+
backgroundColor: "var(--color-primary)",
|
|
571
|
+
color: "white",
|
|
572
|
+
":hover": { backgroundColor: "var(--color-secondary)" },
|
|
573
|
+
},
|
|
574
|
+
secondary: {
|
|
575
|
+
backgroundColor: "var(--color-secondary)",
|
|
576
|
+
color: "white",
|
|
577
|
+
},
|
|
578
|
+
ghost: {
|
|
579
|
+
backgroundColor: "transparent",
|
|
580
|
+
color: "var(--color-primary)",
|
|
581
|
+
":hover": { backgroundColor: "rgba(127, 119, 221, 0.1)" },
|
|
582
|
+
},
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
const sizeStyles: Record<string, CSSObject> = {
|
|
586
|
+
sm: { padding: "4px 12px", fontSize: "14px" },
|
|
587
|
+
md: { padding: "8px 16px", fontSize: "16px" },
|
|
588
|
+
lg: { padding: "12px 24px", fontSize: "18px" },
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const className = css({
|
|
592
|
+
...baseStyles,
|
|
593
|
+
...variantStyles[variant],
|
|
594
|
+
...sizeStyles[size],
|
|
595
|
+
})
|
|
596
|
+
|
|
597
|
+
return <button className={className}>{children}</button>
|
|
598
|
+
}
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
### Dark Mode Support
|
|
602
|
+
|
|
603
|
+
Use CSS variables to implement dark mode:
|
|
604
|
+
|
|
605
|
+
```tsx
|
|
606
|
+
// app/globals.ts
|
|
607
|
+
import { global } from "next-style"
|
|
608
|
+
|
|
609
|
+
global({
|
|
610
|
+
":root": {
|
|
611
|
+
"--bg-primary": "#ffffff",
|
|
612
|
+
"--text-primary": "#1a1a1a",
|
|
613
|
+
},
|
|
614
|
+
|
|
615
|
+
"[data-theme='dark']": {
|
|
616
|
+
"--bg-primary": "#1a1a1a",
|
|
617
|
+
"--text-primary": "#ffffff",
|
|
618
|
+
},
|
|
619
|
+
})
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
```tsx
|
|
623
|
+
// components/ThemeToggle.tsx
|
|
624
|
+
'use client'
|
|
625
|
+
|
|
626
|
+
import { useEffect, useState } from "react"
|
|
627
|
+
|
|
628
|
+
export function ThemeToggle() {
|
|
629
|
+
const [theme, setTheme] = useState("light")
|
|
630
|
+
|
|
631
|
+
useEffect(() => {
|
|
632
|
+
const current = document.documentElement.getAttribute("data-theme") || "light"
|
|
633
|
+
setTheme(current)
|
|
634
|
+
}, [])
|
|
635
|
+
|
|
636
|
+
const toggle = () => {
|
|
637
|
+
const newTheme = theme === "light" ? "dark" : "light"
|
|
638
|
+
document.documentElement.setAttribute("data-theme", newTheme)
|
|
639
|
+
setTheme(newTheme)
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
return <button onClick={toggle}>Toggle Theme</button>
|
|
643
|
+
}
|
|
644
|
+
```
|
|
645
|
+
|
|
320
646
|
## Troubleshooting
|
|
321
647
|
|
|
322
|
-
|
|
648
|
+
### Styles not appearing
|
|
649
|
+
|
|
650
|
+
1. ✅ Check that `postcss.config.js` includes `"next-style/plugin": {}`
|
|
651
|
+
2. ✅ Check that `@import "next-style";` is at the **top** of `globals.css` (before other styles)
|
|
652
|
+
3. ✅ Verify you imported `css` from the correct package:
|
|
653
|
+
```tsx
|
|
654
|
+
import { css } from "next-style" // ✓ correct
|
|
655
|
+
import { css } from "next-style/plugin" // ✗ incorrect
|
|
656
|
+
```
|
|
657
|
+
4. ✅ Restart the dev server after any PostCSS config change
|
|
658
|
+
5. ✅ Clear the Next.js cache: `rm -rf .next` and restart
|
|
659
|
+
|
|
660
|
+
### First cold boot shows no styles
|
|
661
|
+
|
|
662
|
+
On the very first build, no `css()` calls have been evaluated yet so the cache file doesn't exist. Run the dev server once to populate the cache. Subsequent builds will include all styles. This is expected behavior on cold starts.
|
|
663
|
+
|
|
664
|
+
**For production/CI builds:** The PostCSS plugin automatically scans your source files to extract styles if the cache file is missing, so cold builds on GitHub Actions, Vercel, or other CI systems work without extra setup.
|
|
665
|
+
|
|
666
|
+
### Build errors after adding PostCSS plugins
|
|
667
|
+
|
|
668
|
+
Ensure `next-style` is listed **first** in the plugins object — it must run before other transformations:
|
|
323
669
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
670
|
+
```js
|
|
671
|
+
// ✓ Correct order
|
|
672
|
+
export default {
|
|
673
|
+
plugins: {
|
|
674
|
+
"next-style/plugin": {},
|
|
675
|
+
autoprefixer: {},
|
|
676
|
+
"postcss-preset-env": {},
|
|
677
|
+
},
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// ✗ Wrong order (next-style must be first)
|
|
681
|
+
export default {
|
|
682
|
+
plugins: {
|
|
683
|
+
autoprefixer: {},
|
|
684
|
+
"next-style/plugin": {},
|
|
685
|
+
},
|
|
686
|
+
}
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
### Styles working in dev but not in production
|
|
690
|
+
|
|
691
|
+
1. Check that the PostCSS plugin is configured in your build environment
|
|
692
|
+
2. Verify `NODE_ENV=production` is set during build
|
|
693
|
+
3. Look for CSS minification errors in the build logs
|
|
694
|
+
|
|
695
|
+
### Breakpoints not working
|
|
696
|
+
|
|
697
|
+
Ensure you're using the correct shorthand:
|
|
698
|
+
|
|
699
|
+
```tsx
|
|
700
|
+
// ✓ Correct
|
|
701
|
+
css({ "@md": { fontSize: "20px" } })
|
|
702
|
+
|
|
703
|
+
// ✗ Incorrect (use @md, not md)
|
|
704
|
+
css({ "md": { fontSize: "20px" } })
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
### TypeScript errors
|
|
708
|
+
|
|
709
|
+
Install type definitions (usually automatic):
|
|
710
|
+
|
|
711
|
+
```bash
|
|
712
|
+
npm install --save-dev @types/node
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
If types are still missing, ensure `tsconfig.json` includes:
|
|
716
|
+
|
|
717
|
+
```json
|
|
718
|
+
{
|
|
719
|
+
"compilerOptions": {
|
|
720
|
+
"types": ["node"],
|
|
721
|
+
"strict": true
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
### Performance concerns
|
|
727
|
+
|
|
728
|
+
next-style is extremely lightweight:
|
|
729
|
+
- **Runtime JS:** 0 bytes (all styles extracted at build time)
|
|
730
|
+
- **Build time:** Negligible (simple hashing and string operations)
|
|
731
|
+
- **CSS output:** Automatically deduplicated and minified in production
|
|
732
|
+
|
|
733
|
+
No additional optimizations are needed.
|
|
734
|
+
|
|
735
|
+
## Browser Support
|
|
736
|
+
|
|
737
|
+
**next-style** outputs standard CSS and supports all modern browsers:
|
|
738
|
+
|
|
739
|
+
| Browser | Minimum Version |
|
|
740
|
+
|---------|-----------------|
|
|
741
|
+
| Chrome / Chromium | 90+ |
|
|
742
|
+
| Firefox | 87+ |
|
|
743
|
+
| Safari | 14+ |
|
|
744
|
+
| Edge | 90+ |
|
|
745
|
+
|
|
746
|
+
Older browsers are supported through standard CSS features used. All advanced CSS (container queries, `@supports`, etc.) gracefully degrade in unsupported browsers.
|
|
747
|
+
|
|
748
|
+
## How It Works
|
|
749
|
+
|
|
750
|
+
### Architecture Overview
|
|
751
|
+
|
|
752
|
+
1. **Build Time (Development & Production)**
|
|
753
|
+
- Your `css()` and `global()` calls are evaluated
|
|
754
|
+
- Each unique style object is hashed and assigned a class name (e.g., `ns-abc123`)
|
|
755
|
+
- Compiled CSS is written to `node_modules/.cache/next-style/styles.css`
|
|
756
|
+
|
|
757
|
+
2. **PostCSS Processing**
|
|
758
|
+
- The PostCSS plugin reads the cache file
|
|
759
|
+
- Replaces `@import "next-style"` in your `globals.css` with the collected CSS
|
|
760
|
+
- Minifies CSS in production using `cssnano`
|
|
761
|
+
|
|
762
|
+
3. **Runtime (Production)**
|
|
763
|
+
- Zero JavaScript overhead — styles are already in the static CSS file
|
|
764
|
+
- No style injection, no hydration cost, no runtime recalculation
|
|
765
|
+
|
|
766
|
+
### Deduplication
|
|
767
|
+
|
|
768
|
+
Identical style objects always hash to the same class name:
|
|
769
|
+
|
|
770
|
+
```tsx
|
|
771
|
+
// These produce the same class name and CSS rule
|
|
772
|
+
const style1 = css({ color: "red", fontSize: "16px" })
|
|
773
|
+
const style2 = css({ color: "red", fontSize: "16px" })
|
|
774
|
+
|
|
775
|
+
// style1 === style2 → true
|
|
776
|
+
// Output: only one `.ns-xyz { color: red; font-size: 16px; }` rule
|
|
777
|
+
```
|
|
328
778
|
|
|
329
|
-
|
|
779
|
+
### File-Based Bridge
|
|
330
780
|
|
|
331
|
-
|
|
781
|
+
Since PostCSS runs in a separate process, next-style uses a file-based bridge:
|
|
782
|
+
- Every `css()` / `global()` call writes compiled CSS to a cache file
|
|
783
|
+
- The PostCSS plugin reads that file and injects it into your CSS
|
|
784
|
+
- This enables dev-server hot-reloading and zero-setup cold builds
|
|
332
785
|
|
|
333
|
-
|
|
786
|
+
## Contributing
|
|
334
787
|
|
|
335
|
-
|
|
788
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
336
789
|
|
|
337
790
|
## License
|
|
338
791
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
${
|
|
3
|
-
${
|
|
1
|
+
"use strict";var H=Object.create;var $=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var G=Object.getOwnPropertyNames;var J=Object.getPrototypeOf,L=Object.prototype.hasOwnProperty;var Z=(s,e)=>{for(var t in e)$(s,t,{get:e[t],enumerable:!0})},N=(s,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of G(e))!L.call(s,r)&&r!==t&&$(s,r,{get:()=>e[r],enumerable:!(n=k(e,r))||n.enumerable});return s};var Q=(s,e,t)=>(t=s!=null?H(J(s)):{},N(e||!s||!s.__esModule?$(t,"default",{value:s,enumerable:!0}):t,s)),_=s=>N($({},"__esModule",{value:!0}),s);var v={};Z(v,{BREAKPOINTS:()=>f,StyleCollector:()=>p,camelToKebab:()=>R,createTransformer:()=>K,css:()=>z,deduplicateStyles:()=>T,generateClassHash:()=>m,global:()=>F,normalizeMediaQuery:()=>x,styleCollector:()=>g,validateCSSProperty:()=>W});module.exports=_(v);var j=Q(require("fs"),1),E=Q(require("path"),1);function R(s){return s.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)}function m(s){let e=typeof s=="string"?s:JSON.stringify(s),t=0;for(let n=0;n<e.length;n++){let r=e.charCodeAt(n);t=(t<<5)-t+r,t=t&t}return Math.abs(t).toString(36)}var f={"@sm":"@media (min-width: 640px)","@md":"@media (min-width: 768px)","@lg":"@media (min-width: 1024px)","@xl":"@media (min-width: 1280px)","@2xl":"@media (min-width: 1536px)"};function x(s){return f[s]??s}function W(s,e){return typeof e=="string"||typeof e=="number"||typeof e=="object"&&!Array.isArray(e)}function T(s){let e=new Map,t=new Set;return s.forEach((n,r)=>{let l=JSON.stringify(n);t.has(l)||(t.add(l),e.set(r,n))}),e}var p=class s{constructor(){this.styles=new Map}addStyle(e){let n=`ns-${m(e)}`;if(this.styles.has(n))return n;let r=this.compileStyle(e);return this.styles.set(n,r),n}addGlobalStyle(e,t){let n=`global:${e}`;if(this.styles.has(n))return;let r=this.buildDeclarations(t);r&&this.styles.set(n,{className:n,css:`${e} {
|
|
2
|
+
${r}}`,mediaQueries:{},pseudoClasses:{},keyframes:"",supports:{},layers:{}})}compileStyle(e){let{mediaQueries:t,pseudoClasses:n,normalStyles:r,keyframes:l,supports:h,container:u,layer:S}=this.parseStyles(e),o=`ns-${m(e)}`,P=this.buildDeclarations(r),I=`.${o} {
|
|
3
|
+
${P}}`,y={};Object.entries(t).forEach(([c,d])=>{let a=x(c),C=this.buildDeclarations(d," ");if(y[a]){let B=y[a].split(`
|
|
4
4
|
`).slice(2,-2).join(`
|
|
5
|
-
`);
|
|
5
|
+
`);y[a]=`${a} {
|
|
6
6
|
.${o} {
|
|
7
|
-
${
|
|
8
|
-
${
|
|
9
|
-
}`}else
|
|
7
|
+
${B}
|
|
8
|
+
${C} }
|
|
9
|
+
}`}else y[a]=`${a} {
|
|
10
10
|
.${o} {
|
|
11
|
-
${
|
|
12
|
-
}`}),Object.entries(
|
|
11
|
+
${C} }
|
|
12
|
+
}`}),Object.entries(u).forEach(([c,d])=>{let a=this.buildDeclarations(d," ");y[c]=`${c} {
|
|
13
13
|
.${o} {
|
|
14
14
|
${a} }
|
|
15
|
-
}`});let
|
|
16
|
-
${a}}`});let
|
|
17
|
-
`,Object.entries(d).forEach(([a,
|
|
18
|
-
${
|
|
19
|
-
`}),
|
|
20
|
-
`});let
|
|
15
|
+
}`});let w={};Object.entries(n).forEach(([c,d])=>{let a=this.buildDeclarations(d);w[c]=`.${o}${c} {
|
|
16
|
+
${a}}`});let b="";Object.entries(l).forEach(([c,d])=>{b+=`@keyframes ${c} {
|
|
17
|
+
`,Object.entries(d).forEach(([a,C])=>{let D=this.buildDeclarations(C," ");b+=` ${a} {
|
|
18
|
+
${D} }
|
|
19
|
+
`}),b+=`}
|
|
20
|
+
`});let M={};Object.entries(h).forEach(([c,d])=>{let a=this.buildDeclarations(d," ");M[c]=`@supports ${c} {
|
|
21
21
|
.${o} {
|
|
22
22
|
${a} }
|
|
23
|
-
}`});let
|
|
24
|
-
${a}}`}),{className:o,css:
|
|
25
|
-
`)}),
|
|
26
|
-
`),
|
|
27
|
-
`),Object.values(
|
|
28
|
-
`}),Object.values(
|
|
29
|
-
`}),Object.values(
|
|
30
|
-
`}),Object.entries(
|
|
31
|
-
`})}),e}extractMinWidth(e){let
|
|
23
|
+
}`});let A={};return Object.entries(S).forEach(([c,d])=>{let a=this.buildDeclarations(d," ");A[c]=`@layer ${c} {
|
|
24
|
+
${a}}`}),{className:o,css:I,mediaQueries:y,pseudoClasses:w,keyframes:b,supports:M,layers:A}}parseStyles(e){let t={},n={},r={},l={},h={},u={},S={};return Object.entries(e).forEach(([i,o])=>{if(i.startsWith("@keyframes "))l[i.slice(11)]=o;else if(i==="@keyframes"&&typeof o=="object")Object.assign(l,o);else if(i.startsWith("@supports")){let P=i.slice(0,9)==="@supports"?i.slice(9).trim():i;h[P]=o}else i.startsWith("@container")?u[i]=o:i.startsWith("@layer")?S[i.slice(7)||"default"]=o:i in f||i.startsWith("@media")?n[i]=o:i.startsWith(":")||i.startsWith("::")?r[i]=o:t[i]=o}),{normalStyles:t,mediaQueries:n,pseudoClasses:r,keyframes:l,supports:h,container:u,layer:S}}buildDeclarations(e,t=" "){let n="";return Object.entries(e).forEach(([r,l])=>{(typeof l=="string"||typeof l=="number")&&(n+=`${t}${R(r)}: ${l};
|
|
25
|
+
`)}),n}getAllStyles(){let e="";return this.styles.forEach(t=>{t.keyframes&&(e+=`${t.keyframes}
|
|
26
|
+
`),t.css&&(e+=`${t.css}
|
|
27
|
+
`),Object.values(t.pseudoClasses).forEach(r=>{e+=`${r}
|
|
28
|
+
`}),Object.values(t.layers).forEach(r=>{e+=`${r}
|
|
29
|
+
`}),Object.values(t.supports).forEach(r=>{e+=`${r}
|
|
30
|
+
`}),Object.entries(t.mediaQueries).sort(([r],[l])=>this.extractMinWidth(r)-this.extractMinWidth(l)).forEach(([,r])=>{e+=`${r}
|
|
31
|
+
`})}),e}extractMinWidth(e){let t=e.match(/min-width:\s*(\d+)px/);return t?parseInt(t[1],10):0}flush(e){try{let t=e??s.defaultCacheFile();j.default.mkdirSync(E.default.dirname(t),{recursive:!0}),j.default.writeFileSync(t,this.getAllStyles(),"utf-8")}catch(t){console.error("Failed to flush styles to cache file:",t)}}static defaultCacheFile(){return E.default.join(process.cwd(),"node_modules",".cache","next-style","styles.css")}getStyleMap(){return new Map(this.styles)}};function K(){let s=new p;return{collector:s,transformCssCall(e){return s.addStyle(e)}}}function z(s){let e=g.addStyle(s);return g.flush(),e}function F(s){Object.entries(s).forEach(([e,t])=>{g.addGlobalStyle(e,t)}),g.flush()}var g=new p;0&&(module.exports={BREAKPOINTS,StyleCollector,camelToKebab,createTransformer,css,deduplicateStyles,generateClassHash,global,normalizeMediaQuery,styleCollector,validateCSSProperty});
|