@tenphi/tasty 0.0.0-snapshot.bbd10c7 → 0.0.0-snapshot.bbd36bd
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 +196 -140
- package/dist/astro-CeYENy2x.js +301 -0
- package/dist/astro-CeYENy2x.js.map +1 -0
- package/dist/{ssr/async-storage.js → async-storage-DKK-wTD4.js} +16 -6
- package/dist/async-storage-DKK-wTD4.js.map +1 -0
- package/dist/babel-DWCsafYw.d.ts +83 -0
- package/dist/collector-B3OsM252.js +327 -0
- package/dist/collector-B3OsM252.js.map +1 -0
- package/dist/collector-BTVXj2hS.d.ts +163 -0
- package/dist/config-B3gPdCqd.js +12421 -0
- package/dist/config-B3gPdCqd.js.map +1 -0
- package/dist/config-LfIDmVHx.d.ts +1363 -0
- package/dist/context-CA8YKeMn.js +24 -0
- package/dist/context-CA8YKeMn.js.map +1 -0
- package/dist/core/index.d.ts +5 -34
- package/dist/core/index.js +6 -27
- package/dist/core-DGm0CFHP.js +1732 -0
- package/dist/core-DGm0CFHP.js.map +1 -0
- package/dist/css-resources-Cyl_axbI.js +149 -0
- package/dist/css-resources-Cyl_axbI.js.map +1 -0
- package/dist/{zero/extractor.js → css-writer-Bgowobbx.js} +148 -24
- package/dist/css-writer-Bgowobbx.js.map +1 -0
- package/dist/{ssr/format-global-rules.js → format-global-rules-DklyaXv-.js} +2 -2
- package/dist/format-global-rules-DklyaXv-.js.map +1 -0
- package/dist/{ssr/format-rules.js → format-rules-XRw9u7d4.js} +33 -2
- package/dist/format-rules-XRw9u7d4.js.map +1 -0
- package/dist/hydrate-CNOmZprz.js +37 -0
- package/dist/hydrate-CNOmZprz.js.map +1 -0
- package/dist/index-Bm9ltvdH.d.ts +1933 -0
- package/dist/index-BmogEzTo.d.ts +1575 -0
- package/dist/index.d.ts +5 -51
- package/dist/index.js +922 -36
- package/dist/index.js.map +1 -0
- package/dist/keyframes-DE-OE76F.js +535 -0
- package/dist/keyframes-DE-OE76F.js.map +1 -0
- package/dist/{utils/merge-styles.d.ts → merge-styles-BcLl1u_d.d.ts} +3 -3
- package/dist/{utils/merge-styles.js → merge-styles-DuoZEsm9.js} +3 -4
- package/dist/merge-styles-DuoZEsm9.js.map +1 -0
- package/dist/{utils/resolve-recipes.js → resolve-recipes-H9NqOQuP.js} +4 -6
- package/dist/resolve-recipes-H9NqOQuP.js.map +1 -0
- package/dist/ssr/astro-client.js +19 -0
- package/dist/ssr/astro-client.js.map +1 -0
- 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 +18 -0
- package/dist/ssr/astro-middleware-static.js +18 -0
- package/dist/ssr/astro-middleware-static.js.map +1 -0
- package/dist/ssr/astro-middleware.d.ts +19 -0
- package/dist/ssr/astro-middleware.js +19 -0
- package/dist/ssr/astro-middleware.js.map +1 -0
- package/dist/ssr/astro.d.ts +117 -11
- package/dist/ssr/astro.js +2 -64
- package/dist/ssr/index.d.ts +37 -4
- package/dist/ssr/index.js +6 -6
- package/dist/ssr/index.js.map +1 -1
- package/dist/ssr/next-config.d.ts +66 -0
- package/dist/ssr/next-config.js +115 -0
- package/dist/ssr/next-config.js.map +1 -0
- package/dist/ssr/next.d.ts +14 -5
- package/dist/ssr/next.js +32 -15
- package/dist/ssr/next.js.map +1 -1
- package/dist/ssr-collector-ref-COs_ioWl.js +29 -0
- package/dist/ssr-collector-ref-COs_ioWl.js.map +1 -0
- package/dist/static/index.d.ts +91 -5
- package/dist/static/index.js +49 -3
- package/dist/static/index.js.map +1 -0
- package/dist/static/inject.js.map +1 -1
- package/dist/zero/babel.d.ts +2 -182
- package/dist/zero/babel.js +80 -40
- package/dist/zero/babel.js.map +1 -1
- package/dist/zero/index.d.ts +81 -3
- package/dist/zero/index.js +2 -3
- package/dist/zero/next.d.ts +1 -1
- package/dist/zero/next.js +1 -1
- package/dist/zero/next.js.map +1 -1
- package/docs/README.md +20 -12
- package/docs/adoption.md +59 -32
- package/docs/ai-agents.md +224 -0
- package/docs/comparison.md +76 -48
- package/docs/configuration.md +566 -69
- package/docs/debug.md +35 -18
- package/docs/design-system.md +63 -32
- package/docs/dsl.md +527 -179
- package/docs/getting-started.md +62 -41
- package/docs/injector.md +157 -51
- package/docs/methodology.md +69 -37
- package/docs/migration-v3.md +285 -0
- package/docs/pipeline.md +741 -0
- package/docs/plugins.md +353 -0
- package/docs/{runtime.md → react-api.md} +176 -67
- package/docs/runtime-benchmarks.md +351 -0
- package/docs/ssr.md +350 -94
- package/docs/styles.md +65 -16
- package/docs/tasty-static.md +149 -87
- package/package.json +78 -23
- package/tasty.config.ts +6 -0
- package/dist/_virtual/_rolldown/runtime.js +0 -7
- package/dist/chunks/cacheKey.js +0 -77
- package/dist/chunks/cacheKey.js.map +0 -1
- package/dist/chunks/definitions.d.ts +0 -37
- package/dist/chunks/definitions.js +0 -258
- package/dist/chunks/definitions.js.map +0 -1
- package/dist/chunks/index.d.ts +0 -1
- package/dist/chunks/renderChunk.d.ts +0 -1
- package/dist/chunks/renderChunk.js +0 -59
- package/dist/chunks/renderChunk.js.map +0 -1
- package/dist/compute-styles.d.ts +0 -31
- package/dist/compute-styles.js +0 -356
- package/dist/compute-styles.js.map +0 -1
- package/dist/config.d.ts +0 -372
- package/dist/config.js +0 -507
- package/dist/config.js.map +0 -1
- package/dist/counter-style/index.js +0 -51
- package/dist/counter-style/index.js.map +0 -1
- package/dist/debug.d.ts +0 -89
- package/dist/debug.js +0 -453
- package/dist/debug.js.map +0 -1
- package/dist/font-face/index.js +0 -63
- package/dist/font-face/index.js.map +0 -1
- package/dist/hooks/index.d.ts +0 -7
- package/dist/hooks/useCounterStyle.d.ts +0 -50
- package/dist/hooks/useCounterStyle.js +0 -45
- package/dist/hooks/useCounterStyle.js.map +0 -1
- package/dist/hooks/useFontFace.d.ts +0 -43
- package/dist/hooks/useFontFace.js +0 -69
- package/dist/hooks/useFontFace.js.map +0 -1
- package/dist/hooks/useGlobalStyles.d.ts +0 -30
- package/dist/hooks/useGlobalStyles.js +0 -77
- package/dist/hooks/useGlobalStyles.js.map +0 -1
- package/dist/hooks/useKeyframes.d.ts +0 -56
- package/dist/hooks/useKeyframes.js +0 -63
- package/dist/hooks/useKeyframes.js.map +0 -1
- package/dist/hooks/useProperty.d.ts +0 -79
- package/dist/hooks/useProperty.js +0 -108
- package/dist/hooks/useProperty.js.map +0 -1
- package/dist/hooks/useRawCSS.d.ts +0 -53
- package/dist/hooks/useRawCSS.js +0 -34
- package/dist/hooks/useRawCSS.js.map +0 -1
- package/dist/hooks/useStyles.d.ts +0 -40
- package/dist/hooks/useStyles.js +0 -29
- package/dist/hooks/useStyles.js.map +0 -1
- package/dist/injector/index.d.ts +0 -166
- package/dist/injector/index.js +0 -166
- package/dist/injector/index.js.map +0 -1
- package/dist/injector/injector.d.ts +0 -166
- package/dist/injector/injector.js +0 -464
- package/dist/injector/injector.js.map +0 -1
- package/dist/injector/sheet-manager.d.ts +0 -136
- package/dist/injector/sheet-manager.js +0 -733
- package/dist/injector/sheet-manager.js.map +0 -1
- package/dist/injector/types.d.ts +0 -204
- package/dist/keyframes/index.js +0 -206
- package/dist/keyframes/index.js.map +0 -1
- package/dist/parser/classify.js +0 -319
- package/dist/parser/classify.js.map +0 -1
- package/dist/parser/const.js +0 -60
- package/dist/parser/const.js.map +0 -1
- package/dist/parser/lru.js +0 -109
- package/dist/parser/lru.js.map +0 -1
- package/dist/parser/parser.d.ts +0 -25
- package/dist/parser/parser.js +0 -115
- package/dist/parser/parser.js.map +0 -1
- package/dist/parser/tokenizer.js +0 -69
- package/dist/parser/tokenizer.js.map +0 -1
- package/dist/parser/types.d.ts +0 -51
- package/dist/parser/types.js +0 -46
- package/dist/parser/types.js.map +0 -1
- package/dist/pipeline/conditions.d.ts +0 -134
- package/dist/pipeline/conditions.js +0 -406
- package/dist/pipeline/conditions.js.map +0 -1
- package/dist/pipeline/exclusive.js +0 -230
- package/dist/pipeline/exclusive.js.map +0 -1
- package/dist/pipeline/index.d.ts +0 -55
- package/dist/pipeline/index.js +0 -708
- package/dist/pipeline/index.js.map +0 -1
- package/dist/pipeline/materialize.js +0 -1103
- package/dist/pipeline/materialize.js.map +0 -1
- package/dist/pipeline/parseStateKey.d.ts +0 -15
- package/dist/pipeline/parseStateKey.js +0 -446
- package/dist/pipeline/parseStateKey.js.map +0 -1
- package/dist/pipeline/simplify.js +0 -515
- package/dist/pipeline/simplify.js.map +0 -1
- package/dist/pipeline/warnings.js +0 -18
- package/dist/pipeline/warnings.js.map +0 -1
- package/dist/plugins/index.d.ts +0 -2
- package/dist/plugins/okhsl-plugin.d.ts +0 -35
- package/dist/plugins/okhsl-plugin.js +0 -97
- package/dist/plugins/okhsl-plugin.js.map +0 -1
- package/dist/plugins/types.d.ts +0 -76
- package/dist/properties/index.js +0 -222
- package/dist/properties/index.js.map +0 -1
- package/dist/properties/property-type-resolver.d.ts +0 -24
- package/dist/properties/property-type-resolver.js +0 -90
- package/dist/properties/property-type-resolver.js.map +0 -1
- package/dist/ssr/astro.js.map +0 -1
- package/dist/ssr/async-storage.d.ts +0 -17
- package/dist/ssr/async-storage.js.map +0 -1
- package/dist/ssr/collect-auto-properties.js +0 -39
- package/dist/ssr/collect-auto-properties.js.map +0 -1
- package/dist/ssr/collector.d.ts +0 -102
- package/dist/ssr/collector.js +0 -219
- package/dist/ssr/collector.js.map +0 -1
- package/dist/ssr/format-global-rules.js.map +0 -1
- package/dist/ssr/format-keyframes.js +0 -69
- package/dist/ssr/format-keyframes.js.map +0 -1
- package/dist/ssr/format-property.js +0 -49
- package/dist/ssr/format-property.js.map +0 -1
- package/dist/ssr/format-rules.js.map +0 -1
- package/dist/ssr/hydrate.d.ts +0 -22
- package/dist/ssr/hydrate.js +0 -49
- package/dist/ssr/hydrate.js.map +0 -1
- package/dist/ssr/ssr-collector-ref.js +0 -12
- package/dist/ssr/ssr-collector-ref.js.map +0 -1
- package/dist/states/index.d.ts +0 -49
- package/dist/states/index.js +0 -170
- package/dist/states/index.js.map +0 -1
- package/dist/static/tastyStatic.d.ts +0 -46
- package/dist/static/tastyStatic.js +0 -30
- package/dist/static/tastyStatic.js.map +0 -1
- package/dist/static/types.d.ts +0 -49
- package/dist/static/types.js +0 -24
- package/dist/static/types.js.map +0 -1
- package/dist/styles/border.d.ts +0 -25
- package/dist/styles/border.js +0 -120
- package/dist/styles/border.js.map +0 -1
- package/dist/styles/color.d.ts +0 -14
- package/dist/styles/color.js +0 -26
- package/dist/styles/color.js.map +0 -1
- package/dist/styles/const.js +0 -17
- package/dist/styles/const.js.map +0 -1
- package/dist/styles/createStyle.js +0 -79
- package/dist/styles/createStyle.js.map +0 -1
- package/dist/styles/dimension.js +0 -109
- package/dist/styles/dimension.js.map +0 -1
- package/dist/styles/directional.js +0 -133
- package/dist/styles/directional.js.map +0 -1
- package/dist/styles/display.d.ts +0 -30
- package/dist/styles/display.js +0 -73
- package/dist/styles/display.js.map +0 -1
- package/dist/styles/fade.d.ts +0 -15
- package/dist/styles/fade.js +0 -62
- package/dist/styles/fade.js.map +0 -1
- package/dist/styles/fill.d.ts +0 -42
- package/dist/styles/fill.js +0 -51
- package/dist/styles/fill.js.map +0 -1
- package/dist/styles/flow.d.ts +0 -16
- package/dist/styles/flow.js +0 -12
- package/dist/styles/flow.js.map +0 -1
- package/dist/styles/gap.d.ts +0 -31
- package/dist/styles/gap.js +0 -38
- package/dist/styles/gap.js.map +0 -1
- package/dist/styles/height.d.ts +0 -17
- package/dist/styles/height.js +0 -19
- package/dist/styles/height.js.map +0 -1
- package/dist/styles/index.d.ts +0 -1
- package/dist/styles/index.js +0 -8
- package/dist/styles/index.js.map +0 -1
- package/dist/styles/inset.d.ts +0 -24
- package/dist/styles/inset.js +0 -34
- package/dist/styles/inset.js.map +0 -1
- package/dist/styles/list.d.ts +0 -16
- package/dist/styles/list.js +0 -100
- package/dist/styles/list.js.map +0 -1
- package/dist/styles/margin.d.ts +0 -24
- package/dist/styles/margin.js +0 -32
- package/dist/styles/margin.js.map +0 -1
- package/dist/styles/outline.d.ts +0 -29
- package/dist/styles/outline.js +0 -55
- package/dist/styles/outline.js.map +0 -1
- package/dist/styles/padding.d.ts +0 -24
- package/dist/styles/padding.js +0 -32
- package/dist/styles/padding.js.map +0 -1
- package/dist/styles/placement.d.ts +0 -37
- package/dist/styles/placement.js +0 -74
- package/dist/styles/placement.js.map +0 -1
- package/dist/styles/predefined.d.ts +0 -71
- package/dist/styles/predefined.js +0 -237
- package/dist/styles/predefined.js.map +0 -1
- package/dist/styles/preset.d.ts +0 -52
- package/dist/styles/preset.js +0 -127
- package/dist/styles/preset.js.map +0 -1
- package/dist/styles/radius.d.ts +0 -12
- package/dist/styles/radius.js +0 -83
- package/dist/styles/radius.js.map +0 -1
- package/dist/styles/scrollMargin.d.ts +0 -24
- package/dist/styles/scrollMargin.js +0 -32
- package/dist/styles/scrollMargin.js.map +0 -1
- package/dist/styles/scrollbar.d.ts +0 -25
- package/dist/styles/scrollbar.js +0 -51
- package/dist/styles/scrollbar.js.map +0 -1
- package/dist/styles/shadow.d.ts +0 -14
- package/dist/styles/shadow.js +0 -25
- package/dist/styles/shadow.js.map +0 -1
- package/dist/styles/shared.js +0 -17
- package/dist/styles/shared.js.map +0 -1
- package/dist/styles/transition.d.ts +0 -14
- package/dist/styles/transition.js +0 -159
- package/dist/styles/transition.js.map +0 -1
- package/dist/styles/types.d.ts +0 -564
- package/dist/styles/width.d.ts +0 -17
- package/dist/styles/width.js +0 -19
- package/dist/styles/width.js.map +0 -1
- package/dist/tasty.d.ts +0 -134
- package/dist/tasty.js +0 -240
- package/dist/tasty.js.map +0 -1
- package/dist/types.d.ts +0 -184
- package/dist/utils/cache-wrapper.js +0 -21
- package/dist/utils/cache-wrapper.js.map +0 -1
- package/dist/utils/case-converter.js +0 -8
- package/dist/utils/case-converter.js.map +0 -1
- package/dist/utils/color-math.d.ts +0 -46
- package/dist/utils/color-math.js +0 -749
- package/dist/utils/color-math.js.map +0 -1
- package/dist/utils/color-space.d.ts +0 -5
- package/dist/utils/color-space.js +0 -228
- package/dist/utils/color-space.js.map +0 -1
- package/dist/utils/colors.d.ts +0 -5
- package/dist/utils/colors.js +0 -10
- package/dist/utils/colors.js.map +0 -1
- package/dist/utils/css-types.d.ts +0 -7
- package/dist/utils/dotize.d.ts +0 -26
- package/dist/utils/dotize.js +0 -122
- package/dist/utils/dotize.js.map +0 -1
- package/dist/utils/filter-base-props.d.ts +0 -15
- package/dist/utils/filter-base-props.js +0 -45
- package/dist/utils/filter-base-props.js.map +0 -1
- package/dist/utils/get-display-name.d.ts +0 -7
- package/dist/utils/get-display-name.js +0 -10
- package/dist/utils/get-display-name.js.map +0 -1
- package/dist/utils/has-keys.js +0 -13
- package/dist/utils/has-keys.js.map +0 -1
- package/dist/utils/is-dev-env.js +0 -19
- package/dist/utils/is-dev-env.js.map +0 -1
- package/dist/utils/is-valid-element-type.js +0 -15
- package/dist/utils/is-valid-element-type.js.map +0 -1
- package/dist/utils/merge-styles.js.map +0 -1
- package/dist/utils/mod-attrs.d.ts +0 -6
- package/dist/utils/mod-attrs.js +0 -20
- package/dist/utils/mod-attrs.js.map +0 -1
- package/dist/utils/process-tokens.d.ts +0 -17
- package/dist/utils/process-tokens.js +0 -83
- package/dist/utils/process-tokens.js.map +0 -1
- package/dist/utils/resolve-recipes.d.ts +0 -17
- package/dist/utils/resolve-recipes.js.map +0 -1
- package/dist/utils/selector-transform.js +0 -32
- package/dist/utils/selector-transform.js.map +0 -1
- package/dist/utils/string.js +0 -8
- package/dist/utils/string.js.map +0 -1
- package/dist/utils/styles.d.ts +0 -99
- package/dist/utils/styles.js +0 -220
- package/dist/utils/styles.js.map +0 -1
- package/dist/utils/typography.d.ts +0 -47
- package/dist/utils/typography.js +0 -51
- package/dist/utils/typography.js.map +0 -1
- package/dist/utils/warnings.d.ts +0 -16
- package/dist/utils/warnings.js +0 -16
- package/dist/utils/warnings.js.map +0 -1
- package/dist/zero/css-writer.d.ts +0 -45
- package/dist/zero/css-writer.js +0 -73
- package/dist/zero/css-writer.js.map +0 -1
- package/dist/zero/extractor.d.ts +0 -24
- package/dist/zero/extractor.js.map +0 -1
- package/docs/PIPELINE.md +0 -519
- /package/dist/{chunks/cacheKey.d.ts → ssr/astro-client.d.ts} +0 -0
package/docs/ssr.md
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
# Server-Side Rendering (SSR)
|
|
2
2
|
|
|
3
|
-
Tasty supports server-side rendering with zero-cost client hydration. This does **not** introduce a separate styling engine:
|
|
3
|
+
Tasty supports server-side rendering with zero-cost client hydration. This does **not** introduce a separate styling engine: `tasty()` uses the same rendering pipeline on the server and in the browser, while the SSR integrations add server-side CSS collection and client-side cache hydration. Your existing `tasty()` components work unchanged, and SSR remains opt-in with no per-component modifications. For the broader docs map, see the [Docs Hub](README.md).
|
|
4
|
+
|
|
5
|
+
## Zero-runtime terminology
|
|
6
|
+
|
|
7
|
+
Zero-runtime delivery is an outcome, not an alias for `tastyStatic()`. When `tasty()` components render only on the server, their CSS is delivered with the HTML and no Tasty styling runtime is shipped to the browser. Astro's `tastyIntegration({ islands: false })` is the explicit integration for this setup. Server-only Next.js React Server Components follow the same architecture, although you should verify the generated output for your deployment.
|
|
8
|
+
|
|
9
|
+
`tastyStatic()` reaches the same client-side outcome by extracting CSS during the build instead of during React rendering. Use it when extraction must happen before rendering or when the consumer is not React; see [Build-Time Extraction](tasty-static.md).
|
|
4
10
|
|
|
5
11
|
---
|
|
6
12
|
|
|
7
13
|
## Requirements
|
|
8
14
|
|
|
9
|
-
| Dependency | Version | Required for
|
|
10
|
-
|
|
11
|
-
| `react`
|
|
12
|
-
| `next`
|
|
13
|
-
| Node.js
|
|
15
|
+
| Dependency | Version | Required for |
|
|
16
|
+
| ---------- | ------- | ----------------------------------------------------------------------------------------------- |
|
|
17
|
+
| `react` | >= 18 | All SSR entry points (matches the current peer dependency of `@tenphi/tasty`) |
|
|
18
|
+
| `next` | >= 13 | Next.js integration (`@tenphi/tasty/ssr/next`) — App Router with `useServerInsertedHTML` |
|
|
19
|
+
| Node.js | >= 20 | Generic / streaming SSR (`@tenphi/tasty/ssr`) — uses `node:async_hooks` for `AsyncLocalStorage` |
|
|
14
20
|
|
|
15
21
|
The Astro integration (`@tenphi/tasty/ssr/astro`) has no additional dependencies beyond `react`.
|
|
16
22
|
|
|
@@ -18,18 +24,18 @@ The Astro integration (`@tenphi/tasty/ssr/astro`) has no additional dependencies
|
|
|
18
24
|
|
|
19
25
|
## How It Works
|
|
20
26
|
|
|
21
|
-
`tasty()` components are hook-free and use `computeStyles()` internally — a synchronous, framework-agnostic function. On the server, `computeStyles()`
|
|
27
|
+
`tasty()` components are hook-free and use `computeStyles()` internally — a synchronous, framework-agnostic function. On the server, `computeStyles()` discovers a `ServerStyleCollector` via a registered getter (module-level for Next.js, `globalThis` for Astro/generic frameworks using `AsyncLocalStorage`) and collects CSS into it instead of trying to access the DOM. On the client, CSS is injected synchronously into the DOM during render; the injector's content-based cache makes this idempotent. The collector accumulates all styles and serializes them as `<style>` tags plus a class-list script in the HTML. On the client, `hydrateTastyClasses()` pre-populates the injector's rules map with the rendered class names so that `computeStyles()` skips the rendering pipeline entirely during hydration.
|
|
22
28
|
|
|
23
29
|
```
|
|
24
30
|
Server Client
|
|
25
31
|
────── ──────
|
|
26
|
-
tasty() renders
|
|
27
|
-
└─ computeStyles() └─
|
|
32
|
+
tasty() renders hydrateTastyClasses() reads window.__TASTY__
|
|
33
|
+
└─ computeStyles() └─ marks rendered class names as already-in-DOM
|
|
28
34
|
└─ collector.collect()
|
|
29
35
|
tasty() renders
|
|
30
36
|
After render: └─ computeStyles()
|
|
31
|
-
<style data-tasty-ssr>
|
|
32
|
-
<script
|
|
37
|
+
<style data-tasty-ssr> └─ class name known → skip pipeline
|
|
38
|
+
<script> (pushes to __TASTY__) └─ no CSS re-injection
|
|
33
39
|
```
|
|
34
40
|
|
|
35
41
|
---
|
|
@@ -80,17 +86,109 @@ export default function RootLayout({
|
|
|
80
86
|
|
|
81
87
|
That's it. All `tasty()` components inside the tree automatically get SSR support. No per-component changes needed.
|
|
82
88
|
|
|
89
|
+
### Optional shared globals stylesheet
|
|
90
|
+
|
|
91
|
+
By default, configured global CSS is included in the streamed Tasty style tag
|
|
92
|
+
for every route. `withTastyNext()` can move that stable CSS into one
|
|
93
|
+
content-hashed stylesheet shared by all routes while leaving component and hook
|
|
94
|
+
styles in the normal streaming path.
|
|
95
|
+
|
|
96
|
+
Keep the Tasty config in a side-effect-free module:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
// app/tasty.config.ts
|
|
100
|
+
import type { TastyConfig } from '@tenphi/tasty';
|
|
101
|
+
|
|
102
|
+
const config: TastyConfig = {
|
|
103
|
+
tokens: { $gap: '8px', '#brand': 'rebeccapurple' },
|
|
104
|
+
globalStyles: { body: { margin: '0', color: '#brand' } },
|
|
105
|
+
fontFaces: {
|
|
106
|
+
Brand: { src: 'url("/fonts/brand.woff2") format("woff2")' },
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export default config;
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Use it from the Next config:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
// next.config.ts
|
|
117
|
+
import { withTastyNext } from '@tenphi/tasty/ssr/next-config';
|
|
118
|
+
import config from './app/tasty.config';
|
|
119
|
+
|
|
120
|
+
export default withTastyNext({
|
|
121
|
+
config,
|
|
122
|
+
})({
|
|
123
|
+
// your Next.js config
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The runtime must receive the same config. Import and configure it before the
|
|
128
|
+
registry renders:
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
// app/tasty-registry.tsx
|
|
132
|
+
'use client';
|
|
133
|
+
|
|
134
|
+
import { configure } from '@tenphi/tasty';
|
|
135
|
+
import { TastyRegistry } from '@tenphi/tasty/ssr/next';
|
|
136
|
+
import config from './tasty.config';
|
|
137
|
+
|
|
138
|
+
configure(config);
|
|
139
|
+
|
|
140
|
+
export default function TastyStyleRegistry({ children }) {
|
|
141
|
+
return <TastyRegistry>{children}</TastyRegistry>;
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The generated file contains eager configuration artifacts: built-in and custom
|
|
146
|
+
`@property` rules, tokens and presets, `@font-face`, `@counter-style`, native
|
|
147
|
+
CSS `@function` definitions, and `globalStyles`. Route-dependent component
|
|
148
|
+
rules and calls to `useGlobalStyles`, `useRawCSS`, `useKeyframes`,
|
|
149
|
+
`useProperty`, `useFontFace`, `useCounterStyle`, and `useFunction` remain
|
|
150
|
+
route-specific. Configured keyframes also remain lazy and are streamed only
|
|
151
|
+
when a route references them.
|
|
152
|
+
|
|
153
|
+
The default output directory is `public/_tasty`. The wrapper adds the generated
|
|
154
|
+
URL to `TastyRegistry`, respects `basePath`, preserves existing `env` and
|
|
155
|
+
`headers` config, and serves the content-hashed file with an immutable one-year
|
|
156
|
+
cache header on Next server deployments. Static exports keep the content-hashed
|
|
157
|
+
URL and leave cache headers to the hosting provider. Older hashes are not
|
|
158
|
+
deleted automatically, so rolling deployments cannot break pages from the
|
|
159
|
+
previous build; clean the generated directory as part of a clean deployment if
|
|
160
|
+
needed. Page-relative CSS resources such as
|
|
161
|
+
`url(../fonts/brand.woff2)` are rejected because moving them would change their
|
|
162
|
+
meaning; use root-relative, absolute, or data URLs.
|
|
163
|
+
|
|
164
|
+
`withTastyNext()` options:
|
|
165
|
+
|
|
166
|
+
| Option | Type | Default | Description |
|
|
167
|
+
| ------------ | ------------- | ------------------------- | ---------------------------------------------------------------------- |
|
|
168
|
+
| `config` | `TastyConfig` | — | Config object; takes precedence over `configFile` |
|
|
169
|
+
| `configFile` | `string` | — | Project-relative config module path; requires the optional `jiti` peer |
|
|
170
|
+
| `rootDir` | `string` | current directory | Next app root when the build runs from a monorepo root |
|
|
171
|
+
| `outputDir` | `string` | `public/_tasty` | Filesystem output directory |
|
|
172
|
+
| `publicPath` | `string` | inferred from `outputDir` | Root-relative URL; required when output is outside `public` |
|
|
173
|
+
| `enabled` | `boolean` | `true` | Disable generation without changing wrapper composition |
|
|
174
|
+
|
|
83
175
|
### How it works
|
|
84
176
|
|
|
85
177
|
- `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.
|
|
86
|
-
- During SSR, `TastyRegistry` creates a `ServerStyleCollector` and registers it via a module-level
|
|
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 `useCounterStyle()` — discover the collector through the module-level getter or context provider.
|
|
87
179
|
- `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.
|
|
88
|
-
- A companion `<script>` tag
|
|
89
|
-
- When the module loads on the client, `
|
|
180
|
+
- 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
|
+
- 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.
|
|
90
182
|
|
|
91
|
-
### Using
|
|
183
|
+
### Using Tasty in Server Components
|
|
92
184
|
|
|
93
|
-
|
|
185
|
+
All Tasty style functions are hook-free and do not require `'use client'`. They can be used directly in React Server Components:
|
|
186
|
+
|
|
187
|
+
- `tasty()` components — dynamic `styleProps` like `<Grid flow="column">` work normally
|
|
188
|
+
- `useStyles()`, `useGlobalStyles()`, `useRawCSS()` — inject styles by class or selector
|
|
189
|
+
- `useKeyframes()`, `useProperty()`, `useFontFace()`, `useCounterStyle()` — inject ancillary CSS rules
|
|
190
|
+
|
|
191
|
+
During SSR, all functions discover the collector via the same global getter registered by `TastyRegistry` — no React context or client boundary needed. In RSC mode without a collector (e.g., Astro zero-setup), CSS is accumulated in a per-request cache and flushed into an inline `<style>` tag by the next `tasty()` component in the tree. Ensure at least one `tasty()` component is present in every RSC render tree — standalone style functions alone cannot emit their CSS without a `tasty()` component to trigger the flush.
|
|
94
192
|
|
|
95
193
|
### Options
|
|
96
194
|
|
|
@@ -116,18 +214,19 @@ The nonce is automatically applied to all `<style>` and `<script>` tags injected
|
|
|
116
214
|
|
|
117
215
|
## Astro
|
|
118
216
|
|
|
119
|
-
|
|
217
|
+
Tasty offers several levels of Astro integration. Choose the one that matches your needs:
|
|
120
218
|
|
|
121
|
-
|
|
219
|
+
| Setup | Config needed | Deduplication | Hooks work | Client JS |
|
|
220
|
+
| ---------------------------------------------------------------- | ------------- | ------------------------- | ---------------------- | -------------- |
|
|
221
|
+
| Zero setup | None | Per render tree | Yes (within each tree) | None |
|
|
222
|
+
| `tastyIntegration({ islands: false })` | One line | Cross-tree | Yes | None |
|
|
223
|
+
| `tastyIntegration()` | One line | Cross-tree | Yes | Auto-hydration |
|
|
224
|
+
| `tastyIntegration({ css: { mode: 'extract' } })` | One line | Cross-tree and cross-page | Yes | Auto-hydration |
|
|
225
|
+
| `tastyIntegration({ islands: false, css: { mode: 'extract' } })` | One line | Cross-tree and cross-page | Yes | None |
|
|
122
226
|
|
|
123
|
-
|
|
124
|
-
// src/middleware.ts
|
|
125
|
-
import { tastyMiddleware } from '@tenphi/tasty/ssr/astro';
|
|
227
|
+
### Zero setup (static pages)
|
|
126
228
|
|
|
127
|
-
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### 2. Use tasty() components as normal
|
|
229
|
+
`tasty()` components work in Astro with **no configuration**. Each component emits its own inline `<style>` tag during server rendering via the RSC inline path. Just import and use:
|
|
131
230
|
|
|
132
231
|
```tsx
|
|
133
232
|
// src/components/Card.tsx
|
|
@@ -153,80 +252,203 @@ import Card from '../components/Card.tsx';
|
|
|
153
252
|
|
|
154
253
|
<html>
|
|
155
254
|
<body>
|
|
156
|
-
<Card>
|
|
157
|
-
<Card client:load>Island card — styles hydrated on client</Card>
|
|
255
|
+
<Card>Styled with zero setup</Card>
|
|
158
256
|
</body>
|
|
159
257
|
</html>
|
|
160
258
|
```
|
|
161
259
|
|
|
162
|
-
|
|
260
|
+
**Trade-offs**: Styles are deduplicated within each React render tree, but Astro renders separate component trees independently, so shared CSS (tokens, `@property` rules) may appear more than once. All style functions (`useGlobalStyles`, `useRawCSS`, `useKeyframes`, `useProperty`, `useFontFace`, `useCounterStyle`) work in zero-setup mode — their CSS is accumulated in the RSC cache and flushed by the next `tasty()` component in the tree.
|
|
163
261
|
|
|
164
|
-
|
|
262
|
+
Best for quick prototyping, small static sites, or trying Tasty out in Astro.
|
|
165
263
|
|
|
166
|
-
|
|
167
|
-
- **Islands** (`client:load`, `client:visible`, etc.): Styles are collected during SSR the same way. On the client, importing `@tenphi/tasty/ssr/astro` auto-hydrates the cache from `<script data-tasty-cache>`. The island's `computeStyles()` calls hit the cache during hydration.
|
|
264
|
+
### Astro Integration (recommended)
|
|
168
265
|
|
|
169
|
-
|
|
266
|
+
For production use, add `tastyIntegration()` to your Astro config. This registers middleware automatically and, by default, injects client-side hydration for islands.
|
|
170
267
|
|
|
171
|
-
|
|
268
|
+
#### With islands (default)
|
|
172
269
|
|
|
173
|
-
```
|
|
174
|
-
//
|
|
175
|
-
import '
|
|
176
|
-
import
|
|
270
|
+
```ts
|
|
271
|
+
// astro.config.mjs
|
|
272
|
+
import { defineConfig } from 'astro/config';
|
|
273
|
+
import react from '@astrojs/react';
|
|
274
|
+
import { tastyIntegration } from '@tenphi/tasty/ssr/astro';
|
|
275
|
+
|
|
276
|
+
export default defineConfig({
|
|
277
|
+
integrations: [react(), tastyIntegration()],
|
|
278
|
+
});
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
This gives you:
|
|
282
|
+
|
|
283
|
+
- A `ServerStyleCollector` per request via `AsyncLocalStorage`, deduplicating CSS across all React trees on the page
|
|
284
|
+
- A single consolidated `<style data-tasty-ssr>` injected into `</head>`
|
|
285
|
+
- A `<script>` tag pushing the rendered class names into `window.__TASTY__` for client hydration
|
|
286
|
+
- Auto-injected client hydration script (via `injectScript('before-hydration')`) so islands skip the style pipeline during hydration -- no need to import anything manually in each island component
|
|
287
|
+
|
|
288
|
+
All style functions (`useGlobalStyles`, `useRawCSS`, `useKeyframes`, `useProperty`, `useFontFace`, `useCounterStyle`) work on the server.
|
|
177
289
|
|
|
178
|
-
|
|
179
|
-
|
|
290
|
+
```astro
|
|
291
|
+
---
|
|
292
|
+
// src/pages/index.astro
|
|
293
|
+
import Card from '../components/Card.tsx';
|
|
294
|
+
import Interactive from '../components/Interactive.tsx';
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
<html>
|
|
298
|
+
<body>
|
|
299
|
+
<Card>Static -- styles in <style data-tasty-ssr></Card>
|
|
300
|
+
<Interactive client:load>Island -- cache hydrated automatically</Interactive>
|
|
301
|
+
</body>
|
|
302
|
+
</html>
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
#### Static only (no client JS)
|
|
306
|
+
|
|
307
|
+
If your site has no `client:*` islands, skip the hydration script and cache transfer:
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
// astro.config.mjs
|
|
311
|
+
import { defineConfig } from 'astro/config';
|
|
312
|
+
import react from '@astrojs/react';
|
|
313
|
+
import { tastyIntegration } from '@tenphi/tasty/ssr/astro';
|
|
314
|
+
|
|
315
|
+
export default defineConfig({
|
|
316
|
+
integrations: [react(), tastyIntegration({ islands: false })],
|
|
180
317
|
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
This gives the same middleware deduplication and hook support, but ships zero client-side JavaScript. No class-list `<script>` is emitted.
|
|
181
321
|
|
|
182
|
-
|
|
322
|
+
#### Build-wide CSS extraction
|
|
323
|
+
|
|
324
|
+
Static Astro builds can move Tasty CSS into content-hashed, browser-cacheable
|
|
325
|
+
shared and page assets:
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
export default defineConfig({
|
|
329
|
+
integrations: [
|
|
330
|
+
react(),
|
|
331
|
+
tastyIntegration({
|
|
332
|
+
islands: false,
|
|
333
|
+
css: {
|
|
334
|
+
mode: 'extract',
|
|
335
|
+
},
|
|
336
|
+
}),
|
|
337
|
+
],
|
|
338
|
+
});
|
|
183
339
|
```
|
|
184
340
|
|
|
185
|
-
|
|
341
|
+
`css.mode` defaults to `'inline'`, so existing projects keep their current
|
|
342
|
+
output. Extraction requires Astro 5 or newer and only applies to prerendered
|
|
343
|
+
production pages. Development, preview-time SSR, and on-demand routes continue
|
|
344
|
+
to receive the normal inline `<style data-tasty-ssr>` output.
|
|
345
|
+
|
|
346
|
+
Extraction writes every artifact emitted by all styled pages to a shared
|
|
347
|
+
stylesheet. Each page's strict set difference is written to a separate page
|
|
348
|
+
stylesheet. The shared link comes first and the page link follows, so shared
|
|
349
|
+
styles form the base cascade and page-only styles can override them. A fully
|
|
350
|
+
shared page omits the empty page stylesheet. If generated pages have no common
|
|
351
|
+
artifacts, each page receives only its page stylesheet.
|
|
352
|
+
|
|
353
|
+
The shared-base/page-override order is the extraction-mode cascade contract.
|
|
354
|
+
It does not preserve an inline artifact order where a page-only rule originally
|
|
355
|
+
appeared before a shared rule. Use shared styles for defaults and page-only
|
|
356
|
+
styles for overrides.
|
|
357
|
+
|
|
358
|
+
Extracted CSS preserves resource URLs verbatim. Relative URLs in an inline
|
|
359
|
+
style resolve from the page, but in an extracted stylesheet they resolve from
|
|
360
|
+
the asset directory. Use absolute URLs or data URLs when extraction is enabled.
|
|
361
|
+
Root-relative URLs such as `url(/fonts/brand.woff2)` are also safe while the
|
|
362
|
+
stylesheet stays on the page's origin. The build fails with a clear error if an
|
|
363
|
+
artifact contains a page-relative or fragment-only URL, including URL strings
|
|
364
|
+
in `image-set()`, `image()`, `src()`, and `@import`.
|
|
365
|
+
|
|
366
|
+
Assets are written under Astro's configured `build.assets` directory (for
|
|
367
|
+
example, `/_astro/tasty.shared.a1b2c3.css` and
|
|
368
|
+
`/_astro/tasty.page.d4e5f6.css`). Links include the configured Astro `base`, so
|
|
369
|
+
nested routes do not need relative-path handling. Content hashes and output are
|
|
370
|
+
deterministic for identical builds. If `build.assetsPrefix` is configured,
|
|
371
|
+
Tasty uses its CSS-specific prefix (or `fallback`) just like Astro-generated
|
|
372
|
+
stylesheets. When that prefix points to a different origin, root-relative
|
|
373
|
+
resources would resolve against the asset origin rather than the page's origin,
|
|
374
|
+
so the build rejects them as well. Tasty compares the prefix with Astro's `site`
|
|
375
|
+
when it is configured; without `site`, an absolute or protocol-relative prefix
|
|
376
|
+
is treated conservatively as cross-origin. Use a fully absolute resource URL in
|
|
377
|
+
that configuration.
|
|
378
|
+
|
|
379
|
+
### Manual middleware (advanced)
|
|
380
|
+
|
|
381
|
+
If you need to compose Tasty's middleware with other middleware (e.g., via `sequence()`), use `tastyMiddleware()` directly:
|
|
186
382
|
|
|
187
383
|
```ts
|
|
188
|
-
//
|
|
384
|
+
// src/middleware.ts
|
|
385
|
+
import { sequence } from 'astro:middleware';
|
|
386
|
+
import { tastyMiddleware } from '@tenphi/tasty/ssr/astro';
|
|
387
|
+
|
|
388
|
+
export const onRequest = sequence(tastyMiddleware(), myOtherMiddleware);
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
For island hydration with manual middleware, import the client module in a shared entry point or in each island:
|
|
392
|
+
|
|
393
|
+
```tsx
|
|
394
|
+
import '@tenphi/tasty/ssr/astro-client';
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
#### Options
|
|
398
|
+
|
|
399
|
+
```ts
|
|
400
|
+
// Skip cache state transfer (static-only, no islands)
|
|
189
401
|
export const onRequest = tastyMiddleware({ transferCache: false });
|
|
190
402
|
```
|
|
191
403
|
|
|
404
|
+
### How it works
|
|
405
|
+
|
|
406
|
+
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.
|
|
407
|
+
|
|
408
|
+
- **Static components** (no `client:*`): Styles are collected during `renderToString` and injected into `</head>` as a single `<style>` tag. No JavaScript is shipped.
|
|
409
|
+
- **Islands** (`client:load`, `client:visible`, etc.): Styles are collected during SSR the same way. On the client, the hydration script (auto-injected by `tastyIntegration()` or manually via `@tenphi/tasty/ssr/astro-client`) reads the class list from `window.__TASTY__` and pre-populates the injector's rules map. The island's `computeStyles()` calls see the class names as already registered and skip the pipeline during hydration.
|
|
410
|
+
- The middleware reads the full response body, then injects the collected CSS into `</head>` before sending the final HTML.
|
|
411
|
+
- In extraction mode, prerendered responses also carry temporary structured
|
|
412
|
+
artifact metadata. `astro:build:done` uses those collector-provided
|
|
413
|
+
boundaries to write shared and page assets and rewrite generated HTML, then
|
|
414
|
+
removes the metadata. Artifact boundaries are never inferred by splitting or
|
|
415
|
+
reparsing the generated CSS.
|
|
416
|
+
|
|
192
417
|
### CSP nonce
|
|
193
418
|
|
|
194
|
-
|
|
419
|
+
Call `configure({ nonce: '...' })` before any rendering happens. The middleware reads the nonce and applies it to injected `<style>` and `<script>` tags. In extraction mode, the external stylesheet links retain the nonce.
|
|
195
420
|
|
|
196
421
|
---
|
|
197
422
|
|
|
198
423
|
## Generic Framework Integration
|
|
199
424
|
|
|
200
|
-
Any React-based framework can integrate using `runWithCollector`, which binds a `ServerStyleCollector` to the current async context via `AsyncLocalStorage`. All
|
|
425
|
+
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.
|
|
201
426
|
|
|
202
427
|
```tsx
|
|
203
428
|
import {
|
|
204
429
|
ServerStyleCollector,
|
|
430
|
+
createServerStyleCollector,
|
|
205
431
|
runWithCollector,
|
|
206
|
-
|
|
432
|
+
hydrateTastyClasses,
|
|
207
433
|
} from '@tenphi/tasty/ssr';
|
|
208
434
|
import { renderToString } from 'react-dom/server';
|
|
209
435
|
import { hydrateRoot } from 'react-dom/client';
|
|
210
436
|
|
|
211
437
|
// ── Server ──────────────────────────────────────────────
|
|
212
438
|
|
|
213
|
-
const collector =
|
|
439
|
+
const collector = createServerStyleCollector();
|
|
214
440
|
|
|
215
|
-
const html = await runWithCollector(collector, () =>
|
|
216
|
-
renderToString(<App />)
|
|
217
|
-
);
|
|
441
|
+
const html = await runWithCollector(collector, () => renderToString(<App />));
|
|
218
442
|
|
|
219
443
|
const css = collector.getCSS();
|
|
220
|
-
const
|
|
444
|
+
const classNames = collector.getRenderedClassNames();
|
|
221
445
|
|
|
222
446
|
// Embed in your HTML template:
|
|
223
447
|
const fullHtml = `
|
|
224
448
|
<html>
|
|
225
449
|
<head>
|
|
226
450
|
<style data-tasty-ssr>${css}</style>
|
|
227
|
-
<script
|
|
228
|
-
${JSON.stringify(cacheState)}
|
|
229
|
-
</script>
|
|
451
|
+
<script>(window.__TASTY__=window.__TASTY__||[]).push(${JSON.stringify(classNames)})</script>
|
|
230
452
|
</head>
|
|
231
453
|
<body>
|
|
232
454
|
<div id="root">${html}</div>
|
|
@@ -237,7 +459,7 @@ const fullHtml = `
|
|
|
237
459
|
// ── Client ──────────────────────────────────────────────
|
|
238
460
|
|
|
239
461
|
// Before hydration:
|
|
240
|
-
|
|
462
|
+
hydrateTastyClasses(); // reads from window.__TASTY__
|
|
241
463
|
|
|
242
464
|
hydrateRoot(document.getElementById('root'), <App />);
|
|
243
465
|
```
|
|
@@ -247,7 +469,7 @@ hydrateRoot(document.getElementById('root'), <App />);
|
|
|
247
469
|
For streaming with `renderToPipeableStream`, use `flushCSS()` instead of `getCSS()`:
|
|
248
470
|
|
|
249
471
|
```tsx
|
|
250
|
-
const collector =
|
|
472
|
+
const collector = createServerStyleCollector();
|
|
251
473
|
|
|
252
474
|
const stream = await runWithCollector(collector, () =>
|
|
253
475
|
renderToPipeableStream(<App />, {
|
|
@@ -258,14 +480,16 @@ const stream = await runWithCollector(collector, () =>
|
|
|
258
480
|
stream.pipe(res);
|
|
259
481
|
},
|
|
260
482
|
onAllReady() {
|
|
261
|
-
// Flush any remaining styles +
|
|
483
|
+
// Flush any remaining styles + class list
|
|
262
484
|
const css = collector.flushCSS();
|
|
263
485
|
if (css) res.write(`<style data-tasty-ssr>${css}</style>`);
|
|
264
486
|
|
|
265
|
-
const
|
|
266
|
-
res.write(
|
|
487
|
+
const classNames = collector.getRenderedClassNames();
|
|
488
|
+
res.write(
|
|
489
|
+
`<script>(window.__TASTY__=window.__TASTY__||[]).push(${JSON.stringify(classNames)})</script>`,
|
|
490
|
+
);
|
|
267
491
|
},
|
|
268
|
-
})
|
|
492
|
+
}),
|
|
269
493
|
);
|
|
270
494
|
```
|
|
271
495
|
|
|
@@ -275,52 +499,78 @@ const stream = await runWithCollector(collector, () =>
|
|
|
275
499
|
|
|
276
500
|
### Entry points
|
|
277
501
|
|
|
278
|
-
| Import path
|
|
279
|
-
|
|
280
|
-
| `@tenphi/tasty/ssr`
|
|
281
|
-
| `@tenphi/tasty/ssr/next`
|
|
282
|
-
| `@tenphi/tasty/ssr/
|
|
502
|
+
| Import path | Description |
|
|
503
|
+
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
504
|
+
| `@tenphi/tasty/ssr` | Core SSR API: `ServerStyleCollector`, `createServerStyleCollector`, `runWithCollector`, `hydrateTastyClasses` |
|
|
505
|
+
| `@tenphi/tasty/ssr/next` | Next.js App Router: `TastyRegistry` component |
|
|
506
|
+
| `@tenphi/tasty/ssr/next-config` | Next.js config wrapper: shared, content-hashed global stylesheet |
|
|
507
|
+
| `@tenphi/tasty/ssr/astro` | Astro: `tastyIntegration`, `tastyMiddleware` |
|
|
508
|
+
| `@tenphi/tasty/ssr/astro-client` | Astro: client-side cache hydration (auto-injected by integration, or import manually) |
|
|
509
|
+
| `@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()`. |
|
|
283
510
|
|
|
284
511
|
### `ServerStyleCollector`
|
|
285
512
|
|
|
286
513
|
Server-safe style collector. One instance per request.
|
|
287
514
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
|
291
|
-
|
|
|
292
|
-
| `
|
|
293
|
-
| `
|
|
294
|
-
| `
|
|
295
|
-
| `
|
|
296
|
-
| `
|
|
297
|
-
| `
|
|
298
|
-
| `
|
|
515
|
+
Constructor: `new ServerStyleCollector(namePrefix?)`, or use the `createServerStyleCollector(namePrefix?)` factory. The optional `namePrefix` overrides the value from `configure({ namePrefix })`; in normal usage you pass nothing and let the global config drive it. See [Configuration: Name prefix](configuration.md#name-prefix).
|
|
516
|
+
|
|
517
|
+
| Method | Description |
|
|
518
|
+
| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
519
|
+
| `allocateClassName(cacheKey)` | Allocate a deterministic, content-hashed class name for a cache key (e.g. `t1a2b3` with the default prefix). The same `cacheKey` always produces the same class name on server and client when both share the same `namePrefix`. Returns `{ className, isNewAllocation }`. |
|
|
520
|
+
| `collectChunk(cacheKey, className, rules)` | Record CSS rules for a chunk. Deduplicated by `cacheKey`. |
|
|
521
|
+
| `collectKeyframes(name, css)` | Record a `@keyframes` rule. Deduplicated by name. |
|
|
522
|
+
| `allocateKeyframeName(providedName?)` | Allocate a keyframe name. Returns `providedName` if given, otherwise generates one using `${namePrefix}k${counter}` (e.g. `tk0`, `tk1`, ...). |
|
|
523
|
+
| `collectProperty(name, css)` | Record a `@property` rule. Deduplicated by name. |
|
|
524
|
+
| `collectFontFace(key, css)` | Record a `@font-face` rule. Deduplicated by content hash. |
|
|
525
|
+
| `collectCounterStyle(name, css)` | Record a `@counter-style` rule. Deduplicated by name. |
|
|
526
|
+
| `allocateCounterStyleName(providedName?)` | Allocate a counter-style name. Returns `providedName` if given, otherwise generates one using `${namePrefix}c${counter}` (e.g. `tc0`, `tc1`, ...). |
|
|
527
|
+
| `collectGlobalStyles(key, css)` | Record global styles (from `useGlobalStyles`). Deduplicated by key. |
|
|
528
|
+
| `collectRawCSS(key, css)` | Record raw CSS text (from `useRawCSS`). Deduplicated by key. |
|
|
529
|
+
| `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
|
+
| `getCSS()` | Get all collected CSS as a single string. For non-streaming SSR. |
|
|
531
|
+
| `flushCSS()` | Get only CSS collected since the last flush. For streaming SSR. |
|
|
532
|
+
| `getRenderedClassNames()` | Get the list of class names rendered so far. Serialized to `window.__TASTY__` for client hydration via `hydrateTastyClasses()`. |
|
|
299
533
|
|
|
300
534
|
### `TastyRegistry`
|
|
301
535
|
|
|
302
536
|
Next.js App Router component. Props:
|
|
303
537
|
|
|
304
|
-
| Prop
|
|
305
|
-
|
|
306
|
-
| `children`
|
|
307
|
-
| `transferCache`
|
|
538
|
+
| Prop | Type | Default | Description |
|
|
539
|
+
| ------------------ | ----------------- | --------- | ------------------------------------------------------------------------- |
|
|
540
|
+
| `children` | `ReactNode` | required | Application tree |
|
|
541
|
+
| `transferCache` | `boolean` | `true` | Embed cache state script for zero-cost hydration |
|
|
542
|
+
| `sharedStylesheet` | `string \| false` | generated | Override the shared URL, or disable generated shared CSS for the registry |
|
|
543
|
+
|
|
544
|
+
### `withTastyNext(options)`
|
|
545
|
+
|
|
546
|
+
Next.js configuration wrapper exported from
|
|
547
|
+
`@tenphi/tasty/ssr/next-config`. It generates a shared stylesheet from eager
|
|
548
|
+
Tasty configuration artifacts and wires its URL and immutable cache header
|
|
549
|
+
into the Next config. Route-specific CSS continues through `TastyRegistry`.
|
|
550
|
+
|
|
551
|
+
### `tastyIntegration(options?)`
|
|
552
|
+
|
|
553
|
+
Astro integration factory. Registers middleware and optionally injects client hydration.
|
|
554
|
+
|
|
555
|
+
| Option | Type | Default | Description |
|
|
556
|
+
| --------- | --------- | ------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
557
|
+
| `islands` | `boolean` | `true` | When `true`, injects client hydration script and enables `transferCache`. When `false`, no client JS is shipped. |
|
|
308
558
|
|
|
309
559
|
### `tastyMiddleware(options?)`
|
|
310
560
|
|
|
311
|
-
Astro middleware factory.
|
|
561
|
+
Astro middleware factory. Use for manual middleware composition.
|
|
312
562
|
|
|
313
|
-
| Option
|
|
314
|
-
|
|
315
|
-
| `transferCache` | `boolean` | `true`
|
|
563
|
+
| Option | Type | Default | Description |
|
|
564
|
+
| --------------- | --------- | ------- | --------------------------------------------- |
|
|
565
|
+
| `transferCache` | `boolean` | `true` | Embed cache state script for island hydration |
|
|
316
566
|
|
|
317
|
-
### `
|
|
567
|
+
### `hydrateTastyClasses(classes?)`
|
|
318
568
|
|
|
319
|
-
Pre-populate the client injector
|
|
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).
|
|
320
570
|
|
|
321
571
|
### `runWithCollector(collector, fn)`
|
|
322
572
|
|
|
323
|
-
Run a function with a `ServerStyleCollector` bound to the current async context via `AsyncLocalStorage`. All `
|
|
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 `useCounterStyle()` — will find this collector.
|
|
324
574
|
|
|
325
575
|
---
|
|
326
576
|
|
|
@@ -328,25 +578,31 @@ Run a function with a `ServerStyleCollector` bound to the current async context
|
|
|
328
578
|
|
|
329
579
|
### Styles flash on page load (FOUC)
|
|
330
580
|
|
|
331
|
-
The `TastyRegistry` or `
|
|
581
|
+
The `TastyRegistry` or `tastyIntegration` is missing. Ensure your layout wraps the app with `TastyRegistry` (Next.js) or that `tastyIntegration()` is in your Astro config (or `tastyMiddleware()` is registered manually).
|
|
332
582
|
|
|
333
583
|
### Hydration mismatch warnings
|
|
334
584
|
|
|
335
|
-
Class names are deterministic for the same render order. If you see mismatches, ensure `
|
|
585
|
+
Class names are deterministic for the same render 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()` before `hydrateRoot()`.
|
|
586
|
+
|
|
587
|
+
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.
|
|
336
588
|
|
|
337
589
|
### Styles duplicated after hydration
|
|
338
590
|
|
|
339
|
-
|
|
591
|
+
**Global CSS** (`:root` tokens, `@property`, `globalStyles`, `@font-face`, `@counter-style`, `@function`) configured via `configure()` is automatically deduplicated. When Tasty detects an inline or extracted `[data-tasty-ssr]` stylesheet in the document, it skips client-side injection of globals that were already rendered by the SSR collector. This means `configure()` can be called with the full config on both server and client — no `typeof window === 'undefined'` guard is needed.
|
|
592
|
+
|
|
593
|
+
**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:
|
|
340
594
|
|
|
341
595
|
```tsx
|
|
342
|
-
import {
|
|
596
|
+
import { hydrateTastyClasses } from '@tenphi/tasty/ssr';
|
|
343
597
|
|
|
344
|
-
|
|
598
|
+
hydrateTastyClasses();
|
|
345
599
|
hydrateRoot(root, <App />);
|
|
346
600
|
|
|
347
|
-
// Optional: remove SSR style tags after hydration
|
|
348
|
-
document.querySelectorAll('style[data-tasty-ssr]').forEach(el => el.remove());
|
|
349
|
-
document.querySelectorAll('script
|
|
601
|
+
// Optional: remove SSR style tags and class-list scripts after hydration
|
|
602
|
+
document.querySelectorAll('style[data-tasty-ssr]').forEach((el) => el.remove());
|
|
603
|
+
document.querySelectorAll('script').forEach((el) => {
|
|
604
|
+
if (el.textContent?.includes('__TASTY__')) el.remove();
|
|
605
|
+
});
|
|
350
606
|
```
|
|
351
607
|
|
|
352
608
|
### `AsyncLocalStorage` not available
|