@pyreon/styler 0.11.1 → 0.11.2
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/package.json +6 -5
- package/src/ThemeProvider.ts +37 -0
- package/src/__tests__/ThemeProvider.test.ts +67 -0
- package/src/__tests__/benchmark.bench.ts +189 -0
- package/src/__tests__/composition-chain.test.ts +489 -0
- package/src/__tests__/css.test.ts +70 -0
- package/src/__tests__/forward.test.ts +282 -0
- package/src/__tests__/globalStyle.test.ts +72 -0
- package/src/__tests__/hash.test.ts +70 -0
- package/src/__tests__/hybrid-injection.test.ts +205 -0
- package/src/__tests__/index.ts +14 -0
- package/src/__tests__/insertion-effect.test.ts +106 -0
- package/src/__tests__/integration.test.ts +149 -0
- package/src/__tests__/keyframes.test.ts +68 -0
- package/src/__tests__/memory-growth.test.ts +152 -0
- package/src/__tests__/p3-features.test.ts +258 -0
- package/src/__tests__/resolve.test.ts +249 -0
- package/src/__tests__/shared.test.ts +73 -0
- package/src/__tests__/sheet-advanced.test.ts +669 -0
- package/src/__tests__/sheet-split-atrules.test.ts +411 -0
- package/src/__tests__/sheet.test.ts +164 -0
- package/src/__tests__/styled-ssr.test.ts +67 -0
- package/src/__tests__/styled.test.ts +303 -0
- package/src/__tests__/theme.test.ts +33 -0
- package/src/__tests__/useCSS.test.ts +142 -0
- package/src/css.ts +13 -0
- package/src/forward.ts +276 -0
- package/src/globalStyle.ts +48 -0
- package/src/hash.ts +30 -0
- package/src/index.ts +15 -0
- package/src/keyframes.ts +36 -0
- package/src/resolve.ts +172 -0
- package/src/shared.ts +12 -0
- package/src/sheet.ts +387 -0
- package/src/styled.tsx +277 -0
- package/src/useCSS.ts +20 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/styler",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/pyreon/pyreon",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"!lib/**/*.map",
|
|
25
25
|
"!lib/analysis",
|
|
26
26
|
"README.md",
|
|
27
|
-
"LICENSE"
|
|
27
|
+
"LICENSE",
|
|
28
|
+
"src"
|
|
28
29
|
],
|
|
29
30
|
"engines": {
|
|
30
31
|
"node": ">= 22"
|
|
@@ -43,11 +44,11 @@
|
|
|
43
44
|
"typecheck": "tsc --noEmit"
|
|
44
45
|
},
|
|
45
46
|
"peerDependencies": {
|
|
46
|
-
"@pyreon/core": "^0.11.
|
|
47
|
-
"@pyreon/reactivity": "^0.11.
|
|
47
|
+
"@pyreon/core": "^0.11.2",
|
|
48
|
+
"@pyreon/reactivity": "^0.11.2"
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
50
51
|
"@vitus-labs/tools-rolldown": "^1.15.3",
|
|
51
|
-
"@pyreon/typescript": "^0.11.
|
|
52
|
+
"@pyreon/typescript": "^0.11.2"
|
|
52
53
|
}
|
|
53
54
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme context for styled components.
|
|
3
|
+
*
|
|
4
|
+
* Extensible theme interface. Consumers can augment this via module
|
|
5
|
+
* declaration merging for full strict types:
|
|
6
|
+
*
|
|
7
|
+
* declare module '@pyreon/styler' {
|
|
8
|
+
* interface DefaultTheme {
|
|
9
|
+
* colors: { primary: string; secondary: string }
|
|
10
|
+
* spacing: (n: number) => string
|
|
11
|
+
* }
|
|
12
|
+
* }
|
|
13
|
+
*/
|
|
14
|
+
import type { VNode, VNodeChild } from "@pyreon/core"
|
|
15
|
+
import { createContext, provide, useContext } from "@pyreon/core"
|
|
16
|
+
|
|
17
|
+
// biome-ignore lint/suspicious/noEmptyInterface: augmentable via module declaration merging
|
|
18
|
+
export interface DefaultTheme {}
|
|
19
|
+
|
|
20
|
+
type Theme = DefaultTheme & Record<string, unknown>
|
|
21
|
+
|
|
22
|
+
export const ThemeContext = createContext<Theme>({} as Theme)
|
|
23
|
+
|
|
24
|
+
/** Hook to read the current theme from the nearest ThemeProvider. */
|
|
25
|
+
export const useTheme = <T extends object = Theme>(): T => useContext(ThemeContext) as T
|
|
26
|
+
|
|
27
|
+
/** Provides a theme object to all nested styled components via Pyreon context. */
|
|
28
|
+
export function ThemeProvider({
|
|
29
|
+
theme,
|
|
30
|
+
children,
|
|
31
|
+
}: {
|
|
32
|
+
theme: Theme
|
|
33
|
+
children?: VNodeChild
|
|
34
|
+
}): VNode | null {
|
|
35
|
+
provide(ThemeContext, theme)
|
|
36
|
+
return (children ?? null) as VNode | null
|
|
37
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { popContext } from "@pyreon/core"
|
|
2
|
+
import { afterEach, describe, expect, it } from "vitest"
|
|
3
|
+
import { ThemeContext, ThemeProvider, useTheme } from "../ThemeProvider"
|
|
4
|
+
|
|
5
|
+
describe("ThemeContext", () => {
|
|
6
|
+
it("is a Context object with an id", () => {
|
|
7
|
+
expect(ThemeContext).toBeDefined()
|
|
8
|
+
expect(ThemeContext.id).toBeDefined()
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it("has a symbol id for context identification", () => {
|
|
12
|
+
expect(typeof ThemeContext.id).toBe("symbol")
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
describe("ThemeProvider", () => {
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
try {
|
|
19
|
+
popContext()
|
|
20
|
+
} catch {
|
|
21
|
+
// Ignore if no context was pushed
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it("returns children when provided", () => {
|
|
26
|
+
const children = "Hello world"
|
|
27
|
+
const result = ThemeProvider({ theme: {}, children })
|
|
28
|
+
expect(result).toBe("Hello world")
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it("returns null when no children are provided", () => {
|
|
32
|
+
const result = ThemeProvider({ theme: {} })
|
|
33
|
+
expect(result).toBeNull()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it("returns null when children is undefined", () => {
|
|
37
|
+
const result = ThemeProvider({ theme: {}, children: undefined })
|
|
38
|
+
expect(result).toBeNull()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it("provides theme via context (useTheme returns it)", () => {
|
|
42
|
+
const theme = { colors: { primary: "red" }, spacing: 8 }
|
|
43
|
+
ThemeProvider({ theme, children: "child" })
|
|
44
|
+
const result = useTheme()
|
|
45
|
+
expect(result).toEqual(theme)
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
describe("useTheme", () => {
|
|
50
|
+
it("is a function", () => {
|
|
51
|
+
expect(typeof useTheme).toBe("function")
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it("returns the default theme (empty object) when called outside a provider", () => {
|
|
55
|
+
const theme = useTheme()
|
|
56
|
+
expect(theme).toEqual({})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it("can be called with a type parameter", () => {
|
|
60
|
+
interface MyTheme {
|
|
61
|
+
primary: string
|
|
62
|
+
spacing: number
|
|
63
|
+
}
|
|
64
|
+
const theme = useTheme<MyTheme>()
|
|
65
|
+
expect(theme).toBeDefined()
|
|
66
|
+
})
|
|
67
|
+
})
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Benchmark: @pyreon/styler CSS-in-JS operations
|
|
3
|
+
*
|
|
4
|
+
* Run: bun vitest bench
|
|
5
|
+
*
|
|
6
|
+
* Measures core CSS-in-JS operations:
|
|
7
|
+
* 1. css() tagged template creation
|
|
8
|
+
* 2. css() with interpolations
|
|
9
|
+
* 3. Template resolution to CSS string
|
|
10
|
+
* 4. Dynamic function interpolation
|
|
11
|
+
* 5. Hash function throughput
|
|
12
|
+
* 6. Nested css() composition
|
|
13
|
+
* 7. styled() component factory
|
|
14
|
+
* 8. normalizeCSS — Comment Stripping & Cleanup
|
|
15
|
+
*/
|
|
16
|
+
import { bench, describe } from "vitest"
|
|
17
|
+
import { css } from "../css"
|
|
18
|
+
import { hash } from "../hash"
|
|
19
|
+
import { normalizeCSS, resolve } from "../resolve"
|
|
20
|
+
import { styled } from "../styled"
|
|
21
|
+
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// 1. CSS Tagged Template — Creation Speed
|
|
24
|
+
// ============================================================================
|
|
25
|
+
describe("css() tagged template creation", () => {
|
|
26
|
+
bench("@pyreon/styler", () => {
|
|
27
|
+
css`
|
|
28
|
+
display: flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
justify-content: center;
|
|
31
|
+
padding: 16px;
|
|
32
|
+
margin: 8px;
|
|
33
|
+
background-color: #f0f0f0;
|
|
34
|
+
border-radius: 4px;
|
|
35
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
36
|
+
`
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// 2. CSS Tagged Template with Interpolations
|
|
42
|
+
// ============================================================================
|
|
43
|
+
describe("css() with interpolations", () => {
|
|
44
|
+
const color = "#ff0000"
|
|
45
|
+
const size = "16px"
|
|
46
|
+
|
|
47
|
+
bench("@pyreon/styler", () => {
|
|
48
|
+
css`
|
|
49
|
+
color: ${color};
|
|
50
|
+
font-size: ${size};
|
|
51
|
+
display: flex;
|
|
52
|
+
padding: 8px;
|
|
53
|
+
`
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// ============================================================================
|
|
58
|
+
// 3. Template Resolution (strings + values -> CSS string)
|
|
59
|
+
// ============================================================================
|
|
60
|
+
describe("template resolution to CSS string", () => {
|
|
61
|
+
const strings = Object.assign(["display: flex; color: ", "; font-size: ", "; padding: 8px;"], {
|
|
62
|
+
raw: ["display: flex; color: ", "; font-size: ", "; padding: 8px;"],
|
|
63
|
+
}) as unknown as TemplateStringsArray
|
|
64
|
+
|
|
65
|
+
const values = ["red", "16px"]
|
|
66
|
+
const props = { theme: { primary: "blue" } }
|
|
67
|
+
|
|
68
|
+
bench("@pyreon/styler resolve()", () => {
|
|
69
|
+
resolve(strings, values, props)
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
// ============================================================================
|
|
74
|
+
// 4. Dynamic Interpolation (function interpolations)
|
|
75
|
+
// ============================================================================
|
|
76
|
+
describe("dynamic function interpolation", () => {
|
|
77
|
+
const props = { theme: { primary: "blue", size: "14px" }, active: true }
|
|
78
|
+
|
|
79
|
+
const strings = Object.assign(["color: ", "; font-size: ", "; opacity: ", ";"], {
|
|
80
|
+
raw: ["color: ", "; font-size: ", "; opacity: ", ";"],
|
|
81
|
+
}) as unknown as TemplateStringsArray
|
|
82
|
+
|
|
83
|
+
const stylerValues = [
|
|
84
|
+
(p: any) => p.theme.primary,
|
|
85
|
+
(p: any) => p.theme.size,
|
|
86
|
+
(p: any) => (p.active ? "1" : "0.5"),
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
bench("@pyreon/styler resolve()", () => {
|
|
90
|
+
resolve(strings, stylerValues, props)
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
// ============================================================================
|
|
95
|
+
// 5. Hash Function Throughput
|
|
96
|
+
// ============================================================================
|
|
97
|
+
describe("hash function throughput", () => {
|
|
98
|
+
const shortCSS = "display: flex; color: red;"
|
|
99
|
+
const mediumCSS =
|
|
100
|
+
"display: flex; align-items: center; justify-content: space-between; padding: 16px 24px; margin: 0 auto; max-width: 1200px; background-color: #ffffff; border: 1px solid #e0e0e0; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.12);"
|
|
101
|
+
const longCSS = mediumCSS.repeat(5)
|
|
102
|
+
|
|
103
|
+
bench("@pyreon/styler FNV-1a (short)", () => {
|
|
104
|
+
hash(shortCSS)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
bench("@pyreon/styler FNV-1a (medium)", () => {
|
|
108
|
+
hash(mediumCSS)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
bench("@pyreon/styler FNV-1a (long)", () => {
|
|
112
|
+
hash(longCSS)
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
// ============================================================================
|
|
117
|
+
// 6. Nested css() Composition
|
|
118
|
+
// ============================================================================
|
|
119
|
+
describe("nested css() composition", () => {
|
|
120
|
+
bench("@pyreon/styler", () => {
|
|
121
|
+
const base = css`display: flex; padding: 8px;`
|
|
122
|
+
const hover = css`background: #eee;`
|
|
123
|
+
const result = css`
|
|
124
|
+
${base};
|
|
125
|
+
&:hover { ${hover}; }
|
|
126
|
+
color: red;
|
|
127
|
+
`
|
|
128
|
+
result.toString()
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
// ============================================================================
|
|
133
|
+
// 7. Styled Component Creation (factory call)
|
|
134
|
+
// ============================================================================
|
|
135
|
+
describe("styled() component factory", () => {
|
|
136
|
+
bench("@pyreon/styler", () => {
|
|
137
|
+
styled("div")`display: flex; color: red; padding: 8px;`
|
|
138
|
+
})
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
// ============================================================================
|
|
142
|
+
// 8. normalizeCSS — Comment Stripping & Cleanup
|
|
143
|
+
// ============================================================================
|
|
144
|
+
describe("normalizeCSS", () => {
|
|
145
|
+
const plain =
|
|
146
|
+
" display: flex; align-items: center; justify-content: center; padding: 16px; margin: 8px; background-color: #f0f0f0; border-radius: 4px; "
|
|
147
|
+
|
|
148
|
+
const withBlockComments = `
|
|
149
|
+
/* -------------------------------------------------------- */
|
|
150
|
+
/* BASE STATE */
|
|
151
|
+
/* -------------------------------------------------------- */
|
|
152
|
+
display: flex; align-items: center; justify-content: center;
|
|
153
|
+
padding: 16px; margin: 8px; background-color: #f0f0f0;
|
|
154
|
+
/* -------------------------------------------------------- */
|
|
155
|
+
/* HOVER STATE */
|
|
156
|
+
/* -------------------------------------------------------- */
|
|
157
|
+
&:hover { color: red; background: blue; }
|
|
158
|
+
/* -------------------------------------------------------- */
|
|
159
|
+
/* ACTIVE STATE */
|
|
160
|
+
/* -------------------------------------------------------- */
|
|
161
|
+
&:active { color: green; }
|
|
162
|
+
`
|
|
163
|
+
|
|
164
|
+
const withLineComments = `
|
|
165
|
+
// base styles
|
|
166
|
+
display: flex; align-items: center;
|
|
167
|
+
// hover override
|
|
168
|
+
&:hover { color: red; }
|
|
169
|
+
background: url(https://example.com/img.png);
|
|
170
|
+
`
|
|
171
|
+
|
|
172
|
+
const withSemicolonJunk = " ; display: flex;; ; color: red; ; font-size: 1rem;; ; "
|
|
173
|
+
|
|
174
|
+
bench("plain CSS (no comments)", () => {
|
|
175
|
+
normalizeCSS(plain)
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
bench("CSS with /* */ block comments", () => {
|
|
179
|
+
normalizeCSS(withBlockComments)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
bench("CSS with // line comments", () => {
|
|
183
|
+
normalizeCSS(withLineComments)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
bench("CSS with semicolon junk", () => {
|
|
187
|
+
normalizeCSS(withSemicolonJunk)
|
|
188
|
+
})
|
|
189
|
+
})
|