@pyreon/styler 0.24.5 → 0.24.6
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 +5 -7
- package/src/ThemeProvider.ts +0 -65
- package/src/__tests__/ThemeProvider.test.ts +0 -67
- package/src/__tests__/benchmark.bench.ts +0 -200
- package/src/__tests__/composition-chain.test.ts +0 -537
- package/src/__tests__/css.test.ts +0 -70
- package/src/__tests__/dev-gate-treeshake.test.ts +0 -85
- package/src/__tests__/forward.test.ts +0 -282
- package/src/__tests__/globalStyle.test.ts +0 -72
- package/src/__tests__/hash.test.ts +0 -70
- package/src/__tests__/hybrid-injection.test.ts +0 -225
- package/src/__tests__/index.ts +0 -14
- package/src/__tests__/inject-rules.browser.test.ts +0 -40
- package/src/__tests__/insertion-effect.test.ts +0 -119
- package/src/__tests__/integration-dom.test.ts +0 -58
- package/src/__tests__/integration.test.ts +0 -179
- package/src/__tests__/keyframes.test.ts +0 -68
- package/src/__tests__/memory-growth.test.ts +0 -220
- package/src/__tests__/native-marker.test.ts +0 -9
- package/src/__tests__/p3-features.test.ts +0 -316
- package/src/__tests__/resolve-cache.test.ts +0 -94
- package/src/__tests__/resolve.test.ts +0 -308
- package/src/__tests__/shared.test.ts +0 -133
- package/src/__tests__/sheet-advanced.test.ts +0 -659
- package/src/__tests__/sheet-split-atrules.test.ts +0 -410
- package/src/__tests__/sheet.test.ts +0 -250
- package/src/__tests__/static-styler-resolve-cost.test.ts +0 -160
- package/src/__tests__/styled-reactive.test.ts +0 -74
- package/src/__tests__/styled-ssr.test.ts +0 -75
- package/src/__tests__/styled.test.ts +0 -511
- package/src/__tests__/styler.browser.test.tsx +0 -194
- package/src/__tests__/theme.test.ts +0 -33
- package/src/__tests__/useCSS.test.ts +0 -172
- package/src/css.ts +0 -13
- package/src/env.d.ts +0 -6
- package/src/forward.ts +0 -308
- package/src/globalStyle.ts +0 -53
- package/src/hash.ts +0 -28
- package/src/index.ts +0 -15
- package/src/keyframes.ts +0 -36
- package/src/manifest.ts +0 -332
- package/src/resolve.ts +0 -225
- package/src/shared.ts +0 -22
- package/src/sheet.ts +0 -635
- package/src/styled.tsx +0 -503
- package/src/tests/manifest-snapshot.test.ts +0 -51
- package/src/useCSS.ts +0 -20
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Measurement gate: per-mount cost of a fully-static `styled()` component.
|
|
3
|
-
*
|
|
4
|
-
* BACKGROUND. "Proposed compiler win #2 — static styler extraction" theorised
|
|
5
|
-
* that a fully-static styled component (`styled('div')`\`color: red\``, no
|
|
6
|
-
* function interpolations) wastes a per-mount `styler.resolve`, and that a
|
|
7
|
-
* bounded runtime memo slice could be carved out of the (multi-week,
|
|
8
|
-
* roadmap-scale) compile-time extraction effort. This gate MEASURES that
|
|
9
|
-
* premise with the real `styler.resolve` / `styler.staticVNode.hit` /
|
|
10
|
-
* `styler.sheet.insert*` perf counters and DISPROVES it for the runtime
|
|
11
|
-
* layer: the static path is already optimal.
|
|
12
|
-
*
|
|
13
|
-
* WHAT THE COUNTERS PROVE (the contrast IS the proof — self-discriminating,
|
|
14
|
-
* no fake fix to revert; same shape as the static-text baking gate):
|
|
15
|
-
*
|
|
16
|
-
* Fully-static `styled('div')`\`color: red\`` (values.length === 0):
|
|
17
|
-
* - `createStyledComponent` takes `raw = strings[0]` — `resolve()` is
|
|
18
|
-
* NEVER called → `styler.resolve` === 0 for the lifetime.
|
|
19
|
-
* - `sheet.insert` fires EXACTLY ONCE at component-creation time
|
|
20
|
-
* (definition, not mount) → `styler.sheet.insert` === 1.
|
|
21
|
-
* - Every mount with no extra props returns the pre-built
|
|
22
|
-
* `cachedEmptyVNode` → `styler.staticVNode.hit` === N, with ZERO
|
|
23
|
-
* additional resolve / sheet work per mount.
|
|
24
|
-
*
|
|
25
|
-
* Contrast — function-interpolated `styled('div')`\`color: ${p => p.c}\``
|
|
26
|
-
* with NO `$rocketstyle` / `$element` identity (the only shape that DOES
|
|
27
|
-
* re-resolve per call): `styler.resolve` === N. This is CORRECT, not waste
|
|
28
|
-
* — the CSS genuinely depends on per-call props; and real-app shapes
|
|
29
|
-
* (rocketstyle dimensions, Element `$element` interning) already hit
|
|
30
|
-
* `classCache` / `elClassCache` so they collapse to ~0 (proven elsewhere:
|
|
31
|
-
* PR #344 dimension memo, the `$element` interning gate). So #2's runtime
|
|
32
|
-
* layer needs no fix; the remaining #2 surface is purely compile-time /
|
|
33
|
-
* bundle (don't ship the styled wrapper + sheet.insert for provably-static
|
|
34
|
-
* CSS at all) — that is the roadmap item, deliberately NOT half-built here.
|
|
35
|
-
*
|
|
36
|
-
* Bisect note: there is intentionally no fix to revert. The discriminating
|
|
37
|
-
* assertion is `static.resolve === 0 && dynamic.resolve === N` in the SAME
|
|
38
|
-
* run — if the static path ever regressed to per-mount resolve, the static
|
|
39
|
-
* block's `toBe(0)` fails while the dynamic block still passes, pinpointing
|
|
40
|
-
* the regression to the static fast path specifically.
|
|
41
|
-
*/
|
|
42
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
43
|
-
import { sheet } from '../sheet'
|
|
44
|
-
import { styled } from '../styled'
|
|
45
|
-
|
|
46
|
-
type Sink = { __pyreon_count__?: (name: string, n?: number) => void }
|
|
47
|
-
const g = globalThis as Sink
|
|
48
|
-
|
|
49
|
-
let counts: Map<string, number>
|
|
50
|
-
|
|
51
|
-
const get = (name: string): number => counts.get(name) ?? 0
|
|
52
|
-
|
|
53
|
-
beforeEach(() => {
|
|
54
|
-
counts = new Map()
|
|
55
|
-
g.__pyreon_count__ = (name: string, n = 1) => {
|
|
56
|
-
counts.set(name, (counts.get(name) ?? 0) + n)
|
|
57
|
-
}
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
afterEach(() => {
|
|
61
|
-
delete g.__pyreon_count__
|
|
62
|
-
// clearAll (not reset) — fires onSheetClear so styled.tsx's
|
|
63
|
-
// staticComponentCache / _hotCache reset between cases; otherwise the
|
|
64
|
-
// single-entry hot cache leaks a prior case's component.
|
|
65
|
-
sheet.clearAll()
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
const N = 100
|
|
69
|
-
|
|
70
|
-
describe('static styled() per-mount cost (measurement gate)', () => {
|
|
71
|
-
it('a fully-static component resolves ZERO times and inserts ONCE, regardless of mount count', () => {
|
|
72
|
-
// Distinct source-location template literal → its own
|
|
73
|
-
// TemplateStringsArray identity (independent of every other case).
|
|
74
|
-
const Comp = styled('div')`
|
|
75
|
-
color: red;
|
|
76
|
-
padding: 4px;
|
|
77
|
-
`
|
|
78
|
-
|
|
79
|
-
// The sheet.insert at *definition* time already happened above. Snapshot
|
|
80
|
-
// AFTER definition so the per-mount measurement is isolated from the
|
|
81
|
-
// one-time creation cost.
|
|
82
|
-
const insertsAtDefinition = get('styler.sheet.insert')
|
|
83
|
-
expect(insertsAtDefinition).toBe(1) // exactly one — at creation, not mount
|
|
84
|
-
expect(get('styler.resolve')).toBe(0) // values.length===0 → raw=strings[0], no resolve()
|
|
85
|
-
|
|
86
|
-
for (let i = 0; i < N; i++) Comp({})
|
|
87
|
-
|
|
88
|
-
// THE PROOF: N mounts added ZERO resolves and ZERO new sheet inserts.
|
|
89
|
-
expect(get('styler.resolve')).toBe(0)
|
|
90
|
-
expect(get('styler.sheet.insert')).toBe(1) // still just the creation insert
|
|
91
|
-
expect(get('styler.sheet.insert.hit')).toBe(0) // never re-inserted
|
|
92
|
-
// Every mount took the pre-built cachedEmptyVNode fast path.
|
|
93
|
-
expect(get('styler.staticVNode.hit')).toBe(N)
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
it('two distinct static components: 2 inserts total, 0 resolves, N/2 hits each', () => {
|
|
97
|
-
const A = styled('div')`
|
|
98
|
-
color: blue;
|
|
99
|
-
`
|
|
100
|
-
const B = styled('span')`
|
|
101
|
-
color: green;
|
|
102
|
-
`
|
|
103
|
-
expect(get('styler.sheet.insert')).toBe(2) // one per definition
|
|
104
|
-
expect(get('styler.resolve')).toBe(0)
|
|
105
|
-
|
|
106
|
-
for (let i = 0; i < N / 2; i++) {
|
|
107
|
-
A({})
|
|
108
|
-
B({})
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
expect(get('styler.resolve')).toBe(0)
|
|
112
|
-
expect(get('styler.sheet.insert')).toBe(2)
|
|
113
|
-
expect(get('styler.staticVNode.hit')).toBe(N) // N/2 + N/2
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
it('CONTRAST — a function-interpolated styled with no rocketstyle/$element identity DOES resolve per call (correct, not waste)', () => {
|
|
117
|
-
const Dyn = styled('div')<{ c: string }>`
|
|
118
|
-
color: ${(p) => p.c};
|
|
119
|
-
`
|
|
120
|
-
// No object $rocketstyle / $rocketstate / $element on rawProps → neither
|
|
121
|
-
// classCache nor elClassCache can fire; doResolve() runs every call.
|
|
122
|
-
// Same prop value each call → cssText identical → sheet dedups (hit).
|
|
123
|
-
for (let i = 0; i < N; i++) Dyn({ c: 'red' })
|
|
124
|
-
|
|
125
|
-
expect(get('styler.resolve')).toBe(N) // genuinely per-call (CSS depends on props)
|
|
126
|
-
expect(get('styler.staticVNode.hit')).toBe(0) // never the static fast path
|
|
127
|
-
// `styler.sheet.insert` counts every insert() CALL (one per mount here);
|
|
128
|
-
// `.hit` is the dedup subset — identical cssText each call, so all but
|
|
129
|
-
// the first hit the insertCache and inject NO new DOM rule. The dynamic
|
|
130
|
-
// path still pays the resolve() + the insert() call-overhead per mount;
|
|
131
|
-
// only the actual rule injection is deduped. That call-overhead is the
|
|
132
|
-
// residual the rocketstyle/$element identity caches (classCache /
|
|
133
|
-
// elClassCache) eliminate in real-app shapes — proven elsewhere.
|
|
134
|
-
expect(get('styler.sheet.insert')).toBe(N) // one insert() call per mount
|
|
135
|
-
expect(get('styler.sheet.insert.hit')).toBe(N - 1) // first builds cache, rest dedup
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
it('SELF-DISCRIMINATING — static.resolve===0 AND dynamic.resolve===N in one run', () => {
|
|
139
|
-
const Static = styled('div')`
|
|
140
|
-
margin: 8px;
|
|
141
|
-
`
|
|
142
|
-
counts = new Map() // isolate from the definition insert
|
|
143
|
-
for (let i = 0; i < N; i++) Static({})
|
|
144
|
-
const staticResolve = get('styler.resolve')
|
|
145
|
-
|
|
146
|
-
const Dyn = styled('div')<{ c: string }>`
|
|
147
|
-
color: ${(p) => p.c};
|
|
148
|
-
`
|
|
149
|
-
counts = new Map()
|
|
150
|
-
for (let i = 0; i < N; i++) Dyn({ c: 'blue' })
|
|
151
|
-
const dynResolve = get('styler.resolve')
|
|
152
|
-
|
|
153
|
-
// The discriminator: if the static fast path ever regressed to
|
|
154
|
-
// per-mount resolve, this is the assertion that pinpoints it — the
|
|
155
|
-
// static side moves off 0 while the dynamic side stays at N.
|
|
156
|
-
expect(staticResolve).toBe(0)
|
|
157
|
-
expect(dynResolve).toBe(N)
|
|
158
|
-
expect(dynResolve - staticResolve).toBe(N)
|
|
159
|
-
})
|
|
160
|
-
})
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import type { VNode } from '@pyreon/core'
|
|
2
|
-
import { pushContext } from '@pyreon/core'
|
|
3
|
-
import { signal } from '@pyreon/reactivity'
|
|
4
|
-
import { afterEach, describe, expect, it } from 'vitest'
|
|
5
|
-
import { sheet } from '../sheet'
|
|
6
|
-
import { styled } from '../styled'
|
|
7
|
-
import { ThemeContext } from '../ThemeProvider'
|
|
8
|
-
|
|
9
|
-
describe('DynamicStyled reactive class path', () => {
|
|
10
|
-
afterEach(() => {
|
|
11
|
-
sheet.reset()
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
function withTheme(fn: () => void) {
|
|
15
|
-
// Push a reactive ThemeContext so useThemeAccessor() works inside Comp
|
|
16
|
-
pushContext(new Map([[ThemeContext.id, () => ({}) as any]]))
|
|
17
|
-
fn()
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
it('creates a VNode with class when $rocketstyle is a function accessor', () => {
|
|
21
|
-
const modeSig = signal<'light' | 'dark'>('light')
|
|
22
|
-
const rsAccessor = () => ({
|
|
23
|
-
color: modeSig() === 'light' ? 'red' : 'blue',
|
|
24
|
-
})
|
|
25
|
-
const rsStateAccessor = () => ({ state: 'default' })
|
|
26
|
-
|
|
27
|
-
const Comp = styled('div')`
|
|
28
|
-
color: ${(p: any) => p.$rocketstyle?.color ?? 'black'};
|
|
29
|
-
`
|
|
30
|
-
|
|
31
|
-
let vnode: VNode | null = null
|
|
32
|
-
withTheme(() => {
|
|
33
|
-
vnode = Comp({ $rocketstyle: rsAccessor, $rocketstate: rsStateAccessor }) as VNode
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
expect(vnode).toBeTruthy()
|
|
37
|
-
// The VNode has a class from initial resolve
|
|
38
|
-
expect(vnode!.props.class).toBeTruthy()
|
|
39
|
-
expect(typeof vnode!.props.class).toBe('string')
|
|
40
|
-
expect(vnode!.props.class).toContain('pyr-')
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('static $rocketstyle (plain object) also resolves correctly', () => {
|
|
44
|
-
const Comp = styled('div')`
|
|
45
|
-
color: ${(p: any) => p.$rocketstyle?.color ?? 'black'};
|
|
46
|
-
`
|
|
47
|
-
|
|
48
|
-
let vnode: VNode | null = null
|
|
49
|
-
withTheme(() => {
|
|
50
|
-
vnode = Comp({ $rocketstyle: { color: 'green' }, $rocketstate: { state: 'x' } }) as VNode
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
expect(vnode).toBeTruthy()
|
|
54
|
-
expect(vnode!.props.class).toBeTruthy()
|
|
55
|
-
expect(typeof vnode!.props.class).toBe('string')
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
it('ref callback is wired when reactive path is active', () => {
|
|
59
|
-
const rsAccessor = () => ({ color: 'red' })
|
|
60
|
-
const rsStateAccessor = () => ({ state: 'default' })
|
|
61
|
-
|
|
62
|
-
const Comp = styled('div')`
|
|
63
|
-
color: ${(p: any) => p.$rocketstyle?.color};
|
|
64
|
-
`
|
|
65
|
-
|
|
66
|
-
let vnode: VNode | null = null
|
|
67
|
-
withTheme(() => {
|
|
68
|
-
vnode = Comp({ $rocketstyle: rsAccessor, $rocketstate: rsStateAccessor }) as VNode
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
// The reactive path wires a ref callback for classList mutation
|
|
72
|
-
expect(typeof vnode!.props.ref).toBe('function')
|
|
73
|
-
})
|
|
74
|
-
})
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* SSR tests for styled() and createGlobalStyle(). Re-imports modules
|
|
5
|
-
* with `document` deleted so IS_SERVER evaluates to true.
|
|
6
|
-
*/
|
|
7
|
-
describe('styled -- SSR mode', () => {
|
|
8
|
-
let originalDocument: typeof document
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
vi.resetModules()
|
|
12
|
-
originalDocument = globalThis.document
|
|
13
|
-
// @ts-expect-error - intentionally deleting for SSR simulation
|
|
14
|
-
delete globalThis.document
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
afterEach(() => {
|
|
18
|
-
globalThis.document = originalDocument
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it('static: creates component with SSR injection path', async () => {
|
|
22
|
-
const { styled } = await import('../styled')
|
|
23
|
-
const Comp = styled('div')`
|
|
24
|
-
color: red;
|
|
25
|
-
`
|
|
26
|
-
expect((Comp as any).displayName).toBe('styled(div)')
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it('static: empty CSS template in SSR', async () => {
|
|
30
|
-
const { styled } = await import('../styled')
|
|
31
|
-
const Comp = styled('div')``
|
|
32
|
-
expect((Comp as any).displayName).toBe('styled(div)')
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
it('static: boost option in SSR', async () => {
|
|
36
|
-
const { styled } = await import('../styled')
|
|
37
|
-
const Comp = styled('div', { layer: 'rocketstyle' })`
|
|
38
|
-
color: blue;
|
|
39
|
-
`
|
|
40
|
-
expect((Comp as any).displayName).toBe('styled(div)')
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('static: shouldForwardProp in SSR', async () => {
|
|
44
|
-
const { styled } = await import('../styled')
|
|
45
|
-
const Comp = styled('div', {
|
|
46
|
-
shouldForwardProp: (p) => p !== 'color',
|
|
47
|
-
})`
|
|
48
|
-
display: flex;
|
|
49
|
-
`
|
|
50
|
-
expect((Comp as any).displayName).toBe('styled(div)')
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it('static: styled.div shorthand in SSR', async () => {
|
|
54
|
-
const { styled } = await import('../styled')
|
|
55
|
-
const Comp = styled.div`
|
|
56
|
-
color: green;
|
|
57
|
-
`
|
|
58
|
-
expect((Comp as any).displayName).toBe('styled(div)')
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
it('createGlobalStyle: static SSR path', async () => {
|
|
62
|
-
const { createGlobalStyle } = await import('../globalStyle')
|
|
63
|
-
const GlobalStyle = createGlobalStyle`body { margin: 0; }`
|
|
64
|
-
// Static path injects immediately and returns a function that produces null
|
|
65
|
-
const result = GlobalStyle({})
|
|
66
|
-
expect(result).toBeNull()
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
it('createGlobalStyle: empty CSS in SSR returns null', async () => {
|
|
70
|
-
const { createGlobalStyle } = await import('../globalStyle')
|
|
71
|
-
const GlobalStyle = createGlobalStyle``
|
|
72
|
-
const result = GlobalStyle({})
|
|
73
|
-
expect(result).toBeNull()
|
|
74
|
-
})
|
|
75
|
-
})
|