@tenphi/tasty 0.0.0-snapshot.27b6708 → 0.0.0-snapshot.28eb83c
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 +38 -54
- package/dist/{babel-BUQGeOXA.d.ts → babel-D6lOuSwk.d.ts} +2 -2
- package/dist/chunks/async-storage-DKK-wTD4.js.map +1 -0
- package/dist/chunks/build-config-BBThSdlo.js +45 -0
- package/dist/chunks/build-config-BBThSdlo.js.map +1 -0
- package/dist/{collector-DTahQUiV.js → chunks/collector-DcViW2ZQ.js} +28 -14
- package/dist/chunks/collector-DcViW2ZQ.js.map +1 -0
- package/dist/chunks/config-engine-DQVQK4NU.js +403 -0
- package/dist/chunks/config-engine-DQVQK4NU.js.map +1 -0
- package/dist/chunks/css-definitions-BZ9vm0ci.js +1284 -0
- package/dist/chunks/css-definitions-BZ9vm0ci.js.map +1 -0
- package/dist/chunks/css-resources-Cyl_axbI.js +149 -0
- package/dist/chunks/css-resources-Cyl_axbI.js.map +1 -0
- package/dist/chunks/debug-Vyauml7U.js +583 -0
- package/dist/chunks/debug-Vyauml7U.js.map +1 -0
- package/dist/chunks/dsl-CXvoaLnj.js +2097 -0
- package/dist/chunks/dsl-CXvoaLnj.js.map +1 -0
- package/dist/chunks/hydration-CLdVKcH2.js +59 -0
- package/dist/chunks/hydration-CLdVKcH2.js.map +1 -0
- package/dist/chunks/react-runtime-so_X_yMo.js +1526 -0
- package/dist/chunks/react-runtime-so_X_yMo.js.map +1 -0
- package/dist/chunks/runtime-engine-DQ5laRou.js +3056 -0
- package/dist/chunks/runtime-engine-DQ5laRou.js.map +1 -0
- package/dist/{merge-styles-oklji0KB.js → chunks/shared-utils-OIg0N_dp.js} +42 -3
- package/dist/chunks/shared-utils-OIg0N_dp.js.map +1 -0
- package/dist/{config-B5kHzuNz.js → chunks/style-engine-CBuOt1e4.js} +2387 -7442
- package/dist/chunks/style-engine-CBuOt1e4.js.map +1 -0
- package/dist/{css-writer-B-J87ncv.js → chunks/zero-engine-BFh7gKbK.js} +3 -3
- package/dist/chunks/zero-engine-BFh7gKbK.js.map +1 -0
- package/dist/{collector-DUaHCcTS.d.ts → collector-CAmndLXe.d.ts} +23 -3
- package/dist/{config-YsxGv4tq.d.ts → config-BWYrv_Aa.d.ts} +141 -37
- package/dist/core/index.d.ts +5 -5
- package/dist/core/index.js +9 -6
- package/dist/{index-Cd45t5NM.d.ts → index-BzAC3p06.d.ts} +118 -23
- package/dist/{index-PqN-DIpn.d.ts → index-D4kRLj2o.d.ts} +128 -68
- package/dist/index.d.ts +5 -5
- package/dist/index.js +10 -922
- package/dist/{merge-styles-CU7JbEwg.d.ts → merge-styles-BFktNP8J.d.ts} +2 -2
- package/dist/ssr/astro-client.js +1 -1
- package/dist/ssr/astro-middleware-extract-static.d.ts +11 -0
- package/dist/ssr/astro-middleware-extract-static.js +9 -0
- package/dist/ssr/astro-middleware-extract-static.js.map +1 -0
- package/dist/ssr/astro-middleware-extract.d.ts +11 -0
- package/dist/ssr/astro-middleware-extract.js +9 -0
- package/dist/ssr/astro-middleware-extract.js.map +1 -0
- package/dist/ssr/astro-middleware-static.d.ts +3 -1
- package/dist/ssr/astro-middleware.d.ts +3 -1
- package/dist/ssr/astro.d.ts +45 -3
- package/dist/ssr/astro.js +157 -14
- package/dist/ssr/astro.js.map +1 -1
- package/dist/ssr/index.d.ts +7 -7
- package/dist/ssr/index.js +4 -4
- package/dist/ssr/index.js.map +1 -1
- package/dist/ssr/next-config.d.ts +66 -0
- package/dist/ssr/next-config.js +135 -0
- package/dist/ssr/next-config.js.map +1 -0
- package/dist/ssr/next.d.ts +9 -2
- package/dist/ssr/next.js +26 -10
- package/dist/ssr/next.js.map +1 -1
- package/dist/static/index.d.ts +2 -2
- package/dist/static/index.js +1 -1
- package/dist/zero/babel.d.ts +1 -1
- package/dist/zero/babel.js +25 -24
- package/dist/zero/babel.js.map +1 -1
- package/dist/zero/index.d.ts +1 -1
- package/dist/zero/index.js +1 -1
- package/dist/zero/next.d.ts +1 -1
- package/docs/README.md +5 -3
- package/docs/adoption.md +3 -3
- package/docs/ai-agents.md +83 -78
- package/docs/comparison.md +12 -12
- package/docs/configuration.md +47 -47
- package/docs/debug.md +19 -7
- package/docs/dsl.md +3 -3
- package/docs/getting-started.md +10 -10
- package/docs/injector.md +62 -25
- package/docs/methodology.md +3 -3
- package/docs/migration-v3.md +49 -49
- package/docs/plugins.md +37 -33
- package/docs/react-api.md +2 -2
- package/docs/runtime-benchmarks.md +351 -0
- package/docs/ssr.md +219 -61
- package/docs/styles.md +43 -11
- package/docs/tasty-static.md +136 -103
- package/package.json +80 -11
- package/dist/async-storage-DKK-wTD4.js.map +0 -1
- package/dist/collector-DTahQUiV.js.map +0 -1
- package/dist/config-B5kHzuNz.js.map +0 -1
- package/dist/context-CA8YKeMn.js +0 -24
- package/dist/context-CA8YKeMn.js.map +0 -1
- package/dist/core-Dr4u1NVD.js +0 -1566
- package/dist/core-Dr4u1NVD.js.map +0 -1
- package/dist/css-writer-B-J87ncv.js.map +0 -1
- package/dist/format-global-rules-DklyaXv-.js +0 -22
- package/dist/format-global-rules-DklyaXv-.js.map +0 -1
- package/dist/format-rules-DKOA-6qu.js +0 -130
- package/dist/format-rules-DKOA-6qu.js.map +0 -1
- package/dist/hydrate-OeMX99We.js +0 -37
- package/dist/hydrate-OeMX99We.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/keyframes-CV8azJf3.js +0 -493
- package/dist/keyframes-CV8azJf3.js.map +0 -1
- package/dist/merge-styles-oklji0KB.js.map +0 -1
- package/dist/resolve-recipes-DTG81rzl.js +0 -144
- package/dist/resolve-recipes-DTG81rzl.js.map +0 -1
- /package/dist/{async-storage-DKK-wTD4.js → chunks/async-storage-DKK-wTD4.js} +0 -0
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ Later branches have higher priority. If this button is both hovered and disabled
|
|
|
39
39
|
|
|
40
40
|
That is the core promise: add states, variants, themes, and overrides without reopening selector logic by hand.
|
|
41
41
|
|
|
42
|
-
Tasty fits best when you are building a design system or component library whose components need to stay predictable as their state logic grows. Alongside state maps, it provides a CSS-like DSL, typed style props, tokens, recipes, sub-elements, and
|
|
42
|
+
Tasty fits best when you are building a design system or component library whose components need to stay predictable as their state logic grows. Alongside state maps, it provides a CSS-like DSL, typed style props, tokens, recipes, sub-elements, and client-side, server-only, or build-time CSS delivery.
|
|
43
43
|
|
|
44
44
|
- **Ready to try it?** Follow [Getting Started](docs/getting-started.md).
|
|
45
45
|
- **Evaluating fit?** Read [Comparison](docs/comparison.md) and the [Adoption Guide](docs/adoption.md).
|
|
@@ -53,14 +53,14 @@ Tasty fits best when you are building a design system or component library whose
|
|
|
53
53
|
- **Built for component systems** — Model roots and sub-elements together and expose governed, typed component APIs.
|
|
54
54
|
- **Your design system’s language** — Define tokens, units, aliases, recipes, style props, and parser rules for your system.
|
|
55
55
|
- **One model for every state** — Pseudo-classes, modifiers, root and parent states, media and container queries, `:has()`, and `@supports` all use state maps.
|
|
56
|
-
- **
|
|
56
|
+
- **Client, server, or build time** — Use `tasty()` in client React, React Server Components, SSR, and static server rendering, or use `tastyStatic()` for framework-agnostic build-time extraction.
|
|
57
57
|
|
|
58
58
|
### Supporting capabilities
|
|
59
59
|
|
|
60
60
|
- **Typed style props and mod props** — `styleProps` exposes selected CSS properties as typed React props (`<Space flow="row" gap="2x">`); `modProps` does the same for modifier keys (`<Button isLoading size="large">`). Both support state maps and full TypeScript autocomplete. See [Style Props](#style-props) and [Mod Props](#mod-props).
|
|
61
|
-
- **
|
|
61
|
+
- **Zero client styling runtime with the full React API** — All `tasty()` components and style functions are hook-free. In server-only rendering, they generate CSS on the server and ship no Tasty styling runtime to the browser. Astro without islands is the concrete integration; server-only Next.js RSC follows the same architecture. Use `tastyStatic()` when you specifically need build-time extraction without React.
|
|
62
62
|
- **Broad modern CSS coverage** — Media queries, container queries, `@supports`, `:has()`, `@starting-style`, `@property`, `@keyframes`, `@font-face`, `@counter-style`, `@function`, and more. Features that do not fit the component model (such as `@layer` and `!important`) are intentionally left out.
|
|
63
|
-
- **Performance and caching** —
|
|
63
|
+
- **Performance and caching** — When styles are generated in the browser, Tasty injects CSS on demand, reuses chunks aggressively, and relies on multi-level caching so large component systems stay practical.
|
|
64
64
|
- **TypeScript-first and AI-friendly** — Style definitions are declarative, structurally consistent, and fully typed, which helps both humans and tooling understand advanced stateful styles without hidden cascade logic.
|
|
65
65
|
|
|
66
66
|
## Why State Maps Matter
|
|
@@ -96,7 +96,7 @@ For the fuller docs map beyond the quick routes above, start here:
|
|
|
96
96
|
- **[Adoption Guide](docs/adoption.md)** — understand who Tasty is for, where it fits, and how to introduce it incrementally
|
|
97
97
|
- **[Getting Started](docs/getting-started.md)** — the canonical onboarding path: install, first component, optional shared `configure()`, ESLint, editor tooling, and rendering mode selection
|
|
98
98
|
- **[Style rendering pipeline](docs/pipeline.md)** — see the selector model behind deterministic style resolution
|
|
99
|
-
- **[Docs Hub](docs/README.md)** — choose docs by role and task:
|
|
99
|
+
- **[Docs Hub](docs/README.md)** — choose docs by role and task: React rendering, zero-client-runtime delivery, build-time extraction, design-system authoring, internals, and debugging
|
|
100
100
|
- **[Methodology](docs/methodology.md)** — the recommended component model and public API conventions for design-system code
|
|
101
101
|
|
|
102
102
|
## Quick Start
|
|
@@ -211,15 +211,15 @@ See [Style Props](#style-props) and [Mod Props](#mod-props) below, or the full r
|
|
|
211
211
|
|
|
212
212
|
## Choose a Styling Approach
|
|
213
213
|
|
|
214
|
-
Once you understand the component model,
|
|
214
|
+
Once you understand the component model, choose where Tasty should generate CSS. Zero-runtime delivery is an outcome, not a synonym for `tastyStatic()`.
|
|
215
215
|
|
|
216
|
-
| Approach
|
|
217
|
-
|
|
|
218
|
-
| **
|
|
219
|
-
| **
|
|
220
|
-
| **
|
|
216
|
+
| Approach | Authoring API | CSS is generated | Tasty runtime in the browser | Best for |
|
|
217
|
+
| ------------------------- | ------------------------------------------- | ----------------------------------- | ---------------------------- | ------------------------------------------------------------------- |
|
|
218
|
+
| **Server-only React** | `tasty()`; optional `@tenphi/tasty/ssr/*` | During server or static rendering | None | Astro without islands, server-only RSC, SSG |
|
|
219
|
+
| **Hydrated React** | `tasty()` plus `@tenphi/tasty/ssr/*` | During SSR, then on demand in React | Only in hydrated components | Interactive React apps, Next.js client components, Astro islands |
|
|
220
|
+
| **Build-time extraction** | `tastyStatic()` from `@tenphi/tasty/static` | During the build | None in file mode | Non-React frameworks or CSS that must be extracted before rendering |
|
|
221
221
|
|
|
222
|
-
|
|
222
|
+
The first approach and `tastyStatic()`'s default file mode both ship zero Tasty styling runtime to the browser. The difference is authoring and timing: server-only `tasty()` retains `styleProps`, sub-elements, variants, and the rest of the React API, while `tastyStatic()` extracts build-time-known styles without a React dependency. (`tastyStatic()` inject mode intentionally ships a tiny CSS injector.) Astro supports the server-only path directly with `tastyIntegration({ islands: false })`; the same server-only model applies to Next.js RSC, though you should verify the output for your deployment. See [Getting Started](docs/getting-started.md#choosing-a-rendering-mode), [Build-Time Extraction](docs/tasty-static.md), and [Server-Side Rendering](docs/ssr.md).
|
|
223
223
|
|
|
224
224
|
## How It Actually Works
|
|
225
225
|
|
|
@@ -439,7 +439,7 @@ See [React API - Mod Props](docs/react-api.md#mod-props) and [Methodology - modP
|
|
|
439
439
|
|
|
440
440
|
### Variants
|
|
441
441
|
|
|
442
|
-
Variants let one component expose named visual versions without pre-generating a separate class for every possible combination.
|
|
442
|
+
Variants let one component expose named visual versions without pre-generating a separate class for every possible combination. With `tasty()`, Tasty emits only the variant CSS that is actually used, whether rendering happens on the server or in the browser.
|
|
443
443
|
|
|
444
444
|
See [React API - Variants](docs/react-api.md#variants).
|
|
445
445
|
|
|
@@ -480,7 +480,7 @@ const Card = tasty({
|
|
|
480
480
|
});
|
|
481
481
|
```
|
|
482
482
|
|
|
483
|
-
Definitions can also come from `configure({ functions })` or the `useFunction` style function, and work across client, SSR/RSC, and
|
|
483
|
+
Definitions can also come from `configure({ functions })` or the `useFunction` style function, and work across client rendering, SSR/RSC, and build-time extraction. `@function` is an experimental CSS feature, so for browsers that do not ship it yet enable `configure({ polyfills: { functions: true } })` to inline every call into plain CSS at parse time.
|
|
484
484
|
|
|
485
485
|
See [Style DSL - Functions](docs/dsl.md#functions-function) and [Configuration - Polyfills](docs/configuration.md#polyfills).
|
|
486
486
|
|
|
@@ -490,21 +490,24 @@ When you do not need a full component wrapper, use the style functions directly:
|
|
|
490
490
|
|
|
491
491
|
See [React API - Style Functions](docs/react-api.md#style-functions).
|
|
492
492
|
|
|
493
|
-
### Zero-Runtime
|
|
493
|
+
### Zero-Runtime Delivery
|
|
494
494
|
|
|
495
|
-
|
|
495
|
+
There are two ways to ship no Tasty styling runtime to the browser:
|
|
496
496
|
|
|
497
|
-
|
|
497
|
+
- Render `tasty()` components only on the server, retaining the full React component API. Astro's `tastyIntegration({ islands: false })` is the clearest example.
|
|
498
|
+
- Use `tastyStatic()` to extract build-time-known CSS without React.
|
|
499
|
+
|
|
500
|
+
See [Server-Side Rendering](docs/ssr.md), [Build-Time Extraction (`tastyStatic`)](docs/tasty-static.md), and [Getting Started - Choosing a rendering mode](docs/getting-started.md#choosing-a-rendering-mode).
|
|
498
501
|
|
|
499
502
|
### `tasty` vs `tastyStatic`
|
|
500
503
|
|
|
501
|
-
`tasty()` returns React components that compute CSS during rendering.
|
|
504
|
+
`tasty()` returns React components that compute CSS during rendering. That render can happen in the browser or only on the server; the server-only path ships no Tasty styling runtime while retaining the full feature set. `tastyStatic()` returns class names and extracts CSS during the build via a Babel plugin, with no React dependency. Both share the same DSL, tokens, units, state mappings, and recipes. Use `tasty()` as the default for React; use `tastyStatic()` when extraction must happen before rendering or the consumer is not React.
|
|
502
505
|
|
|
503
|
-
See [
|
|
506
|
+
See [Build-Time Extraction (`tastyStatic`)](docs/tasty-static.md), [React API](docs/react-api.md), and [Comparison - Where styles are computed](docs/comparison.md#where-styles-are-computed).
|
|
504
507
|
|
|
505
508
|
### Server-Side Rendering
|
|
506
509
|
|
|
507
|
-
`tasty()` components already work on the server without any SSR integration — they are hook-free and render as React Server Components by default. In server-only contexts
|
|
510
|
+
`tasty()` components already work on the server without any SSR integration — they are hook-free and render as React Server Components by default. In server-only contexts, they ship no Tasty styling runtime while retaining the full feature set. Astro without islands is the explicit zero-client-JavaScript setup; server-only Next.js RSC follows the same architecture, with output depending on the application deployment.
|
|
508
511
|
|
|
509
512
|
SSR integration (`TastyRegistry`, `tastyIntegration`) adds CSS batching, deduplication across component trees, FOUC prevention, and client cache hydration. Use it when your app also has client-side rendering:
|
|
510
513
|
|
|
@@ -518,11 +521,11 @@ See the [full SSR guide](docs/ssr.md).
|
|
|
518
521
|
|
|
519
522
|
| Import | Description | Platform |
|
|
520
523
|
| -------------------------------- | ------------------------------------------------------------------------------------------------------- | -------------- |
|
|
521
|
-
| `@tenphi/tasty` |
|
|
522
|
-
| `@tenphi/tasty/static` |
|
|
524
|
+
| `@tenphi/tasty` | React style engine (`tasty`, style functions, `configure`) | Browser / Node |
|
|
525
|
+
| `@tenphi/tasty/static` | Build-time extraction authoring API (`tastyStatic`) | Build time |
|
|
523
526
|
| `@tenphi/tasty/static/inject` | Runtime helper the Babel plugin rewrites `tastyStatic` imports to in inject mode | Browser |
|
|
524
527
|
| `@tenphi/tasty/core` | Lower-level internals (config, parser, pipeline, injector, style handlers) for tooling and advanced use | Browser / Node |
|
|
525
|
-
| `@tenphi/tasty/babel-plugin` | Babel plugin for
|
|
528
|
+
| `@tenphi/tasty/babel-plugin` | Babel plugin for build-time CSS extraction | Node |
|
|
526
529
|
| `@tenphi/tasty/zero` | Programmatic extraction API | Node |
|
|
527
530
|
| `@tenphi/tasty/zero/next` | Next.js integration wrapper | Node |
|
|
528
531
|
| `@tenphi/tasty/ssr` | Core SSR API (collector, context, hydration) | Node |
|
|
@@ -546,9 +549,9 @@ All sizes measured with [size-limit](https://github.com/ai/size-limit) — minif
|
|
|
546
549
|
|
|
547
550
|
| Entry point | Size |
|
|
548
551
|
| ------------------------------------------------- | -------- |
|
|
549
|
-
| `@tenphi/tasty` (
|
|
552
|
+
| `@tenphi/tasty` (client + server rendering) | 50.19 kB |
|
|
550
553
|
| `@tenphi/tasty/core` (runtime, no SSR) | 47.76 kB |
|
|
551
|
-
| `@tenphi/tasty/static` (
|
|
554
|
+
| `@tenphi/tasty/static` (build-time authoring API) | 16.43 kB |
|
|
552
555
|
| `@tenphi/tasty/zero` (programmatic extraction) | 29.6 kB |
|
|
553
556
|
| `@tenphi/tasty/babel-plugin` (Babel plugin entry) | 43.7 kB |
|
|
554
557
|
|
|
@@ -556,31 +559,12 @@ Run `pnpm size` to reproduce (outputs may shift slightly with releases).
|
|
|
556
559
|
|
|
557
560
|
### Runtime Benchmarks
|
|
558
561
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
| `renderStyles` — same styles (cached) | ~5,800,000 | ~0.17 us |
|
|
566
|
-
| `parseStateKey` — simple key like `:hover` (cold) | ~790,000 | ~1.3 us |
|
|
567
|
-
| `parseStateKey` — complex OR/AND/NOT key (cold) | ~140,000 | ~7 us |
|
|
568
|
-
| `parseStateKey` — any key (cached) | ~3,400,000–8,300,000 | ~0.1–0.3 us |
|
|
569
|
-
| `parseStyle` — value tokens like `2x 4x` (cold) | ~344,000 | ~2.9 us |
|
|
570
|
-
| `parseStyle` — color tokens (cold) | ~567,000 | ~1.8 us |
|
|
571
|
-
| `parseStyle` — any value (cached) | ~15,250,000 | ~0.07 us |
|
|
572
|
-
|
|
573
|
-
"Cold" benchmarks use unique inputs to bypass all caches. Cached benchmarks reuse a single input and measure the LRU hot path. Expect roughly ±10% between runs on an otherwise idle machine.
|
|
574
|
-
|
|
575
|
-
Run `pnpm bench` to reproduce. `pnpm bench:browser` runs the component-render benchmarks separately, in headless Chromium — those are useful for comparing variants against each other (a plain element vs. one with sub-elements), but the browser's 0.1 ms timer resolution makes their absolute values too coarse to quote. The two suites are deliberately separate commands: running them together lets the browser compete with Node for CPU and depresses both.
|
|
576
|
-
|
|
577
|
-
#### What This Means in Practice
|
|
578
|
-
|
|
579
|
-
- **Cached path dominates production.** After a component's first render, subsequent renders with stable styles skip the pipeline entirely (React `useMemo` + LRU cache hits at every level). All cached operations are sub-microsecond — effectively free.
|
|
580
|
-
- **Cold path is fast enough.** The heaviest cold operation — a complex state map with media queries, hover, and modifiers — takes ~54 us. Even a page with 100 unique styled components adds only ~5 ms of total style computation on first render, negligible next to React reconciliation and DOM work.
|
|
581
|
-
- **Cache multipliers are 10x–300x.** The parser sees ~30x, state-key parsing ~10x–25x, and the full pipeline ~100x on flat styles and ~300x on complex state maps — the deeper the work, the more the cache saves. This confirms the multi-level LRU architecture (parser, state-key, simplify, condition, pipeline) is delivering real value.
|
|
582
|
-
- **Comparable to lighter systems.** Emotion's `css()` is typically 5–20 us for simple styles; Tasty's cold `renderStyles` at ~17 us for 5 properties is in the same range despite doing significantly more work (state maps, design tokens, sub-elements, chunking).
|
|
583
|
-
- **On slower devices.** The benchmarks above are from an M1 Max (Geekbench 6 SC ~2,400). A mid-range consumer laptop (~1,800 SC) is roughly 1.3x slower; a mid-range phone (~1,200 SC) is roughly 2x slower; a budget phone (~700 SC) is roughly 3–4x slower. Even at 4x, the heaviest cold operation stays under 250 us and 100 unique components under 25 ms — still well within a single frame budget. The cached path remains sub-microsecond on all devices.
|
|
562
|
+
Reproducible benchmarks cover the core style pipeline, the React overhead of an
|
|
563
|
+
empty `tasty({})` wrapper, cold browser generation plus rule injection against
|
|
564
|
+
equivalent CSS already present on the page, and a representative 1,000-element
|
|
565
|
+
React update with shared styles. See
|
|
566
|
+
[Runtime Benchmarks](docs/runtime-benchmarks.md) for the methodology, exact
|
|
567
|
+
results, limitations, and commands.
|
|
584
568
|
|
|
585
569
|
### How It Stays Fast
|
|
586
570
|
|
|
@@ -590,7 +574,7 @@ Run `pnpm bench` to reproduce. `pnpm bench:browser` runs the component-render be
|
|
|
590
574
|
- Style normalization guarantees equivalent style input resolves to the same chunks, improving deduplication hit rates.
|
|
591
575
|
- A style garbage collector removes unused styles/chunks over time.
|
|
592
576
|
- A dedicated style injector minimizes DOM/style-tag overhead.
|
|
593
|
-
- Optional [batched injection](docs/configuration.md#batched-injection)
|
|
577
|
+
- Optional [batched injection](docs/configuration.md#batched-injection) keeps stylesheet writes together before layout reads, allowing the browser to resolve their effects together instead of between individual insertions.
|
|
594
578
|
- This approach is validated in enterprise-scale apps where runtime styling overhead is not noticeable in normal UI flows.
|
|
595
579
|
|
|
596
580
|
## Ecosystem
|
|
@@ -638,7 +622,7 @@ Syntax highlighting for Tasty styles in TypeScript, TSX, JavaScript, and JSX. Hi
|
|
|
638
622
|
|
|
639
623
|
### [tasty.style](https://tasty.style) ([source](https://github.com/tenphi/tasty.style))
|
|
640
624
|
|
|
641
|
-
The official Tasty documentation and landing page — itself built entirely with
|
|
625
|
+
The official Tasty documentation and landing page — itself built entirely with `tasty()`. A showcase for server-rendered Tasty with Next.js and OKHST color theming with Glaze.
|
|
642
626
|
|
|
643
627
|
### [Cube Cloud](https://cube.dev/)
|
|
644
628
|
|
|
@@ -656,7 +640,7 @@ Open-source React UI kit built on Tasty + React Aria. 100+ production components
|
|
|
656
640
|
|
|
657
641
|
Start from the docs hub if you want the shortest path to the right guide for your role or styling approach.
|
|
658
642
|
|
|
659
|
-
- **[Docs Hub](docs/README.md)** — audience-based navigation across onboarding, design-system authoring,
|
|
643
|
+
- **[Docs Hub](docs/README.md)** — audience-based navigation across onboarding, design-system authoring, client and server rendering, build-time extraction, debugging, and internals
|
|
660
644
|
|
|
661
645
|
### Start here
|
|
662
646
|
|
|
@@ -683,7 +667,7 @@ Start from the docs hub if you want the shortest path to the right guide for you
|
|
|
683
667
|
|
|
684
668
|
### Rendering modes
|
|
685
669
|
|
|
686
|
-
- **[
|
|
670
|
+
- **[Build-Time Extraction (`tastyStatic`)](docs/tasty-static.md)** — Babel plugin setup, Next.js integration, and static style patterns
|
|
687
671
|
- **[Server-Side Rendering](docs/ssr.md)** — SSR setup for Next.js, Astro, and generic frameworks: streaming support, cache hydration, and troubleshooting
|
|
688
672
|
|
|
689
673
|
### Internals
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as TastyConfig } from "./config-
|
|
1
|
+
import { t as TastyConfig } from "./config-BWYrv_Aa.js";
|
|
2
2
|
import { PluginPass } from "@babel/core";
|
|
3
3
|
|
|
4
4
|
//#region src/zero/babel-types.d.ts
|
|
@@ -80,4 +80,4 @@ interface TastyZeroBabelOptions {
|
|
|
80
80
|
declare const _default: (api: object, options: TastyZeroBabelOptions | null | undefined, dirname: string) => import("@babel/core").PluginObj<PluginPass>;
|
|
81
81
|
//#endregion
|
|
82
82
|
export { _default as n, TastyZeroConfig as r, TastyZeroBabelOptions as t };
|
|
83
|
-
//# sourceMappingURL=babel-
|
|
83
|
+
//# sourceMappingURL=babel-D6lOuSwk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"async-storage-DKK-wTD4.js","names":[],"sources":["../../src/ssr/async-storage.ts"],"sourcesContent":["/**\n * AsyncLocalStorage integration for SSR collector discovery.\n *\n * Used by Astro middleware and generic framework integrations where\n * the library cannot wrap the React tree with a context provider.\n * The middleware calls runWithCollector() around the render, and\n * useStyles() calls getSSRCollector() to find it.\n *\n * Uses globalThis to ensure the AsyncLocalStorage instance is shared\n * across module instances — frameworks like Astro may load middleware\n * and page components from separate module graphs.\n *\n * This module imports from 'node:async_hooks' — it must be excluded\n * from client bundles via the build configuration.\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\n\nimport type { ServerStyleCollector } from './collector';\n\nconst ALS_KEY = '__tasty_ssr_als__';\n\nfunction getSharedStorage(): AsyncLocalStorage<ServerStyleCollector> {\n const g = globalThis as Record<string, unknown>;\n if (!g[ALS_KEY]) {\n g[ALS_KEY] = new AsyncLocalStorage<ServerStyleCollector>();\n }\n return g[ALS_KEY] as AsyncLocalStorage<ServerStyleCollector>;\n}\n\n/**\n * Run a function with a ServerStyleCollector bound to the current\n * async context. All useStyles() calls within `fn` (and any async\n * continuations) will find this collector via getSSRCollector().\n */\nexport function runWithCollector<T>(\n collector: ServerStyleCollector,\n fn: () => T,\n): T {\n return getSharedStorage().run(collector, fn);\n}\n\n/**\n * Retrieve the ServerStyleCollector bound to the current async context.\n * Returns null when called outside of runWithCollector() or on the client.\n */\nexport function getSSRCollector(): ServerStyleCollector | null {\n const storage = getSharedStorage();\n if (typeof storage?.getStore !== 'function') return null;\n return storage.getStore() ?? null;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAoBA,MAAM,UAAU;AAEhB,SAAS,mBAA4D;CACnE,MAAM,IAAI;CACV,IAAI,CAAC,EAAE,UACL,EAAE,WAAW,IAAI,kBAAwC;CAE3D,OAAO,EAAE;AACX;;;;;;AAOA,SAAgB,iBACd,WACA,IACG;CACH,OAAO,iBAAiB,CAAC,CAAC,IAAI,WAAW,EAAE;AAC7C;;;;;AAMA,SAAgB,kBAA+C;CAC7D,MAAM,UAAU,iBAAiB;CACjC,IAAI,OAAO,SAAS,aAAa,YAAY,OAAO;CACpD,OAAO,QAAQ,SAAS,KAAK;AAC/B"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { h as resetGlobalPredefinedTokens, m as resetGlobalParseFunctions } from "./dsl-CXvoaLnj.js";
|
|
2
|
+
import { o as isDevEnv, r as resetStyleWarnings } from "./shared-utils-OIg0N_dp.js";
|
|
3
|
+
import { M as validateNamePrefix, q as resetFunctionPolyfills } from "./css-definitions-BZ9vm0ci.js";
|
|
4
|
+
import { D as resetHandlers, G as resetGlobalPolyfillsState, K as resetStylesGeneratedState, R as resetStyleChunks, U as isFunctionsPolyfillEnabled, y as clearPipelineCache } from "./style-engine-CBuOt1e4.js";
|
|
5
|
+
import { n as applyStyleConfig, r as normalizeConfig } from "./config-engine-DQVQK4NU.js";
|
|
6
|
+
//#region src/zero/config.ts
|
|
7
|
+
/** Build-time configuration without the browser injector/runtime registries. */
|
|
8
|
+
const emittedWarnings = /* @__PURE__ */ new Set();
|
|
9
|
+
const devMode = isDevEnv();
|
|
10
|
+
function warnOnce(key, message) {
|
|
11
|
+
if (devMode && !emittedWarnings.has(key)) {
|
|
12
|
+
emittedWarnings.add(key);
|
|
13
|
+
console.warn(message);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/** Resolve and apply the style-engine subset used during static extraction. */
|
|
17
|
+
function configureZero(config) {
|
|
18
|
+
if (config.namePrefix !== void 0) validateNamePrefix(config.namePrefix);
|
|
19
|
+
const normalized = normalizeConfig(config);
|
|
20
|
+
return {
|
|
21
|
+
source: config,
|
|
22
|
+
normalized,
|
|
23
|
+
functions: applyStyleConfig(config, normalized, warnOnce)
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** Reset only state that the build-time configuration path can mutate. */
|
|
27
|
+
function resetZeroConfig() {
|
|
28
|
+
resetStylesGeneratedState();
|
|
29
|
+
resetGlobalPolyfillsState();
|
|
30
|
+
resetGlobalPredefinedTokens();
|
|
31
|
+
resetGlobalParseFunctions();
|
|
32
|
+
resetFunctionPolyfills();
|
|
33
|
+
resetHandlers();
|
|
34
|
+
resetStyleChunks();
|
|
35
|
+
clearPipelineCache();
|
|
36
|
+
emittedWarnings.clear();
|
|
37
|
+
resetStyleWarnings();
|
|
38
|
+
}
|
|
39
|
+
function functionsForExtraction(config) {
|
|
40
|
+
return isFunctionsPolyfillEnabled() ? void 0 : config.functions;
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
export { functionsForExtraction as n, resetZeroConfig as r, configureZero as t };
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=build-config-BBThSdlo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-config-BBThSdlo.js","names":[],"sources":["../../src/zero/config.ts"],"sourcesContent":["/** Build-time configuration without the browser injector/runtime registries. */\n\nimport { resetStyleChunks } from '../chunks/style-chunk-map';\nimport {\n applyStyleConfig,\n normalizeConfig,\n type NormalizedConfig,\n} from '../config-normalize';\nimport {\n isFunctionsPolyfillEnabled,\n resetGlobalPolyfillsState,\n resetStylesGeneratedState,\n} from '../config-state';\nimport { resetFunctionPolyfills } from '../functions';\nimport { clearPipelineCache } from '../pipeline';\nimport { resetHandlers } from '../styles/predefined';\nimport { isDevEnv } from '../utils/is-dev-env';\nimport { validateNamePrefix } from '../utils/name-prefix';\nimport {\n resetGlobalParseFunctions,\n resetGlobalPredefinedTokens,\n} from '../utils/styles';\nimport { resetStyleWarnings } from '../utils/warnings';\n\nimport type { FunctionDefinition } from '../injector/types';\nimport type { TastyZeroConfig } from './babel-types';\n\nexport interface ResolvedZeroConfig {\n source: TastyZeroConfig;\n normalized: NormalizedConfig;\n functions: Record<string, FunctionDefinition>;\n}\n\nconst emittedWarnings = new Set<string>();\nconst devMode = isDevEnv();\n\nfunction warnOnce(key: string, message: string): void {\n if (devMode && !emittedWarnings.has(key)) {\n emittedWarnings.add(key);\n console.warn(message);\n }\n}\n\n/** Resolve and apply the style-engine subset used during static extraction. */\nexport function configureZero(config: TastyZeroConfig): ResolvedZeroConfig {\n if (config.namePrefix !== undefined) validateNamePrefix(config.namePrefix);\n\n const normalized = normalizeConfig(config);\n const functions = applyStyleConfig(config, normalized, warnOnce);\n\n return { source: config, normalized, functions };\n}\n\n/** Reset only state that the build-time configuration path can mutate. */\nexport function resetZeroConfig(): void {\n resetStylesGeneratedState();\n resetGlobalPolyfillsState();\n resetGlobalPredefinedTokens();\n resetGlobalParseFunctions();\n resetFunctionPolyfills();\n resetHandlers();\n resetStyleChunks();\n clearPipelineCache();\n emittedWarnings.clear();\n resetStyleWarnings();\n}\n\nexport function functionsForExtraction(\n config: ResolvedZeroConfig,\n): Record<string, FunctionDefinition> | undefined {\n return isFunctionsPolyfillEnabled() ? undefined : config.functions;\n}\n"],"mappings":";;;;;;;AAiCA,MAAM,kCAAkB,IAAI,IAAY;AACxC,MAAM,UAAU,SAAS;AAEzB,SAAS,SAAS,KAAa,SAAuB;CACpD,IAAI,WAAW,CAAC,gBAAgB,IAAI,GAAG,GAAG;EACxC,gBAAgB,IAAI,GAAG;EACvB,QAAQ,KAAK,OAAO;CACtB;AACF;;AAGA,SAAgB,cAAc,QAA6C;CACzE,IAAI,OAAO,eAAe,KAAA,GAAW,mBAAmB,OAAO,UAAU;CAEzE,MAAM,aAAa,gBAAgB,MAAM;CAGzC,OAAO;EAAE,QAAQ;EAAQ;EAAY,WAFnB,iBAAiB,QAAQ,YAAY,QAEV;CAAE;AACjD;;AAGA,SAAgB,kBAAwB;CACtC,0BAA0B;CAC1B,0BAA0B;CAC1B,4BAA4B;CAC5B,0BAA0B;CAC1B,uBAAuB;CACvB,cAAc;CACd,iBAAiB;CACjB,mBAAmB;CACnB,gBAAgB,MAAM;CACtB,mBAAmB;AACrB;AAEA,SAAgB,uBACd,QACgD;CAChD,OAAO,2BAA2B,IAAI,KAAA,IAAY,OAAO;AAC3D"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { D as makeClassName, H as formatFunctionRule, M as validateNamePrefix, N as hashString, O as makeCounterStyleName, S as formatFontFaceRule, W as parseFunctionName, _ as formatCounterStyleRule, a as formatPropertyCSS, b as fontFaceContentHash, k as makeKeyframeName, n as formatGlobalRules, r as formatRules, t as createServerStyleArtifact } from "./css-definitions-BZ9vm0ci.js";
|
|
2
|
+
import { b as renderStyles } from "./style-engine-CBuOt1e4.js";
|
|
3
|
+
import { B as getGlobalStyles, F as getGlobalFontFaces, I as getGlobalFunctions, M as getEffectiveProperties, N as getGlobalConfigTokens, P as getGlobalCounterStyles, V as getNamePrefix } from "./runtime-engine-DQ5laRou.js";
|
|
4
4
|
//#region src/ssr/collector.ts
|
|
5
5
|
/**
|
|
6
6
|
* ServerStyleCollector — server-safe style collector for SSR.
|
|
@@ -209,21 +209,35 @@ var ServerStyleCollector = class {
|
|
|
209
209
|
if (replace || !this.rawCSS.has(key)) this.rawCSS.set(key, css);
|
|
210
210
|
}
|
|
211
211
|
/**
|
|
212
|
+
* Return the collected CSS as structured, ordered artifacts.
|
|
213
|
+
*
|
|
214
|
+
* Artifact boundaries are part of the collector output so build tools never
|
|
215
|
+
* need to split or parse CSS text. IDs include the logical collection key
|
|
216
|
+
* and content, making them stable across equivalent page renders while a CSS
|
|
217
|
+
* change always produces a different ID.
|
|
218
|
+
*/
|
|
219
|
+
getArtifacts() {
|
|
220
|
+
const artifacts = [];
|
|
221
|
+
const append = (kind, entries) => {
|
|
222
|
+
for (const [key, css] of entries) artifacts.push(createServerStyleArtifact(kind, key, css, artifacts.length));
|
|
223
|
+
};
|
|
224
|
+
append("property", this.propertyRules);
|
|
225
|
+
append("font-face", this.fontFaceRules);
|
|
226
|
+
append("counter-style", this.counterStyleRules);
|
|
227
|
+
append("function", this.functionRules);
|
|
228
|
+
append("raw", this.rawCSS);
|
|
229
|
+
append("global", this.globalStyles);
|
|
230
|
+
append("chunk", this.chunks);
|
|
231
|
+
append("keyframes", this.keyframeRules);
|
|
232
|
+
return artifacts;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
212
235
|
* Extract all CSS collected so far as a single string.
|
|
213
236
|
* Includes @property and @keyframes rules.
|
|
214
237
|
* Used for non-streaming SSR (renderToString).
|
|
215
238
|
*/
|
|
216
239
|
getCSS() {
|
|
217
|
-
|
|
218
|
-
for (const css of this.propertyRules.values()) parts.push(css);
|
|
219
|
-
for (const css of this.fontFaceRules.values()) parts.push(css);
|
|
220
|
-
for (const css of this.counterStyleRules.values()) parts.push(css);
|
|
221
|
-
for (const css of this.functionRules.values()) parts.push(css);
|
|
222
|
-
for (const css of this.rawCSS.values()) parts.push(css);
|
|
223
|
-
for (const css of this.globalStyles.values()) parts.push(css);
|
|
224
|
-
for (const css of this.chunks.values()) parts.push(css);
|
|
225
|
-
for (const css of this.keyframeRules.values()) parts.push(css);
|
|
226
|
-
return parts.join("\n");
|
|
240
|
+
return this.getArtifacts().map(({ css }) => css).join("\n");
|
|
227
241
|
}
|
|
228
242
|
/**
|
|
229
243
|
* Flush only newly collected CSS since the last flush.
|
|
@@ -301,4 +315,4 @@ function createServerStyleCollector(namePrefix) {
|
|
|
301
315
|
//#endregion
|
|
302
316
|
export { createServerStyleCollector as n, ServerStyleCollector as t };
|
|
303
317
|
|
|
304
|
-
//# sourceMappingURL=collector-
|
|
318
|
+
//# sourceMappingURL=collector-DcViW2ZQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collector-DcViW2ZQ.js","names":[],"sources":["../../src/ssr/collector.ts"],"sourcesContent":["/**\n * ServerStyleCollector — server-safe style collector for SSR.\n *\n * Accumulates CSS rules and cache metadata during server rendering.\n * This is the server-side counterpart to StyleInjector: it allocates\n * hash-based class names using the configured `namePrefix` (defaults\n * to `'t'`), formats CSS rules into text, and tracks rendered class\n * names for lightweight client transfer.\n *\n * One instance is created per HTTP request. Concurrent requests\n * each get their own collector (via AsyncLocalStorage or React context).\n */\n\nimport {\n getEffectiveProperties,\n getGlobalStyles,\n getGlobalCounterStyles,\n getGlobalFontFaces,\n getGlobalFunctions,\n getGlobalConfigTokens,\n getNamePrefix,\n} from '../config';\nimport { formatCounterStyleRule } from '../counter-style';\nimport { fontFaceContentHash, formatFontFaceRule } from '../font-face';\nimport { formatFunctionRule, parseFunctionName } from '../functions';\nimport { renderStyles } from '../pipeline';\nimport type { StyleResult } from '../pipeline';\nimport { hashString } from '../utils/hash';\nimport {\n makeClassName,\n makeCounterStyleName,\n makeKeyframeName,\n validateNamePrefix,\n} from '../utils/name-prefix';\nimport { createServerStyleArtifact } from './artifacts';\nimport type { ServerStyleArtifact, ServerStyleArtifactKind } from './artifacts';\nimport { formatPropertyCSS } from './format-property';\nimport { formatGlobalRules } from './format-global-rules';\nimport { formatRules } from './format-rules';\n\nexport type { ServerStyleArtifact, ServerStyleArtifactKind } from './artifacts';\n\nexport class ServerStyleCollector {\n private chunks = new Map<string, string>();\n private cacheKeyToClassName = new Map<string, string>();\n private flushedKeys = new Set<string>();\n private propertyRules = new Map<string, string>();\n private flushedPropertyKeys = new Set<string>();\n private keyframeRules = new Map<string, string>();\n private flushedKeyframeKeys = new Set<string>();\n private globalStyles = new Map<string, string>();\n private flushedGlobalKeys = new Set<string>();\n private rawCSS = new Map<string, string>();\n private flushedRawKeys = new Set<string>();\n private fontFaceRules = new Map<string, string>();\n private flushedFontFaceKeys = new Set<string>();\n private counterStyleRules = new Map<string, string>();\n private flushedCounterStyleKeys = new Set<string>();\n private functionRules = new Map<string, string>();\n private flushedFunctionKeys = new Set<string>();\n private keyframesCounter = 0;\n private counterStyleCounter = 0;\n private internalsCollected = false;\n private namePrefix: string;\n\n /**\n * @param namePrefix - Optional override for the configured prefix.\n * Defaults to the value from `configure({ namePrefix })` (or `'t'`).\n * Pass an explicit prefix when constructing a collector outside the\n * normal configure() lifecycle (e.g. in tests). Validated eagerly\n * so misconfiguration fails before any CSS is collected.\n */\n constructor(namePrefix?: string) {\n if (namePrefix !== undefined) {\n validateNamePrefix(namePrefix);\n }\n this.namePrefix = namePrefix ?? getNamePrefix();\n }\n\n private generateClassName(cacheKey: string): string {\n return makeClassName(this.namePrefix, hashString(cacheKey));\n }\n\n /**\n * Collect internal @property rules and :root token defaults.\n * Mirrors markStylesGenerated() from the client-side injector.\n * Called automatically on first chunk collection; idempotent.\n *\n * Internals are always emitted here — the RSC path deliberately\n * defers to SSR so that tokens appear exactly once per page in\n * <style data-tasty-ssr> (avoiding duplication of large token sets).\n */\n collectInternals(): void {\n if (this.internalsCollected) return;\n this.internalsCollected = true;\n\n for (const [token, definition] of Object.entries(\n getEffectiveProperties(),\n )) {\n const css = formatPropertyCSS(token, definition);\n if (css) {\n this.collectProperty(`__prop:${token}`, css);\n }\n }\n\n const tokenStyles = getGlobalConfigTokens();\n if (tokenStyles && Object.keys(tokenStyles).length > 0) {\n const tokenRules = renderStyles(tokenStyles, ':root') as StyleResult[];\n if (tokenRules.length > 0) {\n const css = formatGlobalRules(tokenRules);\n if (css) {\n this.collectGlobalStyles('__global:tokens', css);\n }\n }\n }\n\n const globalFF = getGlobalFontFaces();\n if (globalFF) {\n for (const [family, input] of Object.entries(globalFF)) {\n const descriptors = Array.isArray(input) ? input : [input];\n for (const desc of descriptors) {\n const hash = fontFaceContentHash(family, desc);\n const css = formatFontFaceRule(family, desc);\n this.collectFontFace(hash, css);\n }\n }\n }\n\n const globalCS = getGlobalCounterStyles();\n if (globalCS) {\n for (const [name, descriptors] of Object.entries(globalCS)) {\n const css = formatCounterStyleRule(name, descriptors);\n this.collectCounterStyle(name, css, { weak: true });\n }\n }\n\n const globalFn = getGlobalFunctions();\n if (globalFn) {\n for (const [name, definition] of Object.entries(globalFn)) {\n const css = formatFunctionRule(name, definition);\n this.collectFunction(parseFunctionName(name), css, { weak: true });\n }\n }\n\n const globalStyles = getGlobalStyles();\n if (globalStyles) {\n for (const [selector, styles] of Object.entries(globalStyles)) {\n if (Object.keys(styles).length > 0) {\n const rules = renderStyles(styles, selector) as StyleResult[];\n if (rules.length > 0) {\n const css = formatGlobalRules(rules);\n if (css) {\n this.collectGlobalStyles(`__global:styles:${selector}`, css);\n }\n }\n }\n }\n }\n }\n\n /**\n * Allocate a className for a cache key, server-side.\n * Mirrors StyleInjector.allocateClassName but without DOM access.\n */\n allocateClassName(cacheKey: string): {\n className: string;\n isNewAllocation: boolean;\n } {\n const existing = this.cacheKeyToClassName.get(cacheKey);\n if (existing) {\n return { className: existing, isNewAllocation: false };\n }\n\n const className = this.generateClassName(cacheKey);\n this.cacheKeyToClassName.set(cacheKey, className);\n\n return { className, isNewAllocation: true };\n }\n\n /**\n * Record CSS rules for a chunk.\n * Called by useStyles during server render.\n */\n collectChunk(\n cacheKey: string,\n className: string,\n rules: StyleResult[],\n ): void {\n if (this.chunks.has(cacheKey)) return;\n const css = formatRules(rules, className);\n if (css) {\n this.chunks.set(cacheKey, css);\n }\n }\n\n /**\n * Record a @property rule. Deduplicated by name.\n */\n collectProperty(name: string, css: string): void {\n if (!this.propertyRules.has(name)) {\n this.propertyRules.set(name, css);\n }\n }\n\n /**\n * Record a @keyframes rule. Deduplicated by name.\n */\n collectKeyframes(name: string, css: string): void {\n if (!this.keyframeRules.has(name)) {\n this.keyframeRules.set(name, css);\n }\n }\n\n /**\n * Allocate a keyframe name for SSR. Uses provided name or generates one.\n */\n allocateKeyframeName(providedName?: string): string {\n return (\n providedName ??\n makeKeyframeName(this.namePrefix, String(this.keyframesCounter++))\n );\n }\n\n /**\n * Record a @font-face rule. Deduplicated by key (content hash).\n */\n collectFontFace(key: string, css: string): void {\n if (!this.fontFaceRules.has(key)) {\n this.fontFaceRules.set(key, css);\n }\n }\n\n /**\n * Record a @counter-style rule. Deduplicated by name and overrides an\n * existing rule by default. Pass `weak: true` for global `configure()`\n * definitions, which never clobber an existing rule.\n */\n collectCounterStyle(\n name: string,\n css: string,\n options?: { weak?: boolean },\n ): void {\n const existing = this.counterStyleRules.get(name);\n if (existing === undefined) {\n this.counterStyleRules.set(name, css);\n return;\n }\n if (options?.weak || existing === css) return;\n this.counterStyleRules.set(name, css);\n // If a rule with this name was already flushed (streaming), allow the\n // overriding rule to be flushed again so it wins by source order.\n this.flushedCounterStyleKeys.delete(name);\n }\n\n /**\n * Record a @function rule. Deduplicated by CSS function name and overrides an\n * existing rule by default. Pass `weak: true` for global `configure()`\n * definitions, which never clobber an existing rule.\n */\n collectFunction(\n name: string,\n css: string,\n options?: { weak?: boolean },\n ): void {\n const existing = this.functionRules.get(name);\n if (existing === undefined) {\n this.functionRules.set(name, css);\n return;\n }\n if (options?.weak || existing === css) return;\n this.functionRules.set(name, css);\n // If a rule with this name was already flushed (streaming), allow the\n // overriding rule to be flushed again so it wins by source order.\n this.flushedFunctionKeys.delete(name);\n }\n\n /**\n * Allocate a counter-style name for SSR. Uses provided name or generates one.\n */\n allocateCounterStyleName(providedName?: string): string {\n return (\n providedName ??\n makeCounterStyleName(this.namePrefix, String(this.counterStyleCounter++))\n );\n }\n\n /**\n * Record global styles (from useGlobalStyles). Deduplicated by key.\n *\n * Pass `replace` for slot-keyed entries (an explicit `id`), where the last\n * write must win to match the client's update-tracking behavior.\n */\n collectGlobalStyles(key: string, css: string, replace?: boolean): void {\n if (replace || !this.globalStyles.has(key)) {\n this.globalStyles.set(key, css);\n }\n }\n\n /**\n * Record raw CSS text (from useRawCSS). Deduplicated by key.\n *\n * Pass `replace` for slot-keyed entries (an explicit `id`), where the last\n * write must win to match the client's update-tracking behavior.\n */\n collectRawCSS(key: string, css: string, replace?: boolean): void {\n if (replace || !this.rawCSS.has(key)) {\n this.rawCSS.set(key, css);\n }\n }\n\n /**\n * Return the collected CSS as structured, ordered artifacts.\n *\n * Artifact boundaries are part of the collector output so build tools never\n * need to split or parse CSS text. IDs include the logical collection key\n * and content, making them stable across equivalent page renders while a CSS\n * change always produces a different ID.\n */\n getArtifacts(): ServerStyleArtifact[] {\n const artifacts: ServerStyleArtifact[] = [];\n\n const append = (\n kind: ServerStyleArtifactKind,\n entries: Iterable<[string, string]>,\n ) => {\n for (const [key, css] of entries) {\n artifacts.push(\n createServerStyleArtifact(kind, key, css, artifacts.length),\n );\n }\n };\n\n append('property', this.propertyRules);\n append('font-face', this.fontFaceRules);\n append('counter-style', this.counterStyleRules);\n append('function', this.functionRules);\n append('raw', this.rawCSS);\n append('global', this.globalStyles);\n append('chunk', this.chunks);\n append('keyframes', this.keyframeRules);\n\n return artifacts;\n }\n\n /**\n * Extract all CSS collected so far as a single string.\n * Includes @property and @keyframes rules.\n * Used for non-streaming SSR (renderToString).\n */\n getCSS(): string {\n return this.getArtifacts()\n .map(({ css }) => css)\n .join('\\n');\n }\n\n /**\n * Flush only newly collected CSS since the last flush.\n * Used for streaming SSR (renderToPipeableStream + useServerInsertedHTML).\n */\n flushCSS(): string {\n const parts: string[] = [];\n\n for (const [name, css] of this.propertyRules) {\n if (!this.flushedPropertyKeys.has(name)) {\n parts.push(css);\n this.flushedPropertyKeys.add(name);\n }\n }\n\n for (const [key, css] of this.fontFaceRules) {\n if (!this.flushedFontFaceKeys.has(key)) {\n parts.push(css);\n this.flushedFontFaceKeys.add(key);\n }\n }\n\n for (const [key, css] of this.counterStyleRules) {\n if (!this.flushedCounterStyleKeys.has(key)) {\n parts.push(css);\n this.flushedCounterStyleKeys.add(key);\n }\n }\n\n for (const [key, css] of this.functionRules) {\n if (!this.flushedFunctionKeys.has(key)) {\n parts.push(css);\n this.flushedFunctionKeys.add(key);\n }\n }\n\n for (const [key, css] of this.rawCSS) {\n if (!this.flushedRawKeys.has(key)) {\n parts.push(css);\n this.flushedRawKeys.add(key);\n }\n }\n\n for (const [key, css] of this.globalStyles) {\n if (!this.flushedGlobalKeys.has(key)) {\n parts.push(css);\n this.flushedGlobalKeys.add(key);\n }\n }\n\n for (const [key, css] of this.chunks) {\n if (!this.flushedKeys.has(key)) {\n parts.push(css);\n this.flushedKeys.add(key);\n }\n }\n\n for (const [name, css] of this.keyframeRules) {\n if (!this.flushedKeyframeKeys.has(name)) {\n parts.push(css);\n this.flushedKeyframeKeys.add(name);\n }\n }\n\n return parts.join('\\n');\n }\n\n private flushedClassNames = new Set<string>();\n\n /**\n * Return class names rendered since the last call (for streaming).\n * Used to emit lightweight class-list scripts for client hydration.\n */\n getRenderedClassNames(): string[] {\n const names: string[] = [];\n for (const className of this.cacheKeyToClassName.values()) {\n if (!this.flushedClassNames.has(className)) {\n this.flushedClassNames.add(className);\n names.push(className);\n }\n }\n return names;\n }\n}\n\n/**\n * Factory for creating a {@link ServerStyleCollector} instance.\n *\n * Canonical functional entry point; the `ServerStyleCollector` class remains\n * exported for advanced/internal use.\n *\n * @param namePrefix - Optional override for the configured class-name prefix.\n * Defaults to the value from `configure({ namePrefix })` (or `'t'`).\n *\n * @example\n * ```ts\n * import { createServerStyleCollector } from '@tenphi/tasty/ssr';\n *\n * const collector = createServerStyleCollector();\n * ```\n */\nexport function createServerStyleCollector(\n namePrefix?: string,\n): ServerStyleCollector {\n return new ServerStyleCollector(namePrefix);\n}\n"],"mappings":"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"}
|