@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.
Files changed (106) hide show
  1. package/README.md +38 -54
  2. package/dist/{babel-BUQGeOXA.d.ts → babel-D6lOuSwk.d.ts} +2 -2
  3. package/dist/chunks/async-storage-DKK-wTD4.js.map +1 -0
  4. package/dist/chunks/build-config-BBThSdlo.js +45 -0
  5. package/dist/chunks/build-config-BBThSdlo.js.map +1 -0
  6. package/dist/{collector-DTahQUiV.js → chunks/collector-DcViW2ZQ.js} +28 -14
  7. package/dist/chunks/collector-DcViW2ZQ.js.map +1 -0
  8. package/dist/chunks/config-engine-DQVQK4NU.js +403 -0
  9. package/dist/chunks/config-engine-DQVQK4NU.js.map +1 -0
  10. package/dist/chunks/css-definitions-BZ9vm0ci.js +1284 -0
  11. package/dist/chunks/css-definitions-BZ9vm0ci.js.map +1 -0
  12. package/dist/chunks/css-resources-Cyl_axbI.js +149 -0
  13. package/dist/chunks/css-resources-Cyl_axbI.js.map +1 -0
  14. package/dist/chunks/debug-Vyauml7U.js +583 -0
  15. package/dist/chunks/debug-Vyauml7U.js.map +1 -0
  16. package/dist/chunks/dsl-CXvoaLnj.js +2097 -0
  17. package/dist/chunks/dsl-CXvoaLnj.js.map +1 -0
  18. package/dist/chunks/hydration-CLdVKcH2.js +59 -0
  19. package/dist/chunks/hydration-CLdVKcH2.js.map +1 -0
  20. package/dist/chunks/react-runtime-so_X_yMo.js +1526 -0
  21. package/dist/chunks/react-runtime-so_X_yMo.js.map +1 -0
  22. package/dist/chunks/runtime-engine-DQ5laRou.js +3056 -0
  23. package/dist/chunks/runtime-engine-DQ5laRou.js.map +1 -0
  24. package/dist/{merge-styles-oklji0KB.js → chunks/shared-utils-OIg0N_dp.js} +42 -3
  25. package/dist/chunks/shared-utils-OIg0N_dp.js.map +1 -0
  26. package/dist/{config-B5kHzuNz.js → chunks/style-engine-CBuOt1e4.js} +2387 -7442
  27. package/dist/chunks/style-engine-CBuOt1e4.js.map +1 -0
  28. package/dist/{css-writer-B-J87ncv.js → chunks/zero-engine-BFh7gKbK.js} +3 -3
  29. package/dist/chunks/zero-engine-BFh7gKbK.js.map +1 -0
  30. package/dist/{collector-DUaHCcTS.d.ts → collector-CAmndLXe.d.ts} +23 -3
  31. package/dist/{config-YsxGv4tq.d.ts → config-BWYrv_Aa.d.ts} +141 -37
  32. package/dist/core/index.d.ts +5 -5
  33. package/dist/core/index.js +9 -6
  34. package/dist/{index-Cd45t5NM.d.ts → index-BzAC3p06.d.ts} +118 -23
  35. package/dist/{index-PqN-DIpn.d.ts → index-D4kRLj2o.d.ts} +128 -68
  36. package/dist/index.d.ts +5 -5
  37. package/dist/index.js +10 -922
  38. package/dist/{merge-styles-CU7JbEwg.d.ts → merge-styles-BFktNP8J.d.ts} +2 -2
  39. package/dist/ssr/astro-client.js +1 -1
  40. package/dist/ssr/astro-middleware-extract-static.d.ts +11 -0
  41. package/dist/ssr/astro-middleware-extract-static.js +9 -0
  42. package/dist/ssr/astro-middleware-extract-static.js.map +1 -0
  43. package/dist/ssr/astro-middleware-extract.d.ts +11 -0
  44. package/dist/ssr/astro-middleware-extract.js +9 -0
  45. package/dist/ssr/astro-middleware-extract.js.map +1 -0
  46. package/dist/ssr/astro-middleware-static.d.ts +3 -1
  47. package/dist/ssr/astro-middleware.d.ts +3 -1
  48. package/dist/ssr/astro.d.ts +45 -3
  49. package/dist/ssr/astro.js +157 -14
  50. package/dist/ssr/astro.js.map +1 -1
  51. package/dist/ssr/index.d.ts +7 -7
  52. package/dist/ssr/index.js +4 -4
  53. package/dist/ssr/index.js.map +1 -1
  54. package/dist/ssr/next-config.d.ts +66 -0
  55. package/dist/ssr/next-config.js +135 -0
  56. package/dist/ssr/next-config.js.map +1 -0
  57. package/dist/ssr/next.d.ts +9 -2
  58. package/dist/ssr/next.js +26 -10
  59. package/dist/ssr/next.js.map +1 -1
  60. package/dist/static/index.d.ts +2 -2
  61. package/dist/static/index.js +1 -1
  62. package/dist/zero/babel.d.ts +1 -1
  63. package/dist/zero/babel.js +25 -24
  64. package/dist/zero/babel.js.map +1 -1
  65. package/dist/zero/index.d.ts +1 -1
  66. package/dist/zero/index.js +1 -1
  67. package/dist/zero/next.d.ts +1 -1
  68. package/docs/README.md +5 -3
  69. package/docs/adoption.md +3 -3
  70. package/docs/ai-agents.md +83 -78
  71. package/docs/comparison.md +12 -12
  72. package/docs/configuration.md +47 -47
  73. package/docs/debug.md +19 -7
  74. package/docs/dsl.md +3 -3
  75. package/docs/getting-started.md +10 -10
  76. package/docs/injector.md +62 -25
  77. package/docs/methodology.md +3 -3
  78. package/docs/migration-v3.md +49 -49
  79. package/docs/plugins.md +37 -33
  80. package/docs/react-api.md +2 -2
  81. package/docs/runtime-benchmarks.md +351 -0
  82. package/docs/ssr.md +219 -61
  83. package/docs/styles.md +43 -11
  84. package/docs/tasty-static.md +136 -103
  85. package/package.json +80 -11
  86. package/dist/async-storage-DKK-wTD4.js.map +0 -1
  87. package/dist/collector-DTahQUiV.js.map +0 -1
  88. package/dist/config-B5kHzuNz.js.map +0 -1
  89. package/dist/context-CA8YKeMn.js +0 -24
  90. package/dist/context-CA8YKeMn.js.map +0 -1
  91. package/dist/core-Dr4u1NVD.js +0 -1566
  92. package/dist/core-Dr4u1NVD.js.map +0 -1
  93. package/dist/css-writer-B-J87ncv.js.map +0 -1
  94. package/dist/format-global-rules-DklyaXv-.js +0 -22
  95. package/dist/format-global-rules-DklyaXv-.js.map +0 -1
  96. package/dist/format-rules-DKOA-6qu.js +0 -130
  97. package/dist/format-rules-DKOA-6qu.js.map +0 -1
  98. package/dist/hydrate-OeMX99We.js +0 -37
  99. package/dist/hydrate-OeMX99We.js.map +0 -1
  100. package/dist/index.js.map +0 -1
  101. package/dist/keyframes-CV8azJf3.js +0 -493
  102. package/dist/keyframes-CV8azJf3.js.map +0 -1
  103. package/dist/merge-styles-oklji0KB.js.map +0 -1
  104. package/dist/resolve-recipes-DTG81rzl.js +0 -144
  105. package/dist/resolve-recipes-DTG81rzl.js.map +0 -1
  106. /package/dist/{async-storage-DKK-wTD4.js → chunks/async-storage-DKK-wTD4.js} +0 -0
@@ -1,6 +1,8 @@
1
- # Zero Runtime Mode (tastyStatic)
1
+ # Build-Time Extraction (`tastyStatic`)
2
2
 
3
- `tastyStatic` is a build-time utility for generating CSS with zero runtime overhead. It's designed for static sites, no-JS websites, and performance-critical applications where you want to eliminate all runtime styling code. For the broader docs map, see the [Docs Hub](README.md).
3
+ `tastyStatic` is Tasty's framework-agnostic build-time utility for extracting CSS before rendering. It produces no client styling runtime in file mode, but it is not the only way to achieve zero-runtime delivery: server-only `tasty()` also ships no Tasty runtime to the browser while retaining the full React component API. Astro's `tastyIntegration({ islands: false })` is the clearest example; the same architecture applies to server-only Next.js RSC.
4
+
5
+ Choose `tastyStatic` specifically when styles must be generated during the build, when the consumer is not React, or when you want static class-name objects instead of components. For the broader docs map, see the [Docs Hub](README.md).
4
6
 
5
7
  ---
6
8
 
@@ -8,19 +10,19 @@
8
10
 
9
11
  - **Static site generation (SSG)** — Pre-render all styles at build time
10
12
  - **No-JavaScript websites** — CSS works without any JS runtime
11
- - **Performance-critical pages** — Zero runtime overhead for styling
13
+ - **Performance-critical pages** — CSS extracted before the application renders
12
14
  - **Landing pages** — Minimal bundle size with pre-generated CSS
13
15
 
14
16
  ## Requirements
15
17
 
16
- The zero-runtime mode is part of the main `@tenphi/tasty` package but requires additional peer dependencies depending on your setup:
18
+ Build-time extraction is part of the main `@tenphi/tasty` package but requires additional peer dependencies depending on your setup:
17
19
 
18
- | Dependency | Version | Required for |
19
- |---|---|---|
20
- | `@babel/core` | >= 7.24 | Babel plugin (`@tenphi/tasty/babel-plugin`) |
21
- | `@babel/helper-plugin-utils` | >= 7.24 | Babel plugin |
22
- | `@babel/types` | >= 7.24 | Babel plugin |
23
- | `jiti` | >= 2.6 | Next.js wrapper (`@tenphi/tasty/zero/next`) when using `configFile` option |
20
+ | Dependency | Version | Required for |
21
+ | ---------------------------- | ------- | -------------------------------------------------------------------------- |
22
+ | `@babel/core` | >= 7.24 | Babel plugin (`@tenphi/tasty/babel-plugin`) |
23
+ | `@babel/helper-plugin-utils` | >= 7.24 | Babel plugin |
24
+ | `@babel/types` | >= 7.24 | Babel plugin |
25
+ | `jiti` | >= 2.6 | Next.js wrapper (`@tenphi/tasty/zero/next`) when using `configFile` option |
24
26
 
25
27
  All of these are declared as optional peer dependencies of `@tenphi/tasty`. Install only what your setup requires:
26
28
 
@@ -107,11 +109,11 @@ tastyStatic('body', {
107
109
 
108
110
  ## StaticStyle Object
109
111
 
110
- | Property | Type | Description |
111
- |----------|------|-------------|
112
- | `className` | `string` | Space-separated class names for use in JSX |
113
- | `styles` | `Styles` | The original (or merged) styles object |
114
- | `toString()` | `() => string` | Returns `className` for string coercion |
112
+ | Property | Type | Description |
113
+ | ------------ | -------------- | ------------------------------------------ |
114
+ | `className` | `string` | Space-separated class names for use in JSX |
115
+ | `styles` | `Styles` | The original (or merged) styles object |
116
+ | `toString()` | `() => string` | Returns `className` for string coercion |
115
117
 
116
118
  ---
117
119
 
@@ -123,67 +125,73 @@ tastyStatic('body', {
123
125
  // babel.config.js
124
126
  module.exports = {
125
127
  plugins: [
126
- ['@tenphi/tasty/babel-plugin', {
127
- output: 'public/tasty.css',
128
- }]
129
- ]
128
+ [
129
+ '@tenphi/tasty/babel-plugin',
130
+ {
131
+ output: 'public/tasty.css',
132
+ },
133
+ ],
134
+ ],
130
135
  };
131
136
  ```
132
137
 
133
- These examples use `data-schema="dark"` as the root-state convention. If your app already uses a different root attribute such as `data-theme`, keep the same alias pattern and swap the attribute name consistently in your zero-runtime config.
138
+ These examples use `data-schema="dark"` as the root-state convention. If your app already uses a different root attribute such as `data-theme`, keep the same alias pattern and swap the attribute name consistently in your build-time config.
134
139
 
135
140
  ### With Configuration
136
141
 
137
142
  ```javascript
138
143
  module.exports = {
139
144
  plugins: [
140
- ['@tenphi/tasty/babel-plugin', {
141
- output: 'public/tasty.css',
142
- config: {
143
- states: {
144
- '@mobile': '@media(w < 768px)',
145
- '@tablet': '@media(w < 1024px)',
146
- '@dark': '@root(schema=dark)',
145
+ [
146
+ '@tenphi/tasty/babel-plugin',
147
+ {
148
+ output: 'public/tasty.css',
149
+ config: {
150
+ states: {
151
+ '@mobile': '@media(w < 768px)',
152
+ '@tablet': '@media(w < 1024px)',
153
+ '@dark': '@root(schema=dark)',
154
+ },
155
+ devMode: true,
147
156
  },
148
- devMode: true,
149
157
  },
150
- }]
151
- ]
158
+ ],
159
+ ],
152
160
  };
153
161
  ```
154
162
 
155
163
  ### Plugin Options
156
164
 
157
- | Option | Type | Default | Description |
158
- |--------|------|---------|-------------|
159
- | `output` | `string` | `'tasty.css'` | Path for generated CSS file |
160
- | `mode` | `'file' \| 'inject'` | `'file'` | `'file'` writes CSS to disk; `'inject'` embeds CSS inline in JS (see [Inject Mode](#inject-mode)) |
161
- | `configFile` | `string` | — | Absolute path to a TS/JS module that default-exports a `TastyZeroConfig` object. JSON-serializable alternative to `config` — required for Turbopack. |
162
- | `config` | `TastyZeroConfig \| () => TastyZeroConfig` | `{}` | Inline config object or factory function. Takes precedence over `configFile`. |
163
- | `configDeps` | `string[]` | `[]` | Absolute file paths that affect config (for cache invalidation) |
164
- | `injectImport` | `boolean` | `true` | Replace `@tenphi/tasty/static` imports with an import of the generated CSS file. Set to `false` to manage CSS imports manually. |
165
- | `config.states` | `Record<string, string>` | `{}` | Predefined state aliases (e.g. `{ '@mobile': '@media(w < 768px)' }`) |
166
- | `config.devMode` | `boolean` | `false` | Add source comments to CSS |
167
- | `config.tokens` | `ConfigTokens` | — | Design tokens injected as CSS custom properties on `:root`. Values are parsed through the Tasty DSL. Supports state maps for responsive/themed tokens. |
168
- | `config.replaceTokens` | `Record<string, string \| number>` | — | Parse-time token substitution. Keys use `$name` for custom properties and `#name` for color tokens. |
169
- | `config.recipes` | `Record<string, RecipeStyles>` | `{}` | Predefined style recipes |
170
- | `config.keyframes` | `Record<string, KeyframesSteps>` | — | Global `@keyframes` definitions available to all `tastyStatic` calls |
171
- | `config.fontFaces` | `Record<string, FontFaceInput>` | — | Global `@font-face` definitions |
172
- | `config.counterStyles` | `Record<string, CounterStyleDescriptors>` | — | Global `@counter-style` definitions |
173
- | `config.units` | `Record<string, string \| UnitHandler>` | — | Custom units for the style parser (merged with built-ins). E.g. `{ em: 'em', vw: 'vw' }` |
174
- | `config.functions` | `Record<string, FunctionDefinition \| Function>` | — | Custom functions (merged). Bare keys → parse functions; `$$name` keys → declarative CSS `@function` definitions |
175
- | `config.polyfills` | `{ functions?: boolean }` | `{}` | Opt-in polyfills. `functions: true` inlines `@function` calls into plain CSS at build time |
176
- | `config.plugins` | `TastyPlugin[]` | — | Plugins that extend tasty with custom functions, units, states, and handlers |
177
- | `config.handlers` | `Record<string, StyleHandlerDefinition>` | — | Custom style handlers that transform style properties into CSS declarations |
178
- | `config.presets` | `Record<string, TypographyPreset>` | — | Typography presets — shorthand for `generateTypographyTokens()`. Generated tokens merge under explicit `tokens`. |
179
- | `config.globalStyles` | `Record<string, Styles>` | — | Global Tasty styles keyed by CSS selector. Supports the full style syntax. |
180
- | `config.autoPropertyTypes` | `boolean` | `true` | Automatically infer and register CSS `@property` declarations from values |
181
- | `config.parserCacheSize` | `number` | `1000` | Parser LRU cache size. Larger values improve performance for builds with many unique style values |
182
- | `config.namePrefix` | `string` | `'ts'` | Prefix prepended to every generated identifier. Defaults to `'ts'` so static classes never collide with runtime (`'t'`) classes. See [Configuration: Name prefix](configuration.md#name-prefix). |
183
-
184
- ### Coexisting with runtime tasty
185
-
186
- When a page mixes `tastyStatic` with runtime `tasty`, both must use **different** `namePrefix` values. The defaults handle this automatically (`'t'` for runtime, `'ts'` for zero-runtime). If you customize one, customize the other:
165
+ | Option | Type | Default | Description |
166
+ | -------------------------- | ------------------------------------------------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
167
+ | `output` | `string` | `'tasty.css'` | Path for generated CSS file |
168
+ | `mode` | `'file' \| 'inject'` | `'file'` | `'file'` writes CSS to disk; `'inject'` embeds CSS inline in JS (see [Inject Mode](#inject-mode)) |
169
+ | `configFile` | `string` | — | Absolute path to a TS/JS module that default-exports a `TastyZeroConfig` object. JSON-serializable alternative to `config` — required for Turbopack. |
170
+ | `config` | `TastyZeroConfig \| () => TastyZeroConfig` | `{}` | Inline config object or factory function. Takes precedence over `configFile`. |
171
+ | `configDeps` | `string[]` | `[]` | Absolute file paths that affect config (for cache invalidation) |
172
+ | `injectImport` | `boolean` | `true` | Replace `@tenphi/tasty/static` imports with an import of the generated CSS file. Set to `false` to manage CSS imports manually. |
173
+ | `config.states` | `Record<string, string>` | `{}` | Predefined state aliases (e.g. `{ '@mobile': '@media(w < 768px)' }`) |
174
+ | `config.devMode` | `boolean` | `false` | Add source comments to CSS |
175
+ | `config.tokens` | `ConfigTokens` | — | Design tokens injected as CSS custom properties on `:root`. Values are parsed through the Tasty DSL. Supports state maps for responsive/themed tokens. |
176
+ | `config.replaceTokens` | `Record<string, string \| number>` | — | Parse-time token substitution. Keys use `$name` for custom properties and `#name` for color tokens. |
177
+ | `config.recipes` | `Record<string, RecipeStyles>` | `{}` | Predefined style recipes |
178
+ | `config.keyframes` | `Record<string, KeyframesSteps>` | — | Global `@keyframes` definitions available to all `tastyStatic` calls |
179
+ | `config.fontFaces` | `Record<string, FontFaceInput>` | — | Global `@font-face` definitions |
180
+ | `config.counterStyles` | `Record<string, CounterStyleDescriptors>` | — | Global `@counter-style` definitions |
181
+ | `config.units` | `Record<string, string \| UnitHandler>` | — | Custom units for the style parser (merged with built-ins). E.g. `{ em: 'em', vw: 'vw' }` |
182
+ | `config.functions` | `Record<string, FunctionDefinition \| Function>` | — | Custom functions (merged). Bare keys → parse functions; `$$name` keys → declarative CSS `@function` definitions |
183
+ | `config.polyfills` | `{ functions?: boolean }` | `{}` | Opt-in polyfills. `functions: true` inlines `@function` calls into plain CSS at build time |
184
+ | `config.plugins` | `TastyPlugin[]` | — | Plugins that extend tasty with custom functions, units, states, and handlers |
185
+ | `config.handlers` | `Record<string, StyleHandlerDefinition>` | — | Custom style handlers that transform style properties into CSS declarations |
186
+ | `config.presets` | `Record<string, TypographyPreset>` | — | Typography presets — shorthand for `generateTypographyTokens()`. Generated tokens merge under explicit `tokens`. |
187
+ | `config.globalStyles` | `Record<string, Styles>` | — | Global Tasty styles keyed by CSS selector. Supports the full style syntax. |
188
+ | `config.autoPropertyTypes` | `boolean` | `true` | Automatically infer and register CSS `@property` declarations from values |
189
+ | `config.parserCacheSize` | `number` | `1000` | Parser LRU cache size. Larger values improve performance for builds with many unique style values |
190
+ | `config.namePrefix` | `string` | `'ts'` | Prefix prepended to every generated identifier. Defaults to `'ts'` so static classes never collide with runtime (`'t'`) classes. See [Configuration: Name prefix](configuration.md#name-prefix). |
191
+
192
+ ### Coexisting with `tasty()`
193
+
194
+ When a page mixes `tastyStatic` with `tasty`, both must use **different** `namePrefix` values. The defaults handle this automatically (`'t'` for React rendering, `'ts'` for build-time extraction). If you customize one, customize the other:
187
195
 
188
196
  ```ts
189
197
  // tasty-zero.config.ts (Babel plugin)
@@ -191,7 +199,7 @@ export default { namePrefix: 'mbs' };
191
199
  ```
192
200
 
193
201
  ```ts
194
- // app entry (runtime configure)
202
+ // app entry (`tasty()` configure)
195
203
  configure({ namePrefix: 'mb' });
196
204
  ```
197
205
 
@@ -205,16 +213,24 @@ Recipes work with `tastyStatic` the same way as with runtime `tasty`:
205
213
  // babel.config.js
206
214
  module.exports = {
207
215
  plugins: [
208
- ['@tenphi/tasty/babel-plugin', {
209
- output: 'public/tasty.css',
210
- config: {
211
- recipes: {
212
- card: { padding: '4x', fill: '#surface', radius: '1r', border: true },
213
- elevated: { shadow: '2x 2x 4x #shadow' },
216
+ [
217
+ '@tenphi/tasty/babel-plugin',
218
+ {
219
+ output: 'public/tasty.css',
220
+ config: {
221
+ recipes: {
222
+ card: {
223
+ padding: '4x',
224
+ fill: '#surface',
225
+ radius: '1r',
226
+ border: true,
227
+ },
228
+ elevated: { shadow: '2x 2x 4x #shadow' },
229
+ },
214
230
  },
215
231
  },
216
- }]
217
- ]
232
+ ],
233
+ ],
218
234
  };
219
235
  ```
220
236
 
@@ -226,7 +242,7 @@ const card = tastyStatic({
226
242
  color: '#text',
227
243
  });
228
244
 
229
- <div className={card}>Styled card</div>
245
+ <div className={card}>Styled card</div>;
230
246
  ```
231
247
 
232
248
  ---
@@ -250,14 +266,14 @@ export default withTastyZero({
250
266
 
251
267
  ### `withTastyZero` Options
252
268
 
253
- | Option | Type | Default | Description |
254
- |--------|------|---------|-------------|
255
- | `output` | `string` | `'public/tasty.css'` | Output path for CSS relative to project root |
256
- | `mode` | `'file' \| 'inject'` | `'file'` | `'file'` writes CSS to disk; `'inject'` embeds CSS inline in JS |
257
- | `enabled` | `boolean` | `true` | Enable/disable the plugin |
258
- | `configFile` | `string` | — | Path to a TS/JS module that default-exports `TastyZeroConfig`. Recommended for Turbopack compatibility. |
259
- | `config` | `TastyZeroConfig` | — | Inline config object. For static configs that don't change during dev. |
260
- | `configDeps` | `string[]` | `[]` | Extra files the config depends on (for cache invalidation) |
269
+ | Option | Type | Default | Description |
270
+ | ------------ | -------------------- | -------------------- | ------------------------------------------------------------------------------------------------------- |
271
+ | `output` | `string` | `'public/tasty.css'` | Output path for CSS relative to project root |
272
+ | `mode` | `'file' \| 'inject'` | `'file'` | `'file'` writes CSS to disk; `'inject'` embeds CSS inline in JS |
273
+ | `enabled` | `boolean` | `true` | Enable/disable the plugin |
274
+ | `configFile` | `string` | — | Path to a TS/JS module that default-exports `TastyZeroConfig`. Recommended for Turbopack compatibility. |
275
+ | `config` | `TastyZeroConfig` | — | Inline config object. For static configs that don't change during dev. |
276
+ | `configDeps` | `string[]` | `[]` | Extra files the config depends on (for cache invalidation) |
261
277
 
262
278
  ### Turbopack Support
263
279
 
@@ -295,12 +311,15 @@ export default defineConfig({
295
311
  react({
296
312
  babel: {
297
313
  plugins: [
298
- ['@tenphi/tasty/babel-plugin', {
299
- output: 'public/tasty.css',
300
- config: {
301
- states: { '@mobile': '@media(w < 768px)' },
314
+ [
315
+ '@tenphi/tasty/babel-plugin',
316
+ {
317
+ output: 'public/tasty.css',
318
+ config: {
319
+ states: { '@mobile': '@media(w < 768px)' },
320
+ },
302
321
  },
303
- }],
322
+ ],
304
323
  ],
305
324
  },
306
325
  }),
@@ -334,7 +353,9 @@ export const Button = () => <button className={button}>Click</button>;
334
353
  const button = {
335
354
  className: 'ts3f2a1b ts8c4d2e',
336
355
  styles: { padding: '2x 4x', fill: '#purple', color: '#white' },
337
- toString() { return this.className; }
356
+ toString() {
357
+ return this.className;
358
+ },
338
359
  };
339
360
 
340
361
  export const Button = () => <button className={button}>Click</button>;
@@ -382,13 +403,16 @@ This is ideal for **reusable components**, **extensions**, and **libraries** whe
382
403
  // babel.config.js
383
404
  module.exports = {
384
405
  plugins: [
385
- ['@tenphi/tasty/babel-plugin', {
386
- mode: 'inject',
387
- config: {
388
- states: { '@mobile': '@media(w < 768px)' },
406
+ [
407
+ '@tenphi/tasty/babel-plugin',
408
+ {
409
+ mode: 'inject',
410
+ config: {
411
+ states: { '@mobile': '@media(w < 768px)' },
412
+ },
389
413
  },
390
- }]
391
- ]
414
+ ],
415
+ ],
392
416
  };
393
417
  ```
394
418
 
@@ -428,13 +452,20 @@ tastyStatic('.heading', { preset: 'h1' });
428
452
  ```tsx
429
453
  import { injectCSS as _$i } from '@tenphi/tasty/static/inject';
430
454
 
431
- const button = (_$i("ts3f2a1b ts8c4d2e", ".ts3f2a1b.ts3f2a1b{padding:16px 32px}\n.ts8c4d2e.ts8c4d2e{background:#9370db}"), {
432
- className: 'ts3f2a1b ts8c4d2e',
433
- styles: { padding: '2x 4x', fill: '#purple' },
434
- toString() { return this.className; }
435
- });
436
-
437
- _$i(".heading", ".heading{font-size:2.5rem;font-weight:700;line-height:1.2}");
455
+ const button =
456
+ (_$i(
457
+ 'ts3f2a1b ts8c4d2e',
458
+ '.ts3f2a1b.ts3f2a1b{padding:16px 32px}\n.ts8c4d2e.ts8c4d2e{background:#9370db}',
459
+ ),
460
+ {
461
+ className: 'ts3f2a1b ts8c4d2e',
462
+ styles: { padding: '2x 4x', fill: '#purple' },
463
+ toString() {
464
+ return this.className;
465
+ },
466
+ });
467
+
468
+ _$i('.heading', '.heading{font-size:2.5rem;font-weight:700;line-height:1.2}');
438
469
  ```
439
470
 
440
471
  ### Dev Mode / HMR
@@ -443,7 +474,7 @@ Class names are content-hashed (`ts` + MD5). When styles change, a new hash prod
443
474
 
444
475
  ### Limitations (inject mode)
445
476
 
446
- - **Client-side only** — Styles are injected via the DOM, so they are not available during SSR. For server-rendered apps, use `mode: 'file'` or the runtime `tasty()`.
477
+ - **Client-side only** — Styles are injected via the DOM, so they are not available during SSR. For server-rendered apps, use `mode: 'file'` or React `tasty()`.
447
478
  - **Larger JS bundle** — CSS is embedded in JavaScript, increasing bundle size. Best suited for components and extensions, not full-app styling.
448
479
 
449
480
  ---
@@ -517,8 +548,10 @@ const card = tastyStatic({
517
548
 
518
549
  <div
519
550
  className={card}
520
- style={{ '--card-bg': isActive ? 'var(--purple-color)' : 'var(--white-color)' }}
521
- />
551
+ style={{
552
+ '--card-bg': isActive ? 'var(--purple-color)' : 'var(--white-color)',
553
+ }}
554
+ />;
522
555
  ```
523
556
 
524
557
  ---
@@ -535,15 +568,15 @@ const card = tastyStatic({
535
568
  ## Common Issues
536
569
 
537
570
  - No CSS file is generated: make sure the Babel plugin actually runs for files importing `@tenphi/tasty/static`, and verify the `output` path is writable.
538
- - Styles stay dynamic by mistake: `tastyStatic()` only supports build-time-known values. Move runtime values to CSS variables or switch that component to runtime `tasty()`.
571
+ - Styles stay dynamic by mistake: `tastyStatic()` only supports build-time-known values. Move dynamic values to CSS variables or switch that component to React `tasty()`.
539
572
  - Turbopack config behaves inconsistently: prefer `configFile` over inline functions so the setup stays JSON-serializable.
540
573
 
541
574
  ---
542
575
 
543
576
  ## Related
544
577
 
545
- - [Docs Hub](README.md) — Choose the right guide by task or rendering mode
578
+ - [Docs Hub](README.md) — Choose the right guide by task or CSS delivery model
546
579
  - [Style DSL](dsl.md) — State maps, tokens, units, extending semantics (shared by runtime and static)
547
- - [React API](react-api.md) — Runtime styling: `tasty()` factory, component props, variants, sub-elements, style functions
580
+ - [React API](react-api.md) — Client or server rendering with `tasty()`: component props, variants, sub-elements, and style functions
548
581
  - [Configuration](configuration.md) — Global configuration: tokens, recipes, custom units, and style handlers
549
582
  - [Plugins & Extension Points](plugins.md) — Which extension points work at build time (`functions`, `units`, `states`, `handlers`, `recipes`) and which do not (`propHandlers` — `tastyStatic()` takes styles objects, not props)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenphi/tasty",
3
- "version": "0.0.0-snapshot.27b6708",
3
+ "version": "0.0.0-snapshot.28eb83c",
4
4
  "description": "A design-system-integrated styling system and DSL for concise, state-aware UI styling",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -52,6 +52,11 @@
52
52
  "import": "./dist/ssr/next.js",
53
53
  "default": "./dist/ssr/next.js"
54
54
  },
55
+ "./ssr/next-config": {
56
+ "types": "./dist/ssr/next-config.d.ts",
57
+ "import": "./dist/ssr/next-config.js",
58
+ "default": "./dist/ssr/next-config.js"
59
+ },
55
60
  "./ssr/astro": {
56
61
  "types": "./dist/ssr/astro.d.ts",
57
62
  "import": "./dist/ssr/astro.js",
@@ -72,6 +77,16 @@
72
77
  "import": "./dist/ssr/astro-middleware-static.js",
73
78
  "default": "./dist/ssr/astro-middleware-static.js"
74
79
  },
80
+ "./ssr/astro-middleware-extract": {
81
+ "types": "./dist/ssr/astro-middleware-extract.d.ts",
82
+ "import": "./dist/ssr/astro-middleware-extract.js",
83
+ "default": "./dist/ssr/astro-middleware-extract.js"
84
+ },
85
+ "./ssr/astro-middleware-extract-static": {
86
+ "types": "./dist/ssr/astro-middleware-extract-static.d.ts",
87
+ "import": "./dist/ssr/astro-middleware-extract-static.js",
88
+ "default": "./dist/ssr/astro-middleware-extract-static.js"
89
+ },
75
90
  "./tasty.config": "./tasty.config.ts"
76
91
  },
77
92
  "files": [
@@ -85,7 +100,9 @@
85
100
  "./dist/ssr/astro.js",
86
101
  "./dist/ssr/astro-client.js",
87
102
  "./dist/ssr/astro-middleware.js",
88
- "./dist/ssr/astro-middleware-static.js"
103
+ "./dist/ssr/astro-middleware-static.js",
104
+ "./dist/ssr/astro-middleware-extract.js",
105
+ "./dist/ssr/astro-middleware-extract-static.js"
89
106
  ],
90
107
  "engines": {
91
108
  "node": ">=20"
@@ -106,11 +123,16 @@
106
123
  "size": "size-limit",
107
124
  "bench": "vitest bench --project node",
108
125
  "bench:browser": "vitest bench --project browser",
126
+ "bench:overhead": "vitest bench --config vitest.production-browser-bench.config.ts src/tasty-overhead.bench.tsx",
127
+ "bench:injection": "vitest bench --config vitest.production-browser-bench.config.ts src/tasty-injection.bench.ts",
128
+ "bench:interaction": "vitest bench --config vitest.production-browser-bench.config.ts src/tasty-interaction.bench.tsx",
129
+ "bench:cold-start": "node scripts/cold-start/index.mjs",
109
130
  "prepublishOnly": "pnpm run build",
110
131
  "changeset": "changeset",
111
132
  "version": "changeset version",
112
133
  "release": "changeset publish",
113
134
  "knip": "knip",
135
+ "check:build": "node scripts/check-built-entries.mjs",
114
136
  "check:test-only": "node scripts/check-test-only-code.mjs",
115
137
  "hygiene": "pnpm lint && pnpm format:check && pnpm typecheck",
116
138
  "hygiene:fix": "pnpm lint:fix && pnpm format && pnpm typecheck"
@@ -168,6 +190,7 @@
168
190
  "csstype": "^3.1.0"
169
191
  },
170
192
  "devDependencies": {
193
+ "@astrojs/react": "^4.4.2",
171
194
  "@babel/core": "^7.24.0",
172
195
  "@babel/helper-plugin-utils": "^7.24.0",
173
196
  "@babel/types": "^7.24.0",
@@ -176,47 +199,75 @@
176
199
  "@eslint/js": "^10.0.1",
177
200
  "@size-limit/esbuild": "^12.0.0",
178
201
  "@size-limit/file": "^12.0.0",
179
- "@tenphi/eslint-plugin-tasty": "^0.11.3",
202
+ "@tenphi/eslint-plugin-tasty": "^1.0.5",
180
203
  "@testing-library/jest-dom": "^6.9.1",
181
204
  "@testing-library/react": "^16.3.2",
182
205
  "@types/babel__core": "^7.20.5",
183
206
  "@types/babel__helper-plugin-utils": "^7.10.3",
184
- "@types/node": "^25.3.2",
207
+ "@types/node": "^24.13.3",
185
208
  "@types/react": "^19.0.0",
209
+ "@types/react-dom": "^19.0.0",
186
210
  "@vitest/browser-playwright": "^4.1.10",
211
+ "astro": "^5.18.2",
212
+ "esbuild": "^0.27.7",
187
213
  "eslint": "^10.0.0",
188
214
  "eslint-config-prettier": "^10.1.8",
189
215
  "jiti": "^2.6.1",
190
216
  "knip": "^5.61.3",
191
217
  "next": "^16.0.0",
192
218
  "playwright": "^1.62.1",
193
- "prettier": "^3.8.1",
219
+ "prettier": "^3.9.6",
194
220
  "react": "^19.0.0",
221
+ "react-dom": "^19.0.0",
195
222
  "size-limit": "^12.0.0",
196
223
  "tsdown": "^0.22.0",
197
224
  "typescript": "^6.0.2",
198
225
  "typescript-eslint": "^8.56.0",
199
226
  "vitest": "^4.1.10"
200
227
  },
201
- "packageManager": "pnpm@11.0.8",
228
+ "packageManager": "pnpm@11.24.0",
202
229
  "size-limit": [
203
230
  {
204
231
  "name": "main (import *)",
205
232
  "path": "dist/index.js",
206
233
  "import": "*",
207
- "limit": "56.6 kB"
234
+ "limit": "58.5 kB"
235
+ },
236
+ {
237
+ "name": "main (tasty)",
238
+ "path": "dist/index.js",
239
+ "import": "{ tasty }",
240
+ "limit": "51.5 kB"
241
+ },
242
+ {
243
+ "name": "main (StyleParser)",
244
+ "path": "dist/index.js",
245
+ "import": "{ StyleParser }",
246
+ "limit": "7.5 kB"
247
+ },
248
+ {
249
+ "name": "main (parseColor)",
250
+ "path": "dist/index.js",
251
+ "import": "{ parseColor }",
252
+ "limit": "8 kB"
253
+ },
254
+ {
255
+ "name": "main (useRawCSS)",
256
+ "path": "dist/index.js",
257
+ "import": "{ useRawCSS }",
258
+ "limit": "32 kB"
208
259
  },
209
260
  {
210
261
  "name": "core (import *)",
211
262
  "path": "dist/core/index.js",
212
263
  "import": "*",
213
- "limit": "53.65 kB"
264
+ "limit": "55.5 kB"
214
265
  },
215
266
  {
216
267
  "name": "static",
217
268
  "path": "dist/static/index.js",
218
269
  "import": "*",
219
- "limit": "16.8 kB"
270
+ "limit": "2.1 kB"
220
271
  },
221
272
  {
222
273
  "name": "zero",
@@ -227,7 +278,7 @@
227
278
  "path",
228
279
  "crypto"
229
280
  ],
230
- "limit": "31.25 kB"
281
+ "limit": "32.9 kB"
231
282
  },
232
283
  {
233
284
  "name": "babel-plugin",
@@ -238,7 +289,25 @@
238
289
  "path",
239
290
  "crypto"
240
291
  ],
241
- "limit": "49.35 kB"
292
+ "limit": "42.1 kB"
293
+ },
294
+ {
295
+ "name": "astro client",
296
+ "path": "dist/ssr/astro-client.js",
297
+ "import": "*",
298
+ "limit": "0.5 kB"
299
+ },
300
+ {
301
+ "name": "Next.js config",
302
+ "path": "dist/ssr/next-config.js",
303
+ "import": "{ withTastyNext }",
304
+ "ignore": [
305
+ "node:crypto",
306
+ "node:fs",
307
+ "node:module",
308
+ "node:path"
309
+ ],
310
+ "limit": "44 kB"
242
311
  }
243
312
  ]
244
313
  }
@@ -1 +0,0 @@
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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"collector-DTahQUiV.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 { formatPropertyCSS } from './format-property';\nimport { formatGlobalRules } from './format-global-rules';\nimport { formatRules } from './format-rules';\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 * 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 const parts: string[] = [];\n\n for (const css of this.propertyRules.values()) {\n parts.push(css);\n }\n\n for (const css of this.fontFaceRules.values()) {\n parts.push(css);\n }\n\n for (const css of this.counterStyleRules.values()) {\n parts.push(css);\n }\n\n for (const css of this.functionRules.values()) {\n parts.push(css);\n }\n\n for (const css of this.rawCSS.values()) {\n parts.push(css);\n }\n\n for (const css of this.globalStyles.values()) {\n parts.push(css);\n }\n\n for (const css of this.chunks.values()) {\n parts.push(css);\n }\n\n for (const css of this.keyframeRules.values()) {\n parts.push(css);\n }\n\n return parts.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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"}