@tenphi/tasty 0.0.0-snapshot.06c8a41 → 0.0.0-snapshot.07ea4a0
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 +408 -385
- package/dist/babel-types-BKDTT4--.d.ts +18 -0
- package/dist/chunks/async-storage-f9j9JRtq.js +37 -0
- package/dist/chunks/async-storage-f9j9JRtq.js.map +1 -0
- package/dist/chunks/build-config-CW4k04_V.js +60 -0
- package/dist/chunks/build-config-CW4k04_V.js.map +1 -0
- package/dist/{collector-B3OsM252.js → chunks/collector-DKWQXEwN.js} +10 -127
- package/dist/chunks/collector-DKWQXEwN.js.map +1 -0
- package/dist/chunks/config-engine-DTglOA78.js +272 -0
- package/dist/chunks/config-engine-DTglOA78.js.map +1 -0
- package/dist/chunks/config-file-DlZXJMj8.js +25 -0
- package/dist/chunks/config-file-DlZXJMj8.js.map +1 -0
- package/dist/chunks/css-definitions-_C7C-daI.js +910 -0
- package/dist/chunks/css-definitions-_C7C-daI.js.map +1 -0
- package/dist/{css-resources-Cyl_axbI.js → chunks/css-resources-P7IBUUZs.js} +2 -9
- package/dist/chunks/css-resources-P7IBUUZs.js.map +1 -0
- package/dist/chunks/debug-BapJwy9w.js +502 -0
- package/dist/chunks/debug-BapJwy9w.js.map +1 -0
- package/dist/chunks/dsl-DYSJc1-T.js +1383 -0
- package/dist/chunks/dsl-DYSJc1-T.js.map +1 -0
- package/dist/chunks/hydration-TRcz5vnk.js +41 -0
- package/dist/chunks/hydration-TRcz5vnk.js.map +1 -0
- package/dist/chunks/react-runtime-DayjnPuC.js +689 -0
- package/dist/chunks/react-runtime-DayjnPuC.js.map +1 -0
- package/dist/chunks/runtime-engine-IwGbSMb4.js +2432 -0
- package/dist/chunks/runtime-engine-IwGbSMb4.js.map +1 -0
- package/dist/chunks/runtime-state-CVrL92zX.js +152 -0
- package/dist/chunks/runtime-state-CVrL92zX.js.map +1 -0
- package/dist/{merge-styles-DuoZEsm9.js → chunks/shared-utils-y-HjvPN4.js} +60 -55
- package/dist/chunks/shared-utils-y-HjvPN4.js.map +1 -0
- package/dist/chunks/style-engine-CVmNIjow.js +5521 -0
- package/dist/chunks/style-engine-CVmNIjow.js.map +1 -0
- package/dist/{css-writer-Bgowobbx.js → chunks/zero-engine-igHhzYM6.js} +65 -145
- package/dist/chunks/zero-engine-igHhzYM6.js.map +1 -0
- package/dist/{collector-BTVXj2hS.d.ts → collector-CTU-nl0g.d.ts} +6 -5
- package/dist/{config-LfIDmVHx.d.ts → config-CcxqSZI2.d.ts} +45 -40
- package/dist/core/index.d.ts +5 -5
- package/dist/core/index.js +9 -6
- package/dist/hydrate-CRyjjAyl.d.ts +22 -0
- package/dist/{index-Bm9ltvdH.d.ts → index--GdqCPlp.d.ts} +271 -242
- package/dist/index.d.ts +5 -5
- package/dist/index.js +11 -922
- package/dist/{merge-styles-BcLl1u_d.d.ts → merge-styles-DSkVc6CD.d.ts} +2 -3
- package/dist/ssr/astro-client.d.ts +1 -1
- package/dist/ssr/astro-client.js +1 -14
- package/dist/ssr/astro-client.js.map +1 -1
- package/dist/ssr/astro-middleware-extract-static.d.ts +1 -2
- package/dist/ssr/astro-middleware-extract-static.js +1 -1
- package/dist/ssr/astro-middleware-extract.d.ts +1 -2
- package/dist/ssr/astro-middleware-extract.js +1 -1
- package/dist/ssr/astro-middleware-static.d.ts +1 -2
- package/dist/ssr/astro-middleware-static.js +1 -12
- package/dist/ssr/astro-middleware-static.js.map +1 -1
- package/dist/ssr/astro-middleware.d.ts +1 -2
- package/dist/ssr/astro-middleware.js +1 -13
- package/dist/ssr/astro-middleware.js.map +1 -1
- package/dist/ssr/astro.d.ts +8 -16
- package/dist/ssr/astro.js +220 -1
- package/dist/ssr/astro.js.map +1 -0
- package/dist/ssr/client.d.ts +2 -0
- package/dist/ssr/client.js +2 -0
- package/dist/ssr/index.d.ts +5 -25
- package/dist/ssr/index.js +5 -5
- package/dist/ssr/index.js.map +1 -1
- package/dist/ssr/next-config.d.ts +3 -5
- package/dist/ssr/next-config.js +43 -35
- package/dist/ssr/next-config.js.map +1 -1
- package/dist/ssr/next.d.ts +3 -9
- package/dist/ssr/next.js +7 -38
- package/dist/ssr/next.js.map +1 -1
- package/dist/static/index.d.ts +8 -9
- package/dist/static/index.js +4 -22
- package/dist/static/index.js.map +1 -1
- package/dist/static/inject.d.ts +1 -2
- package/dist/{index-BmogEzTo.d.ts → types-DJEM_BOJ.d.ts} +852 -860
- package/dist/zero/babel.d.ts +67 -2
- package/dist/zero/babel.js +77 -140
- package/dist/zero/babel.js.map +1 -1
- package/dist/zero/index.d.ts +62 -8
- package/dist/zero/index.js +24 -2
- package/dist/zero/index.js.map +1 -0
- package/dist/zero/next.d.ts +4 -5
- package/dist/zero/next.js +3 -38
- package/dist/zero/next.js.map +1 -1
- package/docs/README.md +4 -1
- package/docs/adoption.md +25 -17
- package/docs/ai-agents.md +66 -33
- package/docs/comparison.md +9 -8
- package/docs/configuration.md +192 -60
- package/docs/debug.md +5 -7
- package/docs/design-system.md +117 -15
- package/docs/dsl.md +204 -82
- package/docs/getting-started.md +81 -22
- package/docs/injector.md +41 -40
- package/docs/methodology.md +48 -23
- package/docs/migration-v3.md +3 -3
- package/docs/pipeline.md +54 -27
- package/docs/plugins.md +22 -42
- package/docs/react-api.md +72 -9
- package/docs/runtime-benchmarks.md +146 -91
- package/docs/ssr.md +92 -66
- package/docs/styles.md +117 -31
- package/docs/tasty-static.md +162 -58
- package/package.json +96 -38
- package/dist/astro-CeYENy2x.js +0 -301
- package/dist/astro-CeYENy2x.js.map +0 -1
- package/dist/async-storage-DKK-wTD4.js +0 -44
- package/dist/async-storage-DKK-wTD4.js.map +0 -1
- package/dist/babel-DWCsafYw.d.ts +0 -83
- package/dist/collector-B3OsM252.js.map +0 -1
- package/dist/config-B3gPdCqd.js +0 -12421
- package/dist/config-B3gPdCqd.js.map +0 -1
- package/dist/context-CA8YKeMn.js +0 -24
- package/dist/context-CA8YKeMn.js.map +0 -1
- package/dist/core-DGm0CFHP.js +0 -1732
- package/dist/core-DGm0CFHP.js.map +0 -1
- package/dist/css-resources-Cyl_axbI.js.map +0 -1
- package/dist/css-writer-Bgowobbx.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-XRw9u7d4.js +0 -104
- package/dist/format-rules-XRw9u7d4.js.map +0 -1
- package/dist/hydrate-CNOmZprz.js +0 -37
- package/dist/hydrate-CNOmZprz.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/keyframes-DE-OE76F.js +0 -535
- package/dist/keyframes-DE-OE76F.js.map +0 -1
- package/dist/merge-styles-DuoZEsm9.js.map +0 -1
- package/dist/resolve-recipes-H9NqOQuP.js +0 -144
- package/dist/resolve-recipes-H9NqOQuP.js.map +0 -1
- package/dist/ssr-collector-ref-COs_ioWl.js +0 -29
- package/dist/ssr-collector-ref-COs_ioWl.js.map +0 -1
package/docs/ssr.md
CHANGED
|
@@ -15,10 +15,10 @@ Zero-runtime delivery is an outcome, not an alias for `tastyStatic()`. When `tas
|
|
|
15
15
|
| Dependency | Version | Required for |
|
|
16
16
|
| ---------- | ------- | ----------------------------------------------------------------------------------------------- |
|
|
17
17
|
| `react` | >= 18 | All SSR entry points (matches the current peer dependency of `@tenphi/tasty`) |
|
|
18
|
-
| `next` | >=
|
|
18
|
+
| `next` | >= 14 | Next.js integration (`@tenphi/tasty/ssr/next`) — App Router with `useServerInsertedHTML` |
|
|
19
19
|
| Node.js | >= 20 | Generic / streaming SSR (`@tenphi/tasty/ssr`) — uses `node:async_hooks` for `AsyncLocalStorage` |
|
|
20
20
|
|
|
21
|
-
The Astro integration (`@tenphi/tasty/ssr/astro`)
|
|
21
|
+
The Astro integration (`@tenphi/tasty/ssr/astro`) needs `@astrojs/react`. Like `@tenphi/tasty/ssr`, it imports `node:async_hooks`, so the server runtime must provide it. `css: { mode: 'extract' }` requires Astro 5 or newer.
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
@@ -137,7 +137,11 @@ import config from './tasty.config';
|
|
|
137
137
|
|
|
138
138
|
configure(config);
|
|
139
139
|
|
|
140
|
-
export default function TastyStyleRegistry({
|
|
140
|
+
export default function TastyStyleRegistry({
|
|
141
|
+
children,
|
|
142
|
+
}: {
|
|
143
|
+
children: React.ReactNode;
|
|
144
|
+
}) {
|
|
141
145
|
return <TastyRegistry>{children}</TastyRegistry>;
|
|
142
146
|
}
|
|
143
147
|
```
|
|
@@ -147,8 +151,9 @@ The generated file contains eager configuration artifacts: built-in and custom
|
|
|
147
151
|
CSS `@function` definitions, and `globalStyles`. Route-dependent component
|
|
148
152
|
rules and calls to `useGlobalStyles`, `useRawCSS`, `useKeyframes`,
|
|
149
153
|
`useProperty`, `useFontFace`, `useCounterStyle`, and `useFunction` remain
|
|
150
|
-
route-specific. Configured keyframes
|
|
151
|
-
when a route references
|
|
154
|
+
route-specific. Configured keyframes referenced by `globalStyles` are included in the file; all
|
|
155
|
+
other configured keyframes stay lazy and stream only when a route references
|
|
156
|
+
them.
|
|
152
157
|
|
|
153
158
|
The default output directory is `public/_tasty`. The wrapper adds the generated
|
|
154
159
|
URL to `TastyRegistry`, respects `basePath`, preserves existing `env` and
|
|
@@ -175,20 +180,21 @@ meaning; use root-relative, absolute, or data URLs.
|
|
|
175
180
|
### How it works
|
|
176
181
|
|
|
177
182
|
- `TastyRegistry` is a `'use client'` component, but Next.js still server-renders it on initial page load. The `'use client'` boundary is required solely to access `useServerInsertedHTML` — **not** because `tasty()` components need the client.
|
|
178
|
-
- During SSR, `TastyRegistry` creates a `ServerStyleCollector` and registers it via a module-level getter (not `globalThis` — this avoids leaking between Next.js's separate RSC and SSR module graphs). It also wraps children in a React context provider so that hooks inside the SSR tree can discover the collector. All style functions — `tasty()` components, `computeStyles()`, `useStyles()`, `useGlobalStyles()`, `useRawCSS()`, `useKeyframes()`, `useProperty()`, `useFontFace()`, and `
|
|
183
|
+
- During SSR, `TastyRegistry` creates a `ServerStyleCollector` and registers it via a module-level getter (not `globalThis` — this avoids leaking between Next.js's separate RSC and SSR module graphs). It also wraps children in a React context provider so that hooks inside the SSR tree can discover the collector. All style functions — `tasty()` components, `computeStyles()`, `useStyles()`, `useGlobalStyles()`, `useRawCSS()`, `useKeyframes()`, `useProperty()`, `useFontFace()`, `useCounterStyle()`, and `useFunction()` — discover the collector through the module-level getter or context provider.
|
|
179
184
|
- `TastyRegistry` uses `useServerInsertedHTML` to flush collected CSS into the HTML stream as `<style data-tasty-ssr>` tags. This is fully streaming-compatible — styles are injected alongside each Suspense boundary as it resolves.
|
|
180
185
|
- A companion inline `<script>` tag pushes the rendered class names into `window.__TASTY__` for each flush. This streaming-friendly approach accumulates the class list incrementally as Suspense boundaries resolve.
|
|
181
186
|
- When the `@tenphi/tasty/ssr/next` module loads on the client, `hydrateTastyClasses()` runs automatically from `window.__TASTY__` and pre-populates the injector's rules map. During hydration, `computeStyles()` sees the class name is already registered and skips the entire pipeline.
|
|
182
187
|
|
|
183
188
|
### Using Tasty in Server Components
|
|
184
189
|
|
|
185
|
-
|
|
190
|
+
Every Tasty style function needs no `'use client'`, so it can be used directly in React Server Components:
|
|
186
191
|
|
|
187
192
|
- `tasty()` components — dynamic `styleProps` like `<Grid flow="column">` work normally
|
|
188
|
-
- `useStyles()
|
|
189
|
-
- `
|
|
193
|
+
- `useStyles()` and its hook-free equivalent `computeStyles()` — a class name for any element
|
|
194
|
+
- `useGlobalStyles()`, `useRawCSS()` — inject styles by selector
|
|
195
|
+
- `useKeyframes()`, `useProperty()`, `useFontFace()`, `useCounterStyle()`, `useFunction()` — inject ancillary CSS rules
|
|
190
196
|
|
|
191
|
-
|
|
197
|
+
Next.js renders Server Components in a separate module graph that cannot see the collector `TastyRegistry` registers, so their CSS takes the RSC inline path: it is accumulated in a per-request `React.cache` and emitted as an inline `<style>` tag by the next `tasty()` component in the tree. Standalone style functions, `useStyles()` included, cannot emit CSS on their own, so make sure a `tasty()` component renders after them in every RSC tree. The RSC path does not emit configured globals (tokens, `@property`, `globalStyles`); those come from `TastyRegistry`'s collector or the `withTastyNext()` shared stylesheet. For the same reason, import the module that calls `configure()` from `app/layout.tsx` as well as from the registry module, so the RSC graph sees the same states, recipes, and units.
|
|
192
198
|
|
|
193
199
|
### Options
|
|
194
200
|
|
|
@@ -202,13 +208,13 @@ During SSR, all functions discover the collector via the same global getter regi
|
|
|
202
208
|
If your app uses Content Security Policy with nonces, configure it before rendering:
|
|
203
209
|
|
|
204
210
|
```tsx
|
|
205
|
-
// app/
|
|
211
|
+
// app/tasty-registry.tsx: the 'use client' module that renders TastyRegistry
|
|
206
212
|
import { configure } from '@tenphi/tasty';
|
|
207
213
|
|
|
208
214
|
configure({ nonce: 'your-nonce-value' });
|
|
209
215
|
```
|
|
210
216
|
|
|
211
|
-
The nonce is automatically applied to all `<style>` and `<script>` tags injected by `TastyRegistry`.
|
|
217
|
+
The nonce is automatically applied to all `<style>` and `<script>` tags injected by `TastyRegistry`. Call it in the module that renders `TastyRegistry`; `app/layout.tsx` is a Server Component and configures a separate module instance.
|
|
212
218
|
|
|
213
219
|
---
|
|
214
220
|
|
|
@@ -216,13 +222,13 @@ The nonce is automatically applied to all `<style>` and `<script>` tags injected
|
|
|
216
222
|
|
|
217
223
|
Tasty offers several levels of Astro integration. Choose the one that matches your needs:
|
|
218
224
|
|
|
219
|
-
| Setup | Config needed | Deduplication
|
|
220
|
-
| ---------------------------------------------------------------- | ------------- |
|
|
221
|
-
| Zero setup | None |
|
|
222
|
-
| `tastyIntegration({ islands: false })` | One line | Cross-tree
|
|
223
|
-
| `tastyIntegration()` | One line | Cross-tree
|
|
224
|
-
| `tastyIntegration({ css: { mode: 'extract' } })` | One line | Cross-tree and cross-page
|
|
225
|
-
| `tastyIntegration({ islands: false, css: { mode: 'extract' } })` | One line | Cross-tree and cross-page
|
|
225
|
+
| Setup | Config needed | Deduplication | Hooks work | Client JS |
|
|
226
|
+
| ---------------------------------------------------------------- | ------------- | ----------------------------------- | --------------------------------- | -------------- |
|
|
227
|
+
| Zero setup | None | None (each element inlines its CSS) | No (style functions emit nothing) | None |
|
|
228
|
+
| `tastyIntegration({ islands: false })` | One line | Cross-tree | Yes | None |
|
|
229
|
+
| `tastyIntegration()` | One line | Cross-tree | Yes | Auto-hydration |
|
|
230
|
+
| `tastyIntegration({ css: { mode: 'extract' } })` | One line | Cross-tree and cross-page | Yes | Auto-hydration |
|
|
231
|
+
| `tastyIntegration({ islands: false, css: { mode: 'extract' } })` | One line | Cross-tree and cross-page | Yes | None |
|
|
226
232
|
|
|
227
233
|
### Zero setup (static pages)
|
|
228
234
|
|
|
@@ -235,9 +241,9 @@ import { tasty } from '@tenphi/tasty';
|
|
|
235
241
|
const Card = tasty({
|
|
236
242
|
styles: {
|
|
237
243
|
padding: '4x',
|
|
238
|
-
fill: '#
|
|
244
|
+
fill: '#fff',
|
|
239
245
|
radius: '1r',
|
|
240
|
-
border:
|
|
246
|
+
border: '1bw solid #ddd',
|
|
241
247
|
},
|
|
242
248
|
});
|
|
243
249
|
|
|
@@ -257,9 +263,9 @@ import Card from '../components/Card.tsx';
|
|
|
257
263
|
</html>
|
|
258
264
|
```
|
|
259
265
|
|
|
260
|
-
**Trade-offs**:
|
|
266
|
+
**Trade-offs**: Astro renders React with the standard `react` build, where `React.cache` does nothing, so there is no per-request style cache. Every `tasty()` element inlines the full CSS for its classes, even when the same component renders twice. Configured globals (`:root` tokens, `@property` defaults, `globalStyles`, `@font-face`) are never emitted, because only the SSR collector emits them, and CSS from `useGlobalStyles`, `useRawCSS`, `useKeyframes`, `useProperty`, `useFontFace`, `useCounterStyle`, and `useFunction` is dropped. Without the [default properties](dsl.md#default-properties), styles that read them resolve to nothing: `border: true` and `outline: true` draw nothing, and `transition` with no duration does not animate. In development, Tasty warns once that it is rendering without a style collector.
|
|
261
267
|
|
|
262
|
-
Best for
|
|
268
|
+
Best for trying Tasty out with plain values, as above. Use `tastyIntegration()` for anything that relies on `configure()` or on Tasty's default tokens.
|
|
263
269
|
|
|
264
270
|
### Astro Integration (recommended)
|
|
265
271
|
|
|
@@ -296,7 +302,7 @@ import Interactive from '../components/Interactive.tsx';
|
|
|
296
302
|
|
|
297
303
|
<html>
|
|
298
304
|
<body>
|
|
299
|
-
<Card>Static -- styles
|
|
305
|
+
<Card>Static -- styles are inlined into the page head</Card>
|
|
300
306
|
<Interactive client:load>Island -- cache hydrated automatically</Interactive>
|
|
301
307
|
</body>
|
|
302
308
|
</html>
|
|
@@ -401,6 +407,8 @@ import '@tenphi/tasty/ssr/astro-client';
|
|
|
401
407
|
export const onRequest = tastyMiddleware({ transferCache: false });
|
|
402
408
|
```
|
|
403
409
|
|
|
410
|
+
Build-wide extraction (`css: { mode: 'extract' }`) is available only through `tastyIntegration()`; `tastyMiddleware()` always inlines CSS.
|
|
411
|
+
|
|
404
412
|
### How it works
|
|
405
413
|
|
|
406
414
|
Astro's `@astrojs/react` renderer calls `renderToString()` for each React component without wrapping the tree in a provider. The middleware creates a `ServerStyleCollector` and binds it via `AsyncLocalStorage`. All `computeStyles()` calls within the request discover this collector automatically.
|
|
@@ -425,20 +433,16 @@ Call `configure({ nonce: '...' })` before any rendering happens. The middleware
|
|
|
425
433
|
Any React-based framework can integrate using `runWithCollector`, which binds a `ServerStyleCollector` to the current async context via `AsyncLocalStorage`. All style function calls within the render automatically discover the collector.
|
|
426
434
|
|
|
427
435
|
```tsx
|
|
436
|
+
// ── Server ──────────────────────────────────────────────
|
|
428
437
|
import {
|
|
429
|
-
ServerStyleCollector,
|
|
430
438
|
createServerStyleCollector,
|
|
431
439
|
runWithCollector,
|
|
432
|
-
hydrateTastyClasses,
|
|
433
440
|
} from '@tenphi/tasty/ssr';
|
|
434
441
|
import { renderToString } from 'react-dom/server';
|
|
435
|
-
import { hydrateRoot } from 'react-dom/client';
|
|
436
|
-
|
|
437
|
-
// ── Server ──────────────────────────────────────────────
|
|
438
442
|
|
|
439
443
|
const collector = createServerStyleCollector();
|
|
440
444
|
|
|
441
|
-
const html =
|
|
445
|
+
const html = runWithCollector(collector, () => renderToString(<App />));
|
|
442
446
|
|
|
443
447
|
const css = collector.getCSS();
|
|
444
448
|
const classNames = collector.getRenderedClassNames();
|
|
@@ -455,42 +459,56 @@ const fullHtml = `
|
|
|
455
459
|
</body>
|
|
456
460
|
</html>
|
|
457
461
|
`;
|
|
462
|
+
```
|
|
458
463
|
|
|
464
|
+
```tsx
|
|
459
465
|
// ── Client ──────────────────────────────────────────────
|
|
466
|
+
import { hydrateTastyClasses } from '@tenphi/tasty/ssr/client';
|
|
467
|
+
import { hydrateRoot } from 'react-dom/client';
|
|
460
468
|
|
|
461
|
-
//
|
|
462
|
-
hydrateTastyClasses(); // reads from window.__TASTY__
|
|
463
|
-
|
|
469
|
+
hydrateTastyClasses(); // reads window.__TASTY__
|
|
464
470
|
hydrateRoot(document.getElementById('root'), <App />);
|
|
465
471
|
```
|
|
466
472
|
|
|
473
|
+
`@tenphi/tasty/ssr/client` is the browser half: it exports `hydrateTastyClasses()` and has no import side effects. A module shared by the server and the client can import `@tenphi/tasty/ssr` too. It is safe in a browser bundle, where there is no server collector to find, and only `runWithCollector()` throws there.
|
|
474
|
+
|
|
467
475
|
### Streaming SSR
|
|
468
476
|
|
|
469
477
|
For streaming with `renderToPipeableStream`, use `flushCSS()` instead of `getCSS()`:
|
|
470
478
|
|
|
471
479
|
```tsx
|
|
480
|
+
import { PassThrough } from 'node:stream';
|
|
481
|
+
import { renderToPipeableStream } from 'react-dom/server';
|
|
482
|
+
|
|
472
483
|
const collector = createServerStyleCollector();
|
|
473
484
|
|
|
474
|
-
|
|
475
|
-
|
|
485
|
+
// React ends the stream it pipes into, so pipe into a PassThrough and
|
|
486
|
+
// append the remaining styles and the class list after it ends.
|
|
487
|
+
const body = new PassThrough();
|
|
488
|
+
body.pipe(res, { end: false });
|
|
489
|
+
body.on('end', () => {
|
|
490
|
+
// Styles collected after the shell (Suspense boundaries) + class list
|
|
491
|
+
const css = collector.flushCSS();
|
|
492
|
+
if (css) res.write(`<style data-tasty-ssr>${css}</style>`);
|
|
493
|
+
|
|
494
|
+
const classNames = collector.getRenderedClassNames();
|
|
495
|
+
res.end(
|
|
496
|
+
`</div><script>(window.__TASTY__=window.__TASTY__||[]).push(${JSON.stringify(classNames)})</script></body></html>`,
|
|
497
|
+
);
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
runWithCollector(collector, () => {
|
|
501
|
+
const { pipe } = renderToPipeableStream(<App />, {
|
|
476
502
|
onShellReady() {
|
|
477
|
-
//
|
|
503
|
+
// Styles collected for the shell
|
|
478
504
|
const css = collector.flushCSS();
|
|
479
|
-
res.write(`<style data-tasty-ssr>${css}</style>`);
|
|
480
|
-
stream.pipe(res);
|
|
481
|
-
},
|
|
482
|
-
onAllReady() {
|
|
483
|
-
// Flush any remaining styles + class list
|
|
484
|
-
const css = collector.flushCSS();
|
|
485
|
-
if (css) res.write(`<style data-tasty-ssr>${css}</style>`);
|
|
486
|
-
|
|
487
|
-
const classNames = collector.getRenderedClassNames();
|
|
488
505
|
res.write(
|
|
489
|
-
|
|
506
|
+
`<!DOCTYPE html><html><head><style data-tasty-ssr>${css}</style></head><body><div id="root">`,
|
|
490
507
|
);
|
|
508
|
+
pipe(body);
|
|
491
509
|
},
|
|
492
|
-
})
|
|
493
|
-
);
|
|
510
|
+
});
|
|
511
|
+
});
|
|
494
512
|
```
|
|
495
513
|
|
|
496
514
|
---
|
|
@@ -499,14 +517,15 @@ const stream = await runWithCollector(collector, () =>
|
|
|
499
517
|
|
|
500
518
|
### Entry points
|
|
501
519
|
|
|
502
|
-
| Import path
|
|
503
|
-
|
|
|
504
|
-
| `@tenphi/tasty/ssr`
|
|
505
|
-
| `@tenphi/tasty/ssr/
|
|
506
|
-
| `@tenphi/tasty/ssr/next
|
|
507
|
-
| `@tenphi/tasty/ssr/
|
|
508
|
-
| `@tenphi/tasty/ssr/astro
|
|
509
|
-
| `@tenphi/tasty/ssr/astro-
|
|
520
|
+
| Import path | Description |
|
|
521
|
+
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
522
|
+
| `@tenphi/tasty/ssr` | Core SSR API: `ServerStyleCollector`, `createServerStyleCollector`, `runWithCollector`, `getSSRCollector`, `hydrateTastyClasses`; types `ServerStyleArtifact`, `ServerStyleArtifactKind`. Safe to import in a browser bundle; `runWithCollector()` needs the server |
|
|
523
|
+
| `@tenphi/tasty/ssr/client` | Browser-safe client hydration for any framework: `hydrateTastyClasses`, no import side effects |
|
|
524
|
+
| `@tenphi/tasty/ssr/next` | Next.js App Router: `TastyRegistry` component |
|
|
525
|
+
| `@tenphi/tasty/ssr/next-config` | Next.js config wrapper: shared, content-hashed global stylesheet |
|
|
526
|
+
| `@tenphi/tasty/ssr/astro` | Astro: `tastyIntegration`, `tastyMiddleware` |
|
|
527
|
+
| `@tenphi/tasty/ssr/astro-client` | Astro: client-side cache hydration (auto-injected by integration, or import manually) |
|
|
528
|
+
| `@tenphi/tasty/ssr/astro-middleware`<br />`@tenphi/tasty/ssr/astro-middleware-static`<br />`@tenphi/tasty/ssr/astro-middleware-extract`<br />`@tenphi/tasty/ssr/astro-middleware-extract-static` | Astro: the middleware entrypoints `tastyIntegration()` registers via `addMiddleware()`. Exported so Astro can resolve them by specifier; you should not import them. For manual setups use `tastyMiddleware()`. |
|
|
510
529
|
|
|
511
530
|
### `ServerStyleCollector`
|
|
512
531
|
|
|
@@ -522,14 +541,16 @@ Constructor: `new ServerStyleCollector(namePrefix?)`, or use the `createServerSt
|
|
|
522
541
|
| `allocateKeyframeName(providedName?)` | Allocate a keyframe name. Returns `providedName` if given, otherwise generates one using `${namePrefix}k${counter}` (e.g. `tk0`, `tk1`, ...). |
|
|
523
542
|
| `collectProperty(name, css)` | Record a `@property` rule. Deduplicated by name. |
|
|
524
543
|
| `collectFontFace(key, css)` | Record a `@font-face` rule. Deduplicated by content hash. |
|
|
525
|
-
| `collectCounterStyle(name, css)`
|
|
544
|
+
| `collectCounterStyle(name, css, options?)` | Record a `@counter-style` rule, keyed by name. A different rule under the same name replaces the earlier one (and is re-flushed when streaming); `{ weak: true }`, used for `configure()` definitions, never replaces. |
|
|
545
|
+
| `collectFunction(name, css, options?)` | Record a CSS `@function` rule (from `useFunction` or `@function` styles). Same replace/`weak` semantics as `collectCounterStyle`. |
|
|
526
546
|
| `allocateCounterStyleName(providedName?)` | Allocate a counter-style name. Returns `providedName` if given, otherwise generates one using `${namePrefix}c${counter}` (e.g. `tc0`, `tc1`, ...). |
|
|
527
547
|
| `collectGlobalStyles(key, css)` | Record global styles (from `useGlobalStyles`). Deduplicated by key. |
|
|
528
548
|
| `collectRawCSS(key, css)` | Record raw CSS text (from `useRawCSS`). Deduplicated by key. |
|
|
529
549
|
| `collectInternals()` | Collect eager configured globals: `@property`, `:root` tokens and presets, `@font-face`, `@counter-style`, `@function`, and `globalStyles`. Called automatically on first chunk collection; idempotent. |
|
|
530
550
|
| `getCSS()` | Get all collected CSS as a single string. For non-streaming SSR. |
|
|
531
551
|
| `flushCSS()` | Get only CSS collected since the last flush. For streaming SSR. |
|
|
532
|
-
| `getRenderedClassNames()` | Get the
|
|
552
|
+
| `getRenderedClassNames()` | Get the class names allocated since the previous call (the first call returns all so far). Push each batch to `window.__TASTY__` for hydration via `hydrateTastyClasses()`. |
|
|
553
|
+
| `getArtifacts()` | Return the collected CSS as ordered, typed artifacts (`property`, `font-face`, `counter-style`, `function`, `raw`, `global`, `chunk`, `keyframes`). |
|
|
533
554
|
|
|
534
555
|
### `TastyRegistry`
|
|
535
556
|
|
|
@@ -552,9 +573,10 @@ into the Next config. Route-specific CSS continues through `TastyRegistry`.
|
|
|
552
573
|
|
|
553
574
|
Astro integration factory. Registers middleware and optionally injects client hydration.
|
|
554
575
|
|
|
555
|
-
| Option | Type
|
|
556
|
-
| --------- |
|
|
557
|
-
| `islands` | `boolean`
|
|
576
|
+
| Option | Type | Default | Description |
|
|
577
|
+
| --------- | ---------------------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
578
|
+
| `islands` | `boolean` | `true` | When `true`, injects client hydration script and enables `transferCache`. When `false`, no client JS is shipped. |
|
|
579
|
+
| `css` | `{ mode?: 'inline' \| 'extract' }` | `{ mode: 'inline' }` | `'extract'` writes content-hashed shared and per-page stylesheets for prerendered pages (Astro 5+). See [Build-wide CSS extraction](#build-wide-css-extraction). |
|
|
558
580
|
|
|
559
581
|
### `tastyMiddleware(options?)`
|
|
560
582
|
|
|
@@ -566,11 +588,11 @@ Astro middleware factory. Use for manual middleware composition.
|
|
|
566
588
|
|
|
567
589
|
### `hydrateTastyClasses(classes?)`
|
|
568
590
|
|
|
569
|
-
Pre-populate the client injector's rules map with class names rendered on the server, marking them as already present in the DOM so `computeStyles()` skips re-injection during hydration. When called without arguments, reads the class list from `window.__TASTY__` (populated by the streaming `<script>` tags emitted during SSR).
|
|
591
|
+
Exported from `@tenphi/tasty/ssr/client` and `@tenphi/tasty/ssr`. Pre-populate the client injector's rules map with class names rendered on the server, marking them as already present in the DOM so `computeStyles()` skips re-injection during hydration. When called without arguments, reads the class list from `window.__TASTY__` (populated by the streaming `<script>` tags emitted during SSR).
|
|
570
592
|
|
|
571
593
|
### `runWithCollector(collector, fn)`
|
|
572
594
|
|
|
573
|
-
Run a function with a `ServerStyleCollector` bound to the current async context via `AsyncLocalStorage`. All style function calls within `fn` (and async continuations) — including `computeStyles()`, `useStyles()`, `useGlobalStyles()`, `useRawCSS()`, `useKeyframes()`, `useProperty()`, `useFontFace()`, and `
|
|
595
|
+
Run a function with a `ServerStyleCollector` bound to the current async context via `AsyncLocalStorage`. All style function calls within `fn` (and async continuations) — including `computeStyles()`, `useStyles()`, `useGlobalStyles()`, `useRawCSS()`, `useKeyframes()`, `useProperty()`, `useFontFace()`, `useCounterStyle()`, and `useFunction()` — will find this collector. Returns `fn`'s return value, synchronously unless `fn` returns a promise. Throws where `AsyncLocalStorage` is unavailable, such as in a browser.
|
|
574
596
|
|
|
575
597
|
---
|
|
576
598
|
|
|
@@ -582,7 +604,7 @@ The `TastyRegistry` or `tastyIntegration` is missing. Ensure your layout wraps t
|
|
|
582
604
|
|
|
583
605
|
### Hydration mismatch warnings
|
|
584
606
|
|
|
585
|
-
Class names are
|
|
607
|
+
Class names are content hashes of the resolved styles, so render order does not affect them. Unnamed `useKeyframes()` and `useCounterStyle()` names are counters (`tk0`, `tc0`) on both server and client; pass `name` if the two sides can call them in a different order. If you see mismatches, ensure `hydrateTastyClasses()` runs before React hydration. For Next.js, this is automatic. For Astro with `tastyIntegration()`, this is also automatic. For manual Astro middleware setups, import `@tenphi/tasty/ssr/astro-client` in your island components. For custom setups, call `hydrateTastyClasses()` from `@tenphi/tasty/ssr/client` before `hydrateRoot()`.
|
|
586
608
|
|
|
587
609
|
Class names are also derived from the _resolved_ styles, so the server and the client must configure Tasty identically. Anything that changes what a component's styles resolve to will produce a mismatch if it is registered on only one side — `namePrefix`, `recipes`, `handlers`, and the `propHandlers` / `baseStyleProps` extension points described in [Plugins](plugins.md). Call the same `configure()` on both; global CSS is deduplicated automatically, so no `typeof window` guard is needed.
|
|
588
610
|
|
|
@@ -593,7 +615,7 @@ Class names are also derived from the _resolved_ styles, so the server and the c
|
|
|
593
615
|
**Component CSS**: SSR `<style data-tasty-ssr>` tags remain in the DOM. The client injector creates separate `<style>` elements for any new styles. SSR styles are never modified or removed by the client. If this is a concern for very large apps, you can remove the SSR style tags and hydration scripts manually after hydration:
|
|
594
616
|
|
|
595
617
|
```tsx
|
|
596
|
-
import { hydrateTastyClasses } from '@tenphi/tasty/ssr';
|
|
618
|
+
import { hydrateTastyClasses } from '@tenphi/tasty/ssr/client';
|
|
597
619
|
|
|
598
620
|
hydrateTastyClasses();
|
|
599
621
|
hydrateRoot(root, <App />);
|
|
@@ -607,4 +629,8 @@ document.querySelectorAll('script').forEach((el) => {
|
|
|
607
629
|
|
|
608
630
|
### `AsyncLocalStorage` not available
|
|
609
631
|
|
|
610
|
-
|
|
632
|
+
`runWithCollector()` throws `runWithCollector() needs AsyncLocalStorage from node:async_hooks` where the runtime has no `AsyncLocalStorage`, such as in a browser bundle. Call it on the server. The rest of `@tenphi/tasty/ssr` is safe to import in a browser bundle; on the client, call `hydrateTastyClasses()` from `@tenphi/tasty/ssr/client`, or use `@tenphi/tasty/ssr/next`, which hydrates automatically.
|
|
633
|
+
|
|
634
|
+
### "Server rendering without a style collector" warning
|
|
635
|
+
|
|
636
|
+
Tasty rendered on the server with no collector and no React Server Components request scope: Astro without `tastyIntegration()`, a client component server-rendered without `TastyRegistry`, or a custom setup that skips `runWithCollector()`. Each `tasty()` element then inlines all of its CSS, and configured globals (tokens, the default `@property` set, `globalStyles`, `@font-face`) and the CSS of the standalone style functions are not emitted. Add the integration for your framework. The warning is shown once, in development only.
|