@tenphi/tasty 0.0.0-snapshot.d2dcdeb → 0.0.0-snapshot.d4ccea8
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 +181 -131
- package/dist/{ssr/async-storage.js → async-storage-DKK-wTD4.js} +2 -2
- package/dist/async-storage-DKK-wTD4.js.map +1 -0
- package/dist/babel-eMs_zZjM.d.ts +83 -0
- package/dist/{ssr/collector.js → collector-BjkQEtbh.js} +111 -38
- package/dist/collector-BjkQEtbh.js.map +1 -0
- package/dist/{ssr/collector.d.ts → collector-DzeNxHOq.d.ts} +58 -7
- package/dist/config-DMslHK_e.d.ts +1298 -0
- package/dist/config-DX126v_w.js +12318 -0
- package/dist/config-DX126v_w.js.map +1 -0
- package/dist/{ssr/context.js → context-CA8YKeMn.js} +11 -3
- 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-CMn-Tj2X.js +1573 -0
- package/dist/core-CMn-Tj2X.js.map +1 -0
- package/dist/{zero/extractor.js → css-writer-C8ShXK5R.js} +148 -24
- package/dist/css-writer-C8ShXK5R.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-BkstDvlN.js} +59 -2
- package/dist/format-rules-BkstDvlN.js.map +1 -0
- package/dist/{ssr/hydrate.js → hydrate-DXXWZgm0.js} +4 -12
- package/dist/hydrate-DXXWZgm0.js.map +1 -0
- package/dist/index-BcsaHo8x.d.ts +1568 -0
- package/dist/index-CC0wykKn.d.ts +1909 -0
- package/dist/index.d.ts +5 -51
- package/dist/index.js +922 -36
- package/dist/index.js.map +1 -0
- package/dist/keyframes-Z0YLVOWq.js +493 -0
- package/dist/keyframes-Z0YLVOWq.js.map +1 -0
- package/dist/{utils/merge-styles.d.ts → merge-styles-BTFAkYSD.d.ts} +3 -3
- package/dist/{utils/merge-styles.js → merge-styles-Dpueyn82.js} +3 -4
- package/dist/merge-styles-Dpueyn82.js.map +1 -0
- package/dist/{utils/resolve-recipes.js → resolve-recipes-B2Vjr7ET.js} +4 -6
- package/dist/resolve-recipes-B2Vjr7ET.js.map +1 -0
- package/dist/ssr/astro-client.js +1 -1
- package/dist/ssr/astro-client.js.map +1 -1
- package/dist/ssr/astro-middleware-static.d.ts +16 -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 +8 -6
- package/dist/ssr/astro-middleware.js +10 -10
- package/dist/ssr/astro-middleware.js.map +1 -1
- package/dist/ssr/astro.d.ts +2 -17
- package/dist/ssr/astro.js +29 -21
- package/dist/ssr/astro.js.map +1 -1
- package/dist/ssr/index.d.ts +37 -4
- package/dist/ssr/index.js +5 -5
- package/dist/ssr/index.js.map +1 -1
- package/dist/ssr/next.d.ts +2 -3
- package/dist/ssr/next.js +7 -8
- package/dist/ssr/next.js.map +1 -1
- 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 -81
- package/dist/zero/babel.js +45 -29
- 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 +18 -10
- package/docs/adoption.md +56 -31
- package/docs/ai-agents.md +224 -0
- package/docs/comparison.md +69 -40
- package/docs/configuration.md +501 -72
- package/docs/debug.md +22 -18
- package/docs/design-system.md +43 -24
- package/docs/dsl.md +512 -179
- package/docs/getting-started.md +60 -39
- package/docs/injector.md +110 -32
- package/docs/methodology.md +68 -36
- package/docs/migration-v3.md +285 -0
- package/docs/pipeline.md +741 -0
- package/docs/plugins.md +353 -0
- package/docs/react-api.md +164 -40
- package/docs/runtime-benchmarks.md +216 -0
- package/docs/ssr.md +92 -81
- package/docs/styles.md +65 -16
- package/docs/tasty-static.md +149 -99
- package/package.json +49 -21
- package/tasty.config.ts +6 -0
- package/dist/_virtual/_rolldown/runtime.js +0 -7
- package/dist/chunks/cacheKey.d.ts +0 -1
- 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 -335
- package/dist/compute-styles.js.map +0 -1
- package/dist/config.d.ts +0 -407
- package/dist/config.js +0 -587
- 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 -36
- package/dist/hooks/useCounterStyle.js +0 -65
- package/dist/hooks/useCounterStyle.js.map +0 -1
- package/dist/hooks/useFontFace.d.ts +0 -45
- package/dist/hooks/useFontFace.js +0 -66
- package/dist/hooks/useFontFace.js.map +0 -1
- package/dist/hooks/useGlobalStyles.d.ts +0 -46
- package/dist/hooks/useGlobalStyles.js +0 -88
- package/dist/hooks/useGlobalStyles.js.map +0 -1
- package/dist/hooks/useKeyframes.d.ts +0 -58
- package/dist/hooks/useKeyframes.js +0 -55
- package/dist/hooks/useKeyframes.js.map +0 -1
- package/dist/hooks/useProperty.d.ts +0 -81
- package/dist/hooks/useProperty.js +0 -96
- package/dist/hooks/useProperty.js.map +0 -1
- package/dist/hooks/useRawCSS.d.ts +0 -22
- package/dist/hooks/useRawCSS.js +0 -103
- package/dist/hooks/useRawCSS.js.map +0 -1
- package/dist/hooks/useStyles.d.ts +0 -40
- package/dist/hooks/useStyles.js +0 -31
- package/dist/hooks/useStyles.js.map +0 -1
- package/dist/injector/index.d.ts +0 -182
- package/dist/injector/index.js +0 -185
- package/dist/injector/index.js.map +0 -1
- package/dist/injector/injector.d.ts +0 -198
- package/dist/injector/injector.js +0 -651
- package/dist/injector/injector.js.map +0 -1
- package/dist/injector/sheet-manager.d.ts +0 -132
- package/dist/injector/sheet-manager.js +0 -699
- package/dist/injector/sheet-manager.js.map +0 -1
- package/dist/injector/types.d.ts +0 -235
- 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 -87
- 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/rsc-cache.js +0 -79
- package/dist/rsc-cache.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 -58
- package/dist/ssr/collect-auto-properties.js.map +0 -1
- package/dist/ssr/collector.js.map +0 -1
- package/dist/ssr/context.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 -29
- package/dist/ssr/hydrate.js.map +0 -1
- package/dist/ssr/ssr-collector-ref.js +0 -29
- 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 -248
- 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/deps-equal.js +0 -15
- package/dist/utils/deps-equal.js.map +0 -1
- 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/hash.js +0 -14
- package/dist/utils/hash.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 -58
- 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/docs/react-api.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# React API
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use `tasty()` to turn state-aware style definitions into normal React components, extend existing Tasty components, and expose typed styling APIs to consumers.
|
|
4
|
+
|
|
5
|
+
All Tasty style functions — `tasty()` components, `useStyles()`, `useGlobalStyles()`, `useRawCSS()`, `useKeyframes()`, `useProperty()`, `useFontFace()`, `useCounterStyle()`, and `useFunction()` — are hook-free and compatible with React Server Components. No `'use client'` directive is needed. For state maps, tokens, units, and extension semantics, see [Style DSL](dsl.md). For global configuration, see [Configuration](configuration.md). For the broader docs map, see the [Docs Hub](README.md).
|
|
4
6
|
|
|
5
7
|
> **Note:** This file was previously named `runtime.md`. All functionality documented here works in both server and client contexts — "runtime" referred to style computation during React rendering, not to client-side JavaScript.
|
|
6
8
|
|
|
@@ -42,6 +44,32 @@ const PrimaryButton = tasty(Button, {
|
|
|
42
44
|
|
|
43
45
|
Style maps merge intelligently — see [Style DSL — Extending vs. Replacing State Maps](dsl.md#extending-vs-replacing-state-maps) for extend mode, replace mode, `@inherit`, `null`, and `false` tombstones.
|
|
44
46
|
|
|
47
|
+
`tasty(Component, ...)` always wraps `Component` and forwards every prop to it, so `Component` must be Tasty-aware (i.e. it accepts `styles`, `mods`, `qa`, etc. and renders them through its own Tasty pipeline). To apply styles to a third-party component or a string DOM tag via `className`, use the options-only form with `as`:
|
|
48
|
+
|
|
49
|
+
```jsx
|
|
50
|
+
import NextLink from 'next/link';
|
|
51
|
+
|
|
52
|
+
const Link = tasty({
|
|
53
|
+
as: NextLink,
|
|
54
|
+
styles: {
|
|
55
|
+
color: { '': '#accent-text', ':hover': '#text' },
|
|
56
|
+
textDecoration: 'underline',
|
|
57
|
+
},
|
|
58
|
+
styleProps: ['padding'],
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const Span = tasty({
|
|
62
|
+
as: 'span',
|
|
63
|
+
styles: { preset: 'strong' },
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
<Link href="/blog" padding="1x">
|
|
67
|
+
Blog
|
|
68
|
+
</Link>;
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The wrapped component only needs to forward `className` (and ideally `style`/`ref`). Tasty-specific props (`qa`, `qaVal`, `mods`, `tokens`, `styleProps`, `modProps`, `tokenProps`) are consumed by Tasty and never leak to the DOM.
|
|
72
|
+
|
|
45
73
|
---
|
|
46
74
|
|
|
47
75
|
## Style Props
|
|
@@ -60,7 +88,7 @@ const FlexibleBox = tasty({
|
|
|
60
88
|
|
|
61
89
|
<FlexibleBox gap="2x" align="center" fill="#surface">
|
|
62
90
|
Content
|
|
63
|
-
</FlexibleBox
|
|
91
|
+
</FlexibleBox>;
|
|
64
92
|
```
|
|
65
93
|
|
|
66
94
|
Style props accept state maps, so responsive values work through the same API:
|
|
@@ -74,6 +102,26 @@ Style props accept state maps, so responsive values work through the same API:
|
|
|
74
102
|
|
|
75
103
|
For predefined style prop lists (`FLOW_STYLES`, `POSITION_STYLES`, `DIMENSION_STYLES`, etc.) and guidance on which props to expose per component category, see [Methodology — styleProps as the public API](methodology.md#styleprops-as-the-public-api).
|
|
76
104
|
|
|
105
|
+
### Always-available style props
|
|
106
|
+
|
|
107
|
+
A small set of style properties — `display`, `font`, `preset`, `hide`, `whiteSpace`, `opacity`, and `transition` — are always harvested as style props on every `tasty()` component, even when `styleProps` is omitted. This means you can pass `<Card display="grid" />` or `<Text preset="h1" />` without declaring them. When you do declare `styleProps`, these base props are unioned with your list (not replaced).
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## `Element`
|
|
112
|
+
|
|
113
|
+
`Element` is the unstyled base component exported from the main entry — equivalent to `tasty({})` (a `div` with no default styles). It accepts all Tasty props (`styles`, `styleProps`, `mods`, `tokens`, `as`, `qa`, `theme`, the `is*` props, etc.) and is useful as a generic styled box or as a building block for layout primitives.
|
|
114
|
+
|
|
115
|
+
```jsx
|
|
116
|
+
import { Element } from '@tenphi/tasty';
|
|
117
|
+
|
|
118
|
+
<Element as="section" padding="4x" fill="#surface">
|
|
119
|
+
Content
|
|
120
|
+
</Element>;
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
> Note: `Element` shadows the global DOM `Element` type when imported from `@tenphi/tasty`. In files that need the DOM type, alias the import: `import { Element as TastyElement } from '@tenphi/tasty'`.
|
|
124
|
+
|
|
77
125
|
---
|
|
78
126
|
|
|
79
127
|
## Mod Props
|
|
@@ -112,9 +160,9 @@ Map modifier names to type descriptors for precise TypeScript types:
|
|
|
112
160
|
```tsx
|
|
113
161
|
const Button = tasty({
|
|
114
162
|
modProps: {
|
|
115
|
-
isLoading: Boolean,
|
|
116
|
-
isSelected: Boolean,
|
|
117
|
-
size: ['small', 'medium', 'large'] as const,
|
|
163
|
+
isLoading: Boolean, // isLoading?: boolean
|
|
164
|
+
isSelected: Boolean, // isSelected?: boolean
|
|
165
|
+
size: ['small', 'medium', 'large'] as const, // size?: 'small' | 'medium' | 'large'
|
|
118
166
|
},
|
|
119
167
|
styles: {
|
|
120
168
|
padding: { '': '2x 4x', 'size=small': '1x 2x', 'size=large': '3x 6x' },
|
|
@@ -122,18 +170,20 @@ const Button = tasty({
|
|
|
122
170
|
},
|
|
123
171
|
});
|
|
124
172
|
|
|
125
|
-
<Button isLoading size="large">
|
|
173
|
+
<Button isLoading size="large">
|
|
174
|
+
Submit
|
|
175
|
+
</Button>;
|
|
126
176
|
// Renders: <button data-is-loading="" data-size="large">Submit</button>
|
|
127
177
|
```
|
|
128
178
|
|
|
129
179
|
Available type descriptors:
|
|
130
180
|
|
|
131
|
-
| Descriptor
|
|
132
|
-
|
|
133
|
-
| `Boolean`
|
|
134
|
-
| `String`
|
|
135
|
-
| `Number`
|
|
136
|
-
| `['a', 'b'] as const` | `'a' \| 'b'`
|
|
181
|
+
| Descriptor | TypeScript type | Example |
|
|
182
|
+
| --------------------- | --------------- | ----------------------------------- |
|
|
183
|
+
| `Boolean` | `boolean` | `isLoading: Boolean` |
|
|
184
|
+
| `String` | `string` | `label: String` |
|
|
185
|
+
| `Number` | `number` | `count: Number` |
|
|
186
|
+
| `['a', 'b'] as const` | `'a' \| 'b'` | `size: ['sm', 'md', 'lg'] as const` |
|
|
137
187
|
|
|
138
188
|
### Merge with `mods`
|
|
139
189
|
|
|
@@ -146,11 +196,11 @@ Mod props and the `mods` object can be used together. Mod props take precedence:
|
|
|
146
196
|
|
|
147
197
|
### When to use `modProps` vs `mods`
|
|
148
198
|
|
|
149
|
-
| Use case
|
|
150
|
-
|
|
199
|
+
| Use case | Recommendation |
|
|
200
|
+
| -------------------------------------------- | -------------------------------------------------------- |
|
|
151
201
|
| Component has a fixed set of known modifiers | `modProps` — cleaner API, better TypeScript autocomplete |
|
|
152
|
-
| Component needs arbitrary/dynamic modifiers
|
|
153
|
-
| Both fixed and dynamic
|
|
202
|
+
| Component needs arbitrary/dynamic modifiers | `mods` — open-ended `Record<string, ModValue>` |
|
|
203
|
+
| Both fixed and dynamic | Combine: `modProps` for known keys, `mods` for ad-hoc |
|
|
154
204
|
|
|
155
205
|
For architecture guidance on when to use modifiers vs `styleProps`, see [Methodology — modProps and mods](methodology.md#modprops-and-mods).
|
|
156
206
|
|
|
@@ -180,7 +230,7 @@ const ProgressBar = tasty({
|
|
|
180
230
|
|
|
181
231
|
<ProgressBar progress="75%" accentColor="#purple" />
|
|
182
232
|
// 'progress' → $progress → --progress
|
|
183
|
-
// 'accentColor' → #accent → --accent-color
|
|
233
|
+
// 'accentColor' → #accent → --accent-color
|
|
184
234
|
```
|
|
185
235
|
|
|
186
236
|
### Object form
|
|
@@ -196,7 +246,7 @@ const Card = tasty({
|
|
|
196
246
|
styles: { padding: '$card-size', fill: '#card-accent' },
|
|
197
247
|
});
|
|
198
248
|
|
|
199
|
-
<Card size="4x" color="#purple"
|
|
249
|
+
<Card size="4x" color="#purple" />;
|
|
200
250
|
```
|
|
201
251
|
|
|
202
252
|
### Merge with `tokens`
|
|
@@ -215,11 +265,11 @@ const Bar = tasty({
|
|
|
215
265
|
|
|
216
266
|
### When to use `tokenProps` vs `tokens`
|
|
217
267
|
|
|
218
|
-
| Use case
|
|
219
|
-
|
|
220
|
-
| Component has a fixed set of known token keys
|
|
221
|
-
| Component needs arbitrary/dynamic token values | `tokens` — open-ended `Record<string, TokenValue>`
|
|
222
|
-
| Both fixed and dynamic
|
|
268
|
+
| Use case | Recommendation |
|
|
269
|
+
| ---------------------------------------------- | ---------------------------------------------------------- |
|
|
270
|
+
| Component has a fixed set of known token keys | `tokenProps` — cleaner API, better TypeScript autocomplete |
|
|
271
|
+
| Component needs arbitrary/dynamic token values | `tokens` — open-ended `Record<string, TokenValue>` |
|
|
272
|
+
| Both fixed and dynamic | Combine: `tokenProps` for known keys, `tokens` for ad-hoc |
|
|
223
273
|
|
|
224
274
|
For architecture guidance, see [Methodology — tokenProps](methodology.md#tokenprops).
|
|
225
275
|
|
|
@@ -238,11 +288,11 @@ const Button = tasty({
|
|
|
238
288
|
variants: {
|
|
239
289
|
default: { fill: '#blue', color: '#white' },
|
|
240
290
|
danger: { fill: '#red', color: '#white' },
|
|
241
|
-
outline: { fill: '
|
|
291
|
+
outline: { fill: '#clear', color: '#blue', border: '1bw solid #blue' },
|
|
242
292
|
},
|
|
243
293
|
});
|
|
244
294
|
|
|
245
|
-
<Button variant="danger">Delete</Button
|
|
295
|
+
<Button variant="danger">Delete</Button>;
|
|
246
296
|
```
|
|
247
297
|
|
|
248
298
|
### Extending Variants with Base State Maps
|
|
@@ -298,7 +348,7 @@ const Card = tasty({
|
|
|
298
348
|
<Card>
|
|
299
349
|
<Card.Title>Card Title</Card.Title>
|
|
300
350
|
<Card.Content>Card content</Card.Content>
|
|
301
|
-
</Card
|
|
351
|
+
</Card>;
|
|
302
352
|
```
|
|
303
353
|
|
|
304
354
|
Each entry in `elements` can be a tag name string or a config object:
|
|
@@ -324,7 +374,7 @@ const Card = tasty({
|
|
|
324
374
|
|
|
325
375
|
<Card>
|
|
326
376
|
<div data-element="Title">Card Title</div>
|
|
327
|
-
</Card
|
|
377
|
+
</Card>;
|
|
328
378
|
```
|
|
329
379
|
|
|
330
380
|
### Selector Affix (`$`)
|
|
@@ -357,7 +407,7 @@ On the client, CSS is injected synchronously into the DOM (idempotent via the in
|
|
|
357
407
|
|
|
358
408
|
All style functions below are plain functions (not React hooks) and can be used in any environment: client components, SSR with a `ServerStyleCollector`, and React Server Components. They retain their `use` prefix for backward compatibility, but do not use any React hooks internally.
|
|
359
409
|
|
|
360
|
-
In server-only contexts
|
|
410
|
+
In server-only contexts, components that use only Tasty style functions ship no Tasty styling runtime. Astro without `client:*` directives produces no client JavaScript; server-only Next.js RSC follows the same Tasty architecture, while final output depends on the application deployment. Tasty never forces the `'use client'` boundary — that decision belongs to your component when it needs React interactivity (state, effects, event handlers).
|
|
361
411
|
|
|
362
412
|
### useStyles
|
|
363
413
|
|
|
@@ -395,6 +445,15 @@ function ThemeStyles() {
|
|
|
395
445
|
}
|
|
396
446
|
```
|
|
397
447
|
|
|
448
|
+
A slot — the `id`, or the selector when no `id` is given — holds exactly one
|
|
449
|
+
injection **per `root`**. So:
|
|
450
|
+
|
|
451
|
+
- Changing the styles replaces the previous CSS rather than adding to it.
|
|
452
|
+
- Passing styles that produce no CSS (for example `{}`) clears the slot.
|
|
453
|
+
- The same selector used in two shadow roots keeps a separate injection in each.
|
|
454
|
+
- Two independent call sites that share a selector share a slot, and the last
|
|
455
|
+
render wins. Give them distinct `id`s if they should coexist.
|
|
456
|
+
|
|
398
457
|
### useRawCSS
|
|
399
458
|
|
|
400
459
|
Inject raw CSS strings. Accepts an optional `id` in the options for update tracking — when the CSS changes for the same id, the previous injection is replaced:
|
|
@@ -411,6 +470,9 @@ function GlobalReset() {
|
|
|
411
470
|
}
|
|
412
471
|
```
|
|
413
472
|
|
|
473
|
+
An `id` slot holds one injection per `root`. Without an `id` the CSS is deduped
|
|
474
|
+
by content and permanent — there is nothing to replace it with later.
|
|
475
|
+
|
|
414
476
|
### useKeyframes
|
|
415
477
|
|
|
416
478
|
Inject `@keyframes` rules and return the generated animation name:
|
|
@@ -424,14 +486,14 @@ function Spinner() {
|
|
|
424
486
|
from: { transform: 'rotate(0deg)' },
|
|
425
487
|
to: { transform: 'rotate(360deg)' },
|
|
426
488
|
},
|
|
427
|
-
{ name: 'spin' }
|
|
489
|
+
{ name: 'spin' },
|
|
428
490
|
);
|
|
429
491
|
|
|
430
492
|
return <div style={{ animation: `${spin} 1s linear infinite` }} />;
|
|
431
493
|
}
|
|
432
494
|
```
|
|
433
495
|
|
|
434
|
-
`useKeyframes()` also supports a factory function.
|
|
496
|
+
`useKeyframes()` also supports a factory function. Without a `name` the factory runs on every invocation and deduplication is handled internally by content hash; with a `name`, matching deps skip the factory entirely:
|
|
435
497
|
|
|
436
498
|
```tsx
|
|
437
499
|
function Pulse({ scale }: { scale: number }) {
|
|
@@ -440,13 +502,24 @@ function Pulse({ scale }: { scale: number }) {
|
|
|
440
502
|
'0%': { transform: 'scale(1)' },
|
|
441
503
|
'100%': { transform: `scale(${scale})` },
|
|
442
504
|
}),
|
|
443
|
-
[scale]
|
|
505
|
+
[scale],
|
|
506
|
+
{ name: 'pulse' },
|
|
444
507
|
);
|
|
445
508
|
|
|
446
|
-
return
|
|
509
|
+
return (
|
|
510
|
+
<div
|
|
511
|
+
style={{ animation: `${pulse} 500ms ease-in-out alternate infinite` }}
|
|
512
|
+
/>
|
|
513
|
+
);
|
|
447
514
|
}
|
|
448
515
|
```
|
|
449
516
|
|
|
517
|
+
Passing `name` claims a slot owned by that one call site, per `root` — much like
|
|
518
|
+
`id` in `useGlobalStyles()` and `useRawCSS()`. When the steps change the previous
|
|
519
|
+
`@keyframes` rule is disposed and the name is reused, so the rules don't
|
|
520
|
+
accumulate and the returned name stays stable. Anonymous keyframes are permanent
|
|
521
|
+
and shared by content.
|
|
522
|
+
|
|
450
523
|
### useProperty
|
|
451
524
|
|
|
452
525
|
Register a CSS `@property` rule so a custom property can animate smoothly:
|
|
@@ -493,8 +566,16 @@ For multiple weights/styles, pass an array:
|
|
|
493
566
|
|
|
494
567
|
```tsx
|
|
495
568
|
useFontFace('Brand Sans', [
|
|
496
|
-
{
|
|
497
|
-
|
|
569
|
+
{
|
|
570
|
+
src: 'url("/fonts/brand-regular.woff2") format("woff2")',
|
|
571
|
+
fontWeight: 400,
|
|
572
|
+
fontDisplay: 'swap',
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
src: 'url("/fonts/brand-bold.woff2") format("woff2")',
|
|
576
|
+
fontWeight: 700,
|
|
577
|
+
fontDisplay: 'swap',
|
|
578
|
+
},
|
|
498
579
|
]);
|
|
499
580
|
```
|
|
500
581
|
|
|
@@ -512,11 +593,14 @@ Inject a `@counter-style` rule and get back the counter style name. Permanent
|
|
|
512
593
|
import { useCounterStyle } from '@tenphi/tasty';
|
|
513
594
|
|
|
514
595
|
function EmojiList() {
|
|
515
|
-
const styleName = useCounterStyle(
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
596
|
+
const styleName = useCounterStyle(
|
|
597
|
+
{
|
|
598
|
+
system: 'cyclic',
|
|
599
|
+
symbols: '"👍"',
|
|
600
|
+
suffix: '" "',
|
|
601
|
+
},
|
|
602
|
+
{ name: 'thumbs' },
|
|
603
|
+
);
|
|
520
604
|
|
|
521
605
|
return (
|
|
522
606
|
<ol style={{ listStyleType: styleName }}>
|
|
@@ -536,6 +620,46 @@ function useCounterStyle(
|
|
|
536
620
|
): string;
|
|
537
621
|
```
|
|
538
622
|
|
|
623
|
+
### useFunction
|
|
624
|
+
|
|
625
|
+
Register a CSS `@function` (custom function). Permanent — no cleanup on unmount. Deduplicates by function name. The function name accepts `$$name` (matching the call site `$$name(...)`), `$name`, or `--name`.
|
|
626
|
+
|
|
627
|
+
```tsx
|
|
628
|
+
import { tasty, useFunction } from '@tenphi/tasty';
|
|
629
|
+
|
|
630
|
+
const Box = tasty({ styles: { margin: '$$negative(10px) top' } });
|
|
631
|
+
|
|
632
|
+
function Layout() {
|
|
633
|
+
useFunction('$$negative', { args: ['$value'], result: '(-1 * $value)' });
|
|
634
|
+
return <Box />;
|
|
635
|
+
}
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
Call the function through the Tasty DSL rather than a raw `style` prop. An inline `style` value reaches the browser unparsed, so the `$$name(...)` sugar is never expanded — and under `configure({ polyfills: { functions: true } })` it silently does nothing, because the polyfill rewrites calls at parse time.
|
|
639
|
+
|
|
640
|
+
Inside a `tasty()` component you call functions with the same `$$name(...)` sugar:
|
|
641
|
+
|
|
642
|
+
```tsx
|
|
643
|
+
const Box = tasty({
|
|
644
|
+
styles: {
|
|
645
|
+
'@function': { $$negative: { args: ['$value'], result: '(-1 * $value)' } },
|
|
646
|
+
margin: '$$negative(10px) top',
|
|
647
|
+
},
|
|
648
|
+
});
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
Signature:
|
|
652
|
+
|
|
653
|
+
```ts
|
|
654
|
+
function useFunction(
|
|
655
|
+
name: string,
|
|
656
|
+
definition: FunctionDefinition,
|
|
657
|
+
options?: { root?: Document | ShadowRoot },
|
|
658
|
+
): void;
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
See the [Functions section of the DSL reference](dsl.md#functions-function) for the full descriptor shape, token conventions, and value-sugar support. `@function` is an experimental CSS feature — unsupported browsers safely ignore the native rule, or enable the [`polyfills.functions`](configuration.md#polyfills) inline polyfill for full cross-browser support.
|
|
662
|
+
|
|
539
663
|
### Troubleshooting
|
|
540
664
|
|
|
541
665
|
- Styles are not updating: make sure `configure()` runs before first render, and verify the generated class name or global rule with [Debug Utilities](debug.md).
|
|
@@ -552,6 +676,6 @@ function useCounterStyle(
|
|
|
552
676
|
- **[Methodology](methodology.md)** — Recommended patterns: root + sub-elements, styleProps, tokens, wrapping
|
|
553
677
|
- **[Configuration](configuration.md)** — Tokens, recipes, custom units, style handlers, TypeScript extensions
|
|
554
678
|
- **[Style Properties](styles.md)** — Complete reference for all enhanced style properties
|
|
555
|
-
- **[
|
|
679
|
+
- **[Build-Time Extraction (`tastyStatic`)](tasty-static.md)** — Static styling with the Babel plugin
|
|
556
680
|
- **[Server-Side Rendering](ssr.md)** — SSR setup for Next.js, Astro, and generic frameworks
|
|
557
681
|
- **[Debug Utilities](debug.md)** — Inspect injected CSS, cache state, and active styles at runtime
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Runtime Benchmarks
|
|
2
|
+
|
|
3
|
+
Tasty keeps its performance claims in reproducible benchmarks rather than
|
|
4
|
+
combining unlike measurements into one score. The repository measures four
|
|
5
|
+
different costs:
|
|
6
|
+
|
|
7
|
+
1. Style parsing and generation in Node.
|
|
8
|
+
2. The React overhead of an empty `tasty({})` wrapper.
|
|
9
|
+
3. Cold browser generation and injection compared with equivalent CSS that is
|
|
10
|
+
already on the page.
|
|
11
|
+
4. A representative React update that combines wrapper, cache, generation,
|
|
12
|
+
injection, and style-resolution work.
|
|
13
|
+
|
|
14
|
+
These are focused microbenchmarks, not page-load or interaction scores. Run
|
|
15
|
+
them several times on an otherwise idle machine and use a production profile
|
|
16
|
+
to decide whether any cost matters in an application.
|
|
17
|
+
|
|
18
|
+
## Reproducing the Results
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pnpm bench
|
|
22
|
+
pnpm bench:overhead
|
|
23
|
+
pnpm bench:injection
|
|
24
|
+
pnpm bench:tree
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`pnpm bench` runs the core pipeline benchmarks in Node. The other three commands
|
|
28
|
+
use production code paths in headless Chromium. The first checkout may require
|
|
29
|
+
`pnpm test:setup` to download Chromium.
|
|
30
|
+
|
|
31
|
+
Run the Node and browser suites separately so they do not compete for CPU. The
|
|
32
|
+
browser timer has 0.1 ms resolution, so both browser benchmarks perform many
|
|
33
|
+
matched operations per sample and divide the absolute difference by the number
|
|
34
|
+
of elements or rules. Machine load, browser versions, and CPU power will move
|
|
35
|
+
the results.
|
|
36
|
+
|
|
37
|
+
## Core Style Pipeline
|
|
38
|
+
|
|
39
|
+
The following numbers are single-call throughput measured on an Apple M1 Max
|
|
40
|
+
with Node 22:
|
|
41
|
+
|
|
42
|
+
| Operation | ops/sec | Latency (mean) |
|
|
43
|
+
| ----------------------------------------------------------- | -------------------: | -------------: |
|
|
44
|
+
| `renderStyles` — 5 flat properties (cold) | ~60,000 | ~17 us |
|
|
45
|
+
| `renderStyles` — state map with media/hover/modifier (cold) | ~18,500 | ~54 us |
|
|
46
|
+
| `renderStyles` — same styles (cached) | ~5,800,000 | ~0.17 us |
|
|
47
|
+
| `parseStateKey` — simple key like `:hover` (cold) | ~790,000 | ~1.3 us |
|
|
48
|
+
| `parseStateKey` — complex OR/AND/NOT key (cold) | ~140,000 | ~7 us |
|
|
49
|
+
| `parseStateKey` — any key (cached) | ~3,400,000–8,300,000 | ~0.1–0.3 us |
|
|
50
|
+
| `parseStyle` — value tokens like `2x 4x` (cold) | ~344,000 | ~2.9 us |
|
|
51
|
+
| `parseStyle` — color tokens (cold) | ~567,000 | ~1.8 us |
|
|
52
|
+
| `parseStyle` — any value (cached) | ~15,250,000 | ~0.07 us |
|
|
53
|
+
|
|
54
|
+
“Cold” cases use unique inputs to bypass the relevant caches. Cached cases
|
|
55
|
+
reuse one input and measure the LRU hot path. Expect roughly ±10% between runs.
|
|
56
|
+
These benchmarks do not include React, DOM work, stylesheet injection, style
|
|
57
|
+
resolution, layout, or paint.
|
|
58
|
+
|
|
59
|
+
The benchmark sources are colocated with the code they exercise:
|
|
60
|
+
[`pipeline.bench.ts`](../src/pipeline/pipeline.bench.ts),
|
|
61
|
+
[`parseStateKey.bench.ts`](../src/pipeline/parseStateKey.bench.ts), and the
|
|
62
|
+
parser benchmark files under [`src/parser`](../src/parser).
|
|
63
|
+
|
|
64
|
+
## Empty Wrapper Overhead
|
|
65
|
+
|
|
66
|
+
Skipping the style pipeline does not make a `tasty()` component free. Even
|
|
67
|
+
`tasty({})` is a React component between its parent and the host element. React
|
|
68
|
+
tracks another fiber, and Tasty still processes and forwards the element's
|
|
69
|
+
props.
|
|
70
|
+
|
|
71
|
+
[`tasty-overhead.bench.tsx`](../src/tasty-overhead.bench.tsx) compares 10,000
|
|
72
|
+
raw `<div className>` siblings with 10,000 instances of one module-scoped
|
|
73
|
+
`tasty({})` component. Both receive the same props, and the benchmark fails if
|
|
74
|
+
they do not produce equivalent DOM.
|
|
75
|
+
|
|
76
|
+
The benchmark uses production React in headless Chromium. The factory is
|
|
77
|
+
created and its empty class-name cache is warmed before timing, so factory
|
|
78
|
+
creation, style generation, and injection are excluded. A detached container
|
|
79
|
+
excludes layout, paint, and stylesheet matching. Every commit is wrapped in
|
|
80
|
+
`flushSync`, keeping its synchronous reconciliation and commit inside the
|
|
81
|
+
sample. This does not estimate React's concurrent scheduling latency.
|
|
82
|
+
|
|
83
|
+
On an Apple M3 Pro with React 19.2.4 and Chromium 151, three consecutive runs
|
|
84
|
+
produced these ranges:
|
|
85
|
+
|
|
86
|
+
| Work on 10,000 siblings | Raw elements | `tasty({})` | Extra per wrapped element |
|
|
87
|
+
| ----------------------------------- | -----------: | -----------: | ------------------------: |
|
|
88
|
+
| Mount + remove | 4.5–4.9 ms | 14.0–14.9 ms | 0.95–1.00 us |
|
|
89
|
+
| Rerender, same host props | 1.3–1.4 ms | 10.9–11.4 ms | 0.96–1.00 us |
|
|
90
|
+
| Rerender, change one host attribute | 2.8–3.4 ms | 15.0–15.7 ms | 1.21–1.27 us |
|
|
91
|
+
|
|
92
|
+
The useful result is the raw/Tasty time difference divided by 10,000, not the
|
|
93
|
+
ratio between the two times. The ratio becomes large because the raw baseline
|
|
94
|
+
is tiny. In this synthetic workload, an empty wrapper adds roughly 1 us per
|
|
95
|
+
participating element, or 1.2–1.3 us when React also changes a DOM attribute.
|
|
96
|
+
|
|
97
|
+
This is the floor Tasty consumes when it has no styling job. It is not a
|
|
98
|
+
page-level score. Real trees include application components, effects, layout,
|
|
99
|
+
paint, and usually far fewer simultaneous styled-element updates. The benchmark
|
|
100
|
+
also does not measure retained memory; that requires a matched-tree heap
|
|
101
|
+
snapshot experiment with controlled garbage collection.
|
|
102
|
+
|
|
103
|
+
## Cold Generation and Injection
|
|
104
|
+
|
|
105
|
+
[`tasty-injection.bench.ts`](../src/tasty-injection.bench.ts) measures the extra
|
|
106
|
+
work when Tasty must generate and inject CSS that an otherwise equivalent page
|
|
107
|
+
already has. It does not compare different stylesheet insertion techniques.
|
|
108
|
+
|
|
109
|
+
The benchmark covers four group sizes with one style-resolution boundary per
|
|
110
|
+
group:
|
|
111
|
+
|
|
112
|
+
- **One new rule:** add one rule, append its element, and immediately read its
|
|
113
|
+
computed style. Each sample performs 50 independent groups for timer
|
|
114
|
+
stability.
|
|
115
|
+
- **10 new rules together:** generate and insert 10 rules, append their 10
|
|
116
|
+
elements, then read every computed color without another write. Each sample
|
|
117
|
+
performs 10 independent groups.
|
|
118
|
+
- **100 or 1,000 new rules together:** perform the same sequence once per
|
|
119
|
+
sample with 100 or 1,000 rules before the resolution boundary.
|
|
120
|
+
|
|
121
|
+
Repeating the smaller groups inside one sample only raises the measurement
|
|
122
|
+
above Chromium's timer resolution. Results are divided by that repetition
|
|
123
|
+
count, so every row below describes one group with one resolution boundary.
|
|
124
|
+
|
|
125
|
+
For every transaction, the existing-CSS control has the equivalent stylesheet
|
|
126
|
+
parsed, adopted, and attached before timing. The runtime root has a Tasty
|
|
127
|
+
stylesheet pre-created with an unrelated sentinel rule, but not the measured
|
|
128
|
+
rules. Both paths perform the same class assignment, DOM commit, and
|
|
129
|
+
computed-style reads. Only the runtime path calls `computeStyles()` and inserts
|
|
130
|
+
the new rules.
|
|
131
|
+
|
|
132
|
+
Preparation and cleanup happen outside the sample timer. Every runtime style
|
|
133
|
+
value is unique within a cycle, the relevant caches are cleared between cycles,
|
|
134
|
+
and a guard verifies that both paths resolve to the same color. React and the
|
|
135
|
+
`tasty()` wrapper are absent so their independently measured costs do not enter
|
|
136
|
+
the result. Pre-creating both stylesheets also excludes one-time sheet creation
|
|
137
|
+
and adoption from the subtraction.
|
|
138
|
+
|
|
139
|
+
On an Apple M3 Pro with Chromium 151, three consecutive runs produced these
|
|
140
|
+
normalized ranges:
|
|
141
|
+
|
|
142
|
+
| New rules before one resolution | Groups per sample | Incremental cost per group | Incremental cost per rule |
|
|
143
|
+
| ------------------------------: | ----------------: | -------------------------: | ------------------------: |
|
|
144
|
+
| 1 | 50 | 0.111–0.114 ms | 111–114 us |
|
|
145
|
+
| 10 | 10 | 0.142–0.159 ms | 14.2–15.9 us |
|
|
146
|
+
| 100 | 1 | 0.90–0.94 ms | 9.0–9.4 us |
|
|
147
|
+
| 1,000 | 1 | 7.58–8.10 ms | 7.6–8.1 us |
|
|
148
|
+
|
|
149
|
+
The curve makes the fixed work visible. Delivering 1,000 rules before one
|
|
150
|
+
resolution boundary cost roughly 67–73 times as much in total as delivering
|
|
151
|
+
one rule and resolving it, not 1,000 times as much. The average incremental
|
|
152
|
+
cost per rule was roughly 14–15 times lower because the group amortized fixed
|
|
153
|
+
transaction work and let the browser resolve all stylesheet writes together.
|
|
154
|
+
|
|
155
|
+
The subtraction is the meaningful result. It includes Tasty's cold style
|
|
156
|
+
generation, cache and injector bookkeeping, rule insertion, and any additional
|
|
157
|
+
style invalidation exposed by that workload's resolution boundary. It does not
|
|
158
|
+
pretend to isolate `insertRule()` from the system that calls it.
|
|
159
|
+
|
|
160
|
+
This is a deliberately cold workload. Reused styles resolve from cache and do
|
|
161
|
+
not inject another rule. Different rule complexity, DOM shape, stylesheet size,
|
|
162
|
+
browser, and hardware will change the number. Because the same group and
|
|
163
|
+
resolution pattern is present in each workload's control, the difference
|
|
164
|
+
answers the narrower delivery question: how much extra work did Tasty perform
|
|
165
|
+
when the same CSS was not already there?
|
|
166
|
+
|
|
167
|
+
## Representative React Tree
|
|
168
|
+
|
|
169
|
+
[`tasty-tree.bench.tsx`](../src/tasty-tree.bench.tsx) bridges the isolated
|
|
170
|
+
measurements with one production React update. Every path updates 1,000 host
|
|
171
|
+
elements sharing 20 combinations of `color` and `padding`, then reads computed
|
|
172
|
+
styles after the commit:
|
|
173
|
+
|
|
174
|
+
- **CSS already present:** raw elements switch between classes whose rules were
|
|
175
|
+
attached before timing.
|
|
176
|
+
- **Tasty, warm:** the elements switch between two sets of Tasty styles that
|
|
177
|
+
were generated before timing.
|
|
178
|
+
- **Tasty, cold:** every update introduces 20 new combinations shared across
|
|
179
|
+
the 1,000 elements.
|
|
180
|
+
|
|
181
|
+
The Tasty paths use the normal component API and `TastyBatchProvider`. On an
|
|
182
|
+
Apple M3 Pro with production React 19.2.4 and Chromium 151, three consecutive
|
|
183
|
+
runs produced these mean ranges:
|
|
184
|
+
|
|
185
|
+
| Update path | Total time for 1,000 elements |
|
|
186
|
+
| ----------------------------------- | ----------------------------: |
|
|
187
|
+
| CSS already present | 1.64–1.70 ms |
|
|
188
|
+
| Tasty, shared styles already cached | 4.17–4.43 ms |
|
|
189
|
+
| Tasty, 20 new shared styles | 4.62–4.80 ms |
|
|
190
|
+
|
|
191
|
+
In this workload, the warmed Tasty path added 2.52–2.73 ms over raw elements
|
|
192
|
+
with existing CSS. Introducing 20 new shared combinations added another
|
|
193
|
+
0.38–0.55 ms over the warm Tasty update. This is a synthetic update, not an
|
|
194
|
+
application score: it excludes effects, layout reads, paint, and concurrent
|
|
195
|
+
scheduling, while deliberately making all 1,000 elements participate in every
|
|
196
|
+
commit.
|
|
197
|
+
|
|
198
|
+
## Reading the Results Together
|
|
199
|
+
|
|
200
|
+
Do not add all three benchmark numbers to estimate an application blindly.
|
|
201
|
+
They describe different paths:
|
|
202
|
+
|
|
203
|
+
- A stable `tasty()` factory can skip the style pipeline on later renders, but
|
|
204
|
+
its React wrapper still participates in reconciliation.
|
|
205
|
+
- A cached style avoids cold parsing and generation and does not insert a new
|
|
206
|
+
rule.
|
|
207
|
+
- A genuinely new style pays generation and injection once, then becomes
|
|
208
|
+
reusable.
|
|
209
|
+
- A tree that shares a small style vocabulary pays the wrapper path per element
|
|
210
|
+
but cold generation only per new combination.
|
|
211
|
+
- Browser style resolution, layout, and paint depend on the actual document and
|
|
212
|
+
need application-level profiling.
|
|
213
|
+
|
|
214
|
+
The practical optimization target is therefore repeated work: keep style input
|
|
215
|
+
stable when possible, reuse generated chunks, and generate CSS at build or
|
|
216
|
+
server time when runtime flexibility is unnecessary.
|