@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/docs/tasty-static.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Build-Time Extraction (`tastyStatic`)
|
|
2
2
|
|
|
3
|
-
`tastyStatic` is
|
|
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** —
|
|
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
|
-
|
|
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
|
|
19
|
-
|
|
20
|
-
| `@babel/core`
|
|
21
|
-
| `@babel/helper-plugin-utils` | >= 7.24 | Babel plugin
|
|
22
|
-
| `@babel/types`
|
|
23
|
-
| `jiti`
|
|
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
|
|
111
|
-
|
|
112
|
-
| `className`
|
|
113
|
-
| `styles`
|
|
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
|
-
[
|
|
127
|
-
|
|
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
|
|
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
|
-
[
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|
158
|
-
|
|
159
|
-
| `output`
|
|
160
|
-
| `mode`
|
|
161
|
-
| `configFile`
|
|
162
|
-
| `config`
|
|
163
|
-
| `configDeps`
|
|
164
|
-
| `injectImport`
|
|
165
|
-
| `config.states`
|
|
166
|
-
| `config.devMode`
|
|
167
|
-
| `config.tokens`
|
|
168
|
-
| `config.replaceTokens`
|
|
169
|
-
| `config.recipes`
|
|
170
|
-
| `config.keyframes`
|
|
171
|
-
| `config.fontFaces`
|
|
172
|
-
| `config.counterStyles`
|
|
173
|
-
| `config.units`
|
|
174
|
-
| `config.functions`
|
|
175
|
-
| `config.polyfills`
|
|
176
|
-
| `config.plugins`
|
|
177
|
-
| `config.handlers`
|
|
178
|
-
| `config.presets`
|
|
179
|
-
| `config.globalStyles`
|
|
180
|
-
| `config.autoPropertyTypes` | `boolean`
|
|
181
|
-
| `config.parserCacheSize`
|
|
182
|
-
| `config.namePrefix`
|
|
183
|
-
|
|
184
|
-
### Coexisting with
|
|
185
|
-
|
|
186
|
-
When a page mixes `tastyStatic` with
|
|
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 (
|
|
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
|
-
[
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
|
254
|
-
|
|
255
|
-
| `output`
|
|
256
|
-
| `mode`
|
|
257
|
-
| `enabled`
|
|
258
|
-
| `configFile` | `string`
|
|
259
|
-
| `config`
|
|
260
|
-
| `configDeps` | `string[]`
|
|
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
|
-
[
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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() {
|
|
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
|
-
[
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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 =
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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
|
|
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={{
|
|
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
|
|
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
|
|
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) —
|
|
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.
|
|
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.
|
|
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": "^
|
|
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.
|
|
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
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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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"}
|