@csszyx/dynamic 0.10.10 → 0.10.12
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 +93 -0
- package/dist/index.mjs +1 -1
- package/dist/react.d.mts +7 -1
- package/dist/react.mjs +20 -6
- package/dist/shared/{dynamic.2EVb-qef.mjs → dynamic.BWQj5dWI.mjs} +9 -0
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# @csszyx/dynamic
|
|
2
|
+
|
|
3
|
+
Runtime CSS injection for [CSSzyx](https://github.com/nguyennhutien/csszyx) —
|
|
4
|
+
turn sz objects into class strings **at runtime**, injecting CSS only for
|
|
5
|
+
classes not already in your pre-built stylesheet.
|
|
6
|
+
|
|
7
|
+
Static styles belong in the build (`sz` prop / `szv` — zero runtime cost).
|
|
8
|
+
`dynamic()` is the escape hatch for values that genuinely cannot be known at
|
|
9
|
+
build time: styling driven by a CMS, an API response, or user configuration.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { dynamic } from "@csszyx/dynamic";
|
|
13
|
+
|
|
14
|
+
const cls = dynamic(apiResponse.field.style); // e.g. { p: 4, bg: 'blue-500' }
|
|
15
|
+
<div className={cls} />;
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## How it works
|
|
19
|
+
|
|
20
|
+
1. **Manifest delta check** — a build-generated manifest lists every class in
|
|
21
|
+
the built CSS. Classes already covered are returned as-is (mangled in
|
|
22
|
+
production), with no injection.
|
|
23
|
+
2. **CSS generation** — classes NOT in the manifest get a CSS rule generated in
|
|
24
|
+
the browser using Tailwind v4 variable patterns.
|
|
25
|
+
3. **21-tier injection** — rules insert into per-breakpoint
|
|
26
|
+
`CSSStyleSheet`s whose order matches the Tailwind cascade, so
|
|
27
|
+
`sm:`/`md:`/`max-*`/container-query variants win in the right order
|
|
28
|
+
regardless of injection timing.
|
|
29
|
+
|
|
30
|
+
SSR-safe: on the server, `dynamic()` returns class names without touching the
|
|
31
|
+
CSSOM (and applies the build's mangle map during SSG so classes match the
|
|
32
|
+
mangled CSS).
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pnpm add @csszyx/dynamic
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## API
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { dynamic, preloadManifest, cleanup, purifySz } from "@csszyx/dynamic";
|
|
44
|
+
|
|
45
|
+
// Optional: eagerly fetch the manifest at startup for zero-latency first inject
|
|
46
|
+
await preloadManifest("/csszyx-manifest.json");
|
|
47
|
+
|
|
48
|
+
// Untrusted input (JSON from a CMS)? Sanitize before injecting:
|
|
49
|
+
const cls = dynamic(purifySz(untrustedSzObject));
|
|
50
|
+
|
|
51
|
+
// Release injected sheets + manifest cache (e.g. on route teardown)
|
|
52
|
+
cleanup();
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## React
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import { useSz, CsszyxProvider } from "@csszyx/dynamic/react";
|
|
59
|
+
|
|
60
|
+
function FormField({ schema }) {
|
|
61
|
+
const { sz } = useSz(); // stable reference; manages manifest + cleanup
|
|
62
|
+
return <div className={sz(schema.style)}>{schema.label}</div>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Custom manifest URL (non-root deployments):
|
|
66
|
+
<CsszyxProvider manifest="/assets/csszyx-manifest.json">
|
|
67
|
+
<App />
|
|
68
|
+
</CsszyxProvider>;
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`useSz` handles manifest preloading on mount and releases the shared
|
|
72
|
+
stylesheets when the **last** consumer unmounts (StrictMode-safe).
|
|
73
|
+
|
|
74
|
+
## When NOT to use this
|
|
75
|
+
|
|
76
|
+
- A finite set of styles selected by a prop → use `szv` (build-time, typed).
|
|
77
|
+
- Literal styles → use the `sz` prop.
|
|
78
|
+
- A runtime **value** inside an otherwise static rule (`bg-(--user-color)`) →
|
|
79
|
+
consider [`@csszyx/vars`](https://www.npmjs.com/package/@csszyx/vars) + a CSS
|
|
80
|
+
variable instead of generating rules per value.
|
|
81
|
+
|
|
82
|
+
Injected rules live for the session and are not individually removed — apps
|
|
83
|
+
that feed continuously varying values (a new arbitrary width per animation
|
|
84
|
+
frame) will grow the CSSOM. In development, a one-time warning fires if a
|
|
85
|
+
session crosses a large number of unique injected classes.
|
|
86
|
+
|
|
87
|
+
## Documentation
|
|
88
|
+
|
|
89
|
+
<https://csszyx.com/docs/dynamic/>
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
MIT
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import '@csszyx/compiler/browser';
|
|
2
|
-
export { a as cleanup, d as dynamic, b as preloadManifest, e as purifySz } from './shared/dynamic.
|
|
2
|
+
export { a as cleanup, d as dynamic, b as preloadManifest, e as purifySz } from './shared/dynamic.BWQj5dWI.mjs';
|
package/dist/react.d.mts
CHANGED
|
@@ -46,6 +46,12 @@ interface UseSzReturn {
|
|
|
46
46
|
*/
|
|
47
47
|
sz: (props: SzObject) => string;
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Reset the shared consumer counter and pending timer — test-only. Module
|
|
51
|
+
* state persists across tests in one suite, so suites that simulate
|
|
52
|
+
* mount/unmount cycles must start from a known-empty lifecycle.
|
|
53
|
+
*/
|
|
54
|
+
declare function _resetUseSzLifecycle(): void;
|
|
49
55
|
/**
|
|
50
56
|
* React hook for runtime dynamic styling.
|
|
51
57
|
*
|
|
@@ -82,5 +88,5 @@ interface CsszyxProviderProps {
|
|
|
82
88
|
*/
|
|
83
89
|
declare function CsszyxProvider({ manifest, children }: CsszyxProviderProps): ReactElement;
|
|
84
90
|
|
|
85
|
-
export { CsszyxProvider, sz, useSz };
|
|
91
|
+
export { CsszyxProvider, _resetUseSzLifecycle, sz, useSz };
|
|
86
92
|
export type { UseSzReturn };
|
package/dist/react.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createContext, useEffect, createElement, useContext, useCallback } from 'react';
|
|
2
|
-
import { d as dynamic, p as preloadManifest, c as cleanup, r as resetManifest, s as setManifestUrl } from './shared/dynamic.
|
|
2
|
+
import { d as dynamic, p as preloadManifest, c as cleanup, r as resetManifest, s as setManifestUrl } from './shared/dynamic.BWQj5dWI.mjs';
|
|
3
3
|
import '@csszyx/compiler/browser';
|
|
4
4
|
|
|
5
5
|
const CsszyxContext = createContext({
|
|
@@ -7,16 +7,30 @@ const CsszyxContext = createContext({
|
|
|
7
7
|
});
|
|
8
8
|
const sz = dynamic;
|
|
9
9
|
let _cleanupTimer = null;
|
|
10
|
+
let _mountedConsumers = 0;
|
|
11
|
+
function cancelPendingCleanup() {
|
|
12
|
+
if (_cleanupTimer !== null) {
|
|
13
|
+
clearTimeout(_cleanupTimer);
|
|
14
|
+
_cleanupTimer = null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function _resetUseSzLifecycle() {
|
|
18
|
+
cancelPendingCleanup();
|
|
19
|
+
_mountedConsumers = 0;
|
|
20
|
+
}
|
|
10
21
|
function useSz() {
|
|
11
22
|
const { manifestUrl } = useContext(CsszyxContext);
|
|
12
23
|
const stableSz = useCallback((props) => dynamic(props), []);
|
|
13
24
|
useEffect(() => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
_cleanupTimer = null;
|
|
17
|
-
}
|
|
25
|
+
_mountedConsumers++;
|
|
26
|
+
cancelPendingCleanup();
|
|
18
27
|
preloadManifest(manifestUrl);
|
|
19
28
|
return () => {
|
|
29
|
+
_mountedConsumers--;
|
|
30
|
+
if (_mountedConsumers > 0) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
cancelPendingCleanup();
|
|
20
34
|
_cleanupTimer = setTimeout(() => {
|
|
21
35
|
cleanup();
|
|
22
36
|
resetManifest();
|
|
@@ -34,4 +48,4 @@ function CsszyxProvider({ manifest, children }) {
|
|
|
34
48
|
return createElement(CsszyxContext.Provider, { value: { manifestUrl: manifest } }, children);
|
|
35
49
|
}
|
|
36
50
|
|
|
37
|
-
export { CsszyxProvider, sz, useSz };
|
|
51
|
+
export { CsszyxProvider, _resetUseSzLifecycle, sz, useSz };
|
|
@@ -1044,6 +1044,8 @@ function supportsConstructableSheets() {
|
|
|
1044
1044
|
let sheets = null;
|
|
1045
1045
|
let fallbackStyle = null;
|
|
1046
1046
|
const injected = /* @__PURE__ */ new Set();
|
|
1047
|
+
const INJECTED_GROWTH_WARN_THRESHOLD = 5e3;
|
|
1048
|
+
let warnedInjectedGrowth = false;
|
|
1047
1049
|
function wrapForTier(cssRule, tier) {
|
|
1048
1050
|
if (tier === "base") {
|
|
1049
1051
|
return cssRule;
|
|
@@ -1097,6 +1099,12 @@ function injectRule(className, cssRule, tier = "base") {
|
|
|
1097
1099
|
return;
|
|
1098
1100
|
}
|
|
1099
1101
|
injected.add(className);
|
|
1102
|
+
if (process.env.NODE_ENV !== "production" && !warnedInjectedGrowth && injected.size >= INJECTED_GROWTH_WARN_THRESHOLD) {
|
|
1103
|
+
warnedInjectedGrowth = true;
|
|
1104
|
+
console.warn(
|
|
1105
|
+
`[csszyx] dynamic() has injected ${INJECTED_GROWTH_WARN_THRESHOLD}+ unique classes this session. Injected CSS rules are never removed, so continuously varying values (e.g. w-[\`\${x}px\`] per animation frame) grow the CSSOM and slow style recalc. Drive continuously changing values through a CSS variable instead.`
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1100
1108
|
if (!cssRule) {
|
|
1101
1109
|
return;
|
|
1102
1110
|
}
|
|
@@ -1133,6 +1141,7 @@ function cleanup$1() {
|
|
|
1133
1141
|
fallbackStyle = null;
|
|
1134
1142
|
}
|
|
1135
1143
|
injected.clear();
|
|
1144
|
+
warnedInjectedGrowth = false;
|
|
1136
1145
|
}
|
|
1137
1146
|
|
|
1138
1147
|
let manifestClasses = null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@csszyx/dynamic",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.12",
|
|
4
4
|
"description": "Runtime CSS injection engine for @csszyx — injects only CSS not already in the pre-built stylesheet",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"node": ">=22.12.0"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@csszyx/compiler": "0.10.
|
|
41
|
+
"@csszyx/compiler": "0.10.12"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"react": ">=17.0.0"
|