@tenphi/tasty 1.5.4 → 2.0.0
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 +20 -15
- package/dist/compute-styles.js +13 -26
- package/dist/compute-styles.js.map +1 -1
- package/dist/config.d.ts +39 -1
- package/dist/config.js +64 -2
- package/dist/config.js.map +1 -1
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +1 -1
- package/dist/debug.js +4 -4
- package/dist/debug.js.map +1 -1
- package/dist/hooks/useCounterStyle.js +2 -1
- package/dist/hooks/useCounterStyle.js.map +1 -1
- package/dist/hooks/useGlobalStyles.js +2 -2
- package/dist/hooks/useKeyframes.js +2 -1
- package/dist/hooks/useKeyframes.js.map +1 -1
- package/dist/hooks/useRawCSS.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/injector/index.js +1 -1
- package/dist/injector/index.js.map +1 -1
- package/dist/injector/injector.d.ts +5 -0
- package/dist/injector/injector.js +93 -6
- package/dist/injector/injector.js.map +1 -1
- package/dist/injector/sheet-manager.js +3 -2
- package/dist/injector/sheet-manager.js.map +1 -1
- package/dist/injector/types.d.ts +9 -2
- package/dist/pipeline/exclusive.js +57 -2
- package/dist/pipeline/exclusive.js.map +1 -1
- package/dist/pipeline/index.js +2 -2
- package/dist/pipeline/index.js.map +1 -1
- package/dist/pipeline/materialize.js +56 -2
- package/dist/pipeline/materialize.js.map +1 -1
- package/dist/pipeline/simplify.js +180 -5
- package/dist/pipeline/simplify.js.map +1 -1
- package/dist/plugins/types.d.ts +12 -1
- package/dist/rsc-cache.js +2 -4
- package/dist/rsc-cache.js.map +1 -1
- package/dist/ssr/astro-client.js +5 -10
- package/dist/ssr/astro-client.js.map +1 -1
- package/dist/ssr/astro.d.ts +4 -2
- package/dist/ssr/astro.js +2 -2
- package/dist/ssr/astro.js.map +1 -1
- package/dist/ssr/collector.d.ts +9 -13
- package/dist/ssr/collector.js +32 -16
- package/dist/ssr/collector.js.map +1 -1
- package/dist/ssr/hydrate.d.ts +20 -13
- package/dist/ssr/hydrate.js +24 -28
- package/dist/ssr/hydrate.js.map +1 -1
- package/dist/ssr/index.d.ts +3 -3
- package/dist/ssr/index.js +2 -2
- package/dist/ssr/index.js.map +1 -1
- package/dist/ssr/next.d.ts +4 -3
- package/dist/ssr/next.js +5 -5
- package/dist/ssr/next.js.map +1 -1
- package/dist/tasty.d.ts +1 -1
- package/dist/tasty.js +9 -4
- package/dist/tasty.js.map +1 -1
- package/dist/utils/typography.d.ts +21 -10
- package/dist/utils/typography.js +1 -1
- package/dist/utils/typography.js.map +1 -1
- package/dist/zero/babel.d.ts +7 -108
- package/dist/zero/babel.js +36 -12
- package/dist/zero/babel.js.map +1 -1
- package/docs/README.md +2 -2
- package/docs/adoption.md +5 -3
- package/docs/comparison.md +24 -25
- package/docs/configuration.md +69 -1
- package/docs/design-system.md +22 -10
- package/docs/dsl.md +3 -3
- package/docs/getting-started.md +10 -10
- package/docs/injector.md +2 -2
- package/docs/methodology.md +2 -2
- package/docs/{runtime.md → react-api.md} +5 -1
- package/docs/ssr.md +14 -7
- package/docs/tasty-static.md +14 -2
- package/package.json +5 -5
package/dist/ssr/astro.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"astro.js","names":[],"sources":["../../src/ssr/astro.ts"],"sourcesContent":["/**\n * Astro integration for Tasty SSR.\n *\n * Provides:\n * - tastyIntegration() — Astro Integration API (recommended)\n * - tastyMiddleware() — manual middleware for advanced composition\n *\n * Import from '@tenphi/tasty/ssr/astro'.\n */\n\nimport { getConfig } from '../config';\nimport { getSSRCollector, runWithCollector } from './async-storage';\nimport { ServerStyleCollector } from './collector';\nimport { registerSSRCollectorGetterGlobal } from './ssr-collector-ref';\n\n// Wire up ALS-based collector discovery so computeStyles() can find\n// the collector set by tastyMiddleware's runWithCollector().\n// Uses globalThis so the getter is visible across Astro's separate\n// module graphs (middleware vs page components).\nregisterSSRCollectorGetterGlobal(getSSRCollector);\n\nexport interface TastyMiddlewareOptions {\n /**\n * Whether to embed the
|
|
1
|
+
{"version":3,"file":"astro.js","names":[],"sources":["../../src/ssr/astro.ts"],"sourcesContent":["/**\n * Astro integration for Tasty SSR.\n *\n * Provides:\n * - tastyIntegration() — Astro Integration API (recommended)\n * - tastyMiddleware() — manual middleware for advanced composition\n *\n * Import from '@tenphi/tasty/ssr/astro'.\n */\n\nimport { getConfig } from '../config';\nimport { getSSRCollector, runWithCollector } from './async-storage';\nimport { ServerStyleCollector } from './collector';\nimport { registerSSRCollectorGetterGlobal } from './ssr-collector-ref';\n\n// Wire up ALS-based collector discovery so computeStyles() can find\n// the collector set by tastyMiddleware's runWithCollector().\n// Uses globalThis so the getter is visible across Astro's separate\n// module graphs (middleware vs page components).\nregisterSSRCollectorGetterGlobal(getSSRCollector);\n\nexport interface TastyMiddlewareOptions {\n /**\n * Whether to embed the class-list script for client hydration.\n * Set to false to skip class transfer (e.g. for CSP restrictions).\n * Without it, client components may re-inject CSS that already exists\n * in server-rendered `<style>` tags. Default: true.\n */\n transferCache?: boolean;\n}\n\n/**\n * Create an Astro middleware that collects Tasty styles during SSR.\n *\n * All React components rendered during the request will have their\n * computeStyles() calls captured by the collector via AsyncLocalStorage.\n * After rendering, the middleware injects the collected CSS into </head>.\n *\n * @example Manual middleware setup\n * ```ts\n * // src/middleware.ts\n * import { tastyMiddleware } from '@tenphi/tasty/ssr/astro';\n * export const onRequest = tastyMiddleware();\n * ```\n *\n * @example Composing with other middleware\n * ```ts\n * // src/middleware.ts\n * import { sequence } from 'astro:middleware';\n * import { tastyMiddleware } from '@tenphi/tasty/ssr/astro';\n *\n * export const onRequest = sequence(\n * tastyMiddleware(),\n * myOtherMiddleware,\n * );\n * ```\n */\nexport function tastyMiddleware(options?: TastyMiddlewareOptions) {\n return async (\n _context: unknown,\n next: () => Promise<Response>,\n ): Promise<Response> => {\n const transferCache = options?.transferCache ?? true;\n const collector = new ServerStyleCollector();\n\n // Run the entire request — including body stream consumption — inside\n // the ALS context so that components rendering lazily during stream\n // reads can still find the collector via getSSRCollector().\n const rendered = await runWithCollector(collector, async () => {\n const response = await next();\n const body = response.body;\n if (!body) {\n return {\n html: null as string | null,\n status: response.status,\n headers: response.headers,\n };\n }\n\n const reader = body.pipeThrough(new TextDecoderStream()).getReader();\n const parts: string[] = [];\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n parts.push(value);\n }\n return {\n html: parts.join(''),\n status: response.status,\n headers: response.headers,\n };\n });\n\n if (!rendered.html) {\n return new Response(null, {\n status: rendered.status,\n headers: rendered.headers,\n });\n }\n\n let { html } = rendered;\n\n const css = collector.getCSS();\n if (!css) {\n return new Response(html, {\n status: rendered.status,\n headers: rendered.headers,\n });\n }\n\n const nonce = getConfig().nonce;\n const nonceAttr = nonce ? ` nonce=\"${nonce}\"` : '';\n const styleTag = `<style data-tasty-ssr${nonceAttr}>${css}</style>`;\n\n let cacheTag = '';\n if (transferCache) {\n const classNames = collector.getRenderedClassNames();\n if (classNames.length > 0) {\n const classListJSON = classNames.map((n) => `\"${n}\"`).join(',');\n cacheTag = `<script${nonceAttr}>(window.__TASTY__=window.__TASTY__||[]).push(${classListJSON})</script>`;\n }\n }\n\n const injection = styleTag + cacheTag;\n const idx = html.indexOf('</head>');\n if (idx !== -1) {\n html = html.slice(0, idx) + injection + html.slice(idx);\n } else {\n html = injection + html;\n }\n\n const headers = new Headers(rendered.headers);\n headers.delete('content-length');\n\n return new Response(html, {\n status: rendered.status,\n headers,\n });\n };\n}\n\n// ============================================================================\n// Module-level middleware config (set by tastyIntegration, read by\n// astro-middleware.ts via getter property)\n// ============================================================================\n\nlet _middlewareTransferCache = true;\n\n/** @internal */\nexport function setMiddlewareTransferCache(value: boolean): void {\n _middlewareTransferCache = value;\n}\n\n/** @internal */\nexport function getMiddlewareTransferCache(): boolean {\n return _middlewareTransferCache;\n}\n\n// ============================================================================\n// Astro Integration API\n// ============================================================================\n\nexport interface TastyIntegrationOptions {\n /**\n * Enable island hydration support.\n *\n * When `true` (default): injects a client hydration script via\n * `injectScript('before-hydration')` and sets `transferCache: true`\n * on the middleware. Islands skip the style pipeline during hydration.\n *\n * When `false`: no client JS is shipped and `transferCache` is set\n * to `false`. Use this for fully static sites without `client:*`\n * directives.\n */\n islands?: boolean;\n}\n\n/**\n * Astro integration that automatically sets up Tasty SSR.\n *\n * Registers middleware for cross-component CSS deduplication and\n * optionally injects a client hydration script for island support.\n *\n * @example Basic setup (with islands)\n * ```ts\n * // astro.config.mjs\n * import { tastyIntegration } from '@tenphi/tasty/ssr/astro';\n *\n * export default defineConfig({\n * integrations: [tastyIntegration()],\n * });\n * ```\n *\n * @example Static-only (no client JS)\n * ```ts\n * // astro.config.mjs\n * import { tastyIntegration } from '@tenphi/tasty/ssr/astro';\n *\n * export default defineConfig({\n * integrations: [tastyIntegration({ islands: false })],\n * });\n * ```\n */\nexport function tastyIntegration(options?: TastyIntegrationOptions) {\n const { islands = true } = options ?? {};\n\n setMiddlewareTransferCache(islands);\n\n return {\n name: '@tenphi/tasty',\n hooks: {\n 'astro:config:setup': ({\n addMiddleware,\n injectScript,\n }: {\n addMiddleware: (middleware: {\n entrypoint: string | URL;\n order: 'pre' | 'post';\n }) => void;\n injectScript: (stage: string, content: string) => void;\n }) => {\n addMiddleware({\n entrypoint: new URL('./astro-middleware.js', import.meta.url),\n order: 'pre',\n });\n\n if (islands) {\n injectScript(\n 'before-hydration',\n `import \"@tenphi/tasty/ssr/astro-client\";`,\n );\n }\n },\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;AAmBA,iCAAiC,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCjD,SAAgB,gBAAgB,SAAkC;AAChE,QAAO,OACL,UACA,SACsB;EACtB,MAAM,gBAAgB,SAAS,iBAAiB;EAChD,MAAM,YAAY,IAAI,sBAAsB;EAK5C,MAAM,WAAW,MAAM,iBAAiB,WAAW,YAAY;GAC7D,MAAM,WAAW,MAAM,MAAM;GAC7B,MAAM,OAAO,SAAS;AACtB,OAAI,CAAC,KACH,QAAO;IACL,MAAM;IACN,QAAQ,SAAS;IACjB,SAAS,SAAS;IACnB;GAGH,MAAM,SAAS,KAAK,YAAY,IAAI,mBAAmB,CAAC,CAAC,WAAW;GACpE,MAAM,QAAkB,EAAE;AAC1B,YAAS;IACP,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,MAAM;AAC3C,QAAI,KAAM;AACV,UAAM,KAAK,MAAM;;AAEnB,UAAO;IACL,MAAM,MAAM,KAAK,GAAG;IACpB,QAAQ,SAAS;IACjB,SAAS,SAAS;IACnB;IACD;AAEF,MAAI,CAAC,SAAS,KACZ,QAAO,IAAI,SAAS,MAAM;GACxB,QAAQ,SAAS;GACjB,SAAS,SAAS;GACnB,CAAC;EAGJ,IAAI,EAAE,SAAS;EAEf,MAAM,MAAM,UAAU,QAAQ;AAC9B,MAAI,CAAC,IACH,QAAO,IAAI,SAAS,MAAM;GACxB,QAAQ,SAAS;GACjB,SAAS,SAAS;GACnB,CAAC;EAGJ,MAAM,QAAQ,WAAW,CAAC;EAC1B,MAAM,YAAY,QAAQ,WAAW,MAAM,KAAK;EAChD,MAAM,WAAW,wBAAwB,UAAU,GAAG,IAAI;EAE1D,IAAI,WAAW;AACf,MAAI,eAAe;GACjB,MAAM,aAAa,UAAU,uBAAuB;AACpD,OAAI,WAAW,SAAS,EAEtB,YAAW,UAAU,UAAU,gDADT,WAAW,KAAK,MAAM,IAAI,EAAE,GAAG,CAAC,KAAK,IAAI,CAC8B;;EAIjG,MAAM,YAAY,WAAW;EAC7B,MAAM,MAAM,KAAK,QAAQ,UAAU;AACnC,MAAI,QAAQ,GACV,QAAO,KAAK,MAAM,GAAG,IAAI,GAAG,YAAY,KAAK,MAAM,IAAI;MAEvD,QAAO,YAAY;EAGrB,MAAM,UAAU,IAAI,QAAQ,SAAS,QAAQ;AAC7C,UAAQ,OAAO,iBAAiB;AAEhC,SAAO,IAAI,SAAS,MAAM;GACxB,QAAQ,SAAS;GACjB;GACD,CAAC;;;AASN,IAAI,2BAA2B;;AAG/B,SAAgB,2BAA2B,OAAsB;AAC/D,4BAA2B;;;AAI7B,SAAgB,6BAAsC;AACpD,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDT,SAAgB,iBAAiB,SAAmC;CAClE,MAAM,EAAE,UAAU,SAAS,WAAW,EAAE;AAExC,4BAA2B,QAAQ;AAEnC,QAAO;EACL,MAAM;EACN,OAAO,EACL,uBAAuB,EACrB,eACA,mBAOI;AACJ,iBAAc;IACZ,YAAY,IAAI,IAAI,yBAAyB,OAAO,KAAK,IAAI;IAC7D,OAAO;IACR,CAAC;AAEF,OAAI,QACF,cACE,oBACA,2CACD;KAGN;EACF"}
|
package/dist/ssr/collector.d.ts
CHANGED
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
import { StyleResult } from "../pipeline/index.js";
|
|
2
2
|
|
|
3
3
|
//#region src/ssr/collector.d.ts
|
|
4
|
-
/**
|
|
5
|
-
* Cache state serialized to the client for hydration.
|
|
6
|
-
*/
|
|
7
|
-
interface SSRCacheState {
|
|
8
|
-
/** cacheKey → className map, to pre-populate the client registry */
|
|
9
|
-
entries: Record<string, string>;
|
|
10
|
-
/** Counter value so client allocations don't collide with server ones */
|
|
11
|
-
classCounter: number;
|
|
12
|
-
}
|
|
13
4
|
declare class ServerStyleCollector {
|
|
14
5
|
private chunks;
|
|
15
6
|
private cacheKeyToClassName;
|
|
16
|
-
private classCounter;
|
|
17
7
|
private flushedKeys;
|
|
18
8
|
private propertyRules;
|
|
19
9
|
private flushedPropertyKeys;
|
|
@@ -34,6 +24,10 @@ declare class ServerStyleCollector {
|
|
|
34
24
|
* Collect internal @property rules and :root token defaults.
|
|
35
25
|
* Mirrors markStylesGenerated() from the client-side injector.
|
|
36
26
|
* Called automatically on first chunk collection; idempotent.
|
|
27
|
+
*
|
|
28
|
+
* Internals are always emitted here — the RSC path deliberately
|
|
29
|
+
* defers to SSR so that tokens appear exactly once per page in
|
|
30
|
+
* <style data-tasty-ssr> (avoiding duplication of large token sets).
|
|
37
31
|
*/
|
|
38
32
|
collectInternals(): void;
|
|
39
33
|
/**
|
|
@@ -92,11 +86,13 @@ declare class ServerStyleCollector {
|
|
|
92
86
|
* Used for streaming SSR (renderToPipeableStream + useServerInsertedHTML).
|
|
93
87
|
*/
|
|
94
88
|
flushCSS(): string;
|
|
89
|
+
private flushedClassNames;
|
|
95
90
|
/**
|
|
96
|
-
*
|
|
91
|
+
* Return class names rendered since the last call (for streaming).
|
|
92
|
+
* Used to emit lightweight class-list scripts for client hydration.
|
|
97
93
|
*/
|
|
98
|
-
|
|
94
|
+
getRenderedClassNames(): string[];
|
|
99
95
|
}
|
|
100
96
|
//#endregion
|
|
101
|
-
export {
|
|
97
|
+
export { ServerStyleCollector };
|
|
102
98
|
//# sourceMappingURL=collector.d.ts.map
|
package/dist/ssr/collector.js
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
+
import { hashString } from "../utils/hash.js";
|
|
1
2
|
import { fontFaceContentHash, formatFontFaceRule } from "../font-face/index.js";
|
|
2
3
|
import { formatCounterStyleRule } from "../counter-style/index.js";
|
|
3
4
|
import { renderStyles } from "../pipeline/index.js";
|
|
4
|
-
import { getEffectiveProperties, getGlobalConfigTokens, getGlobalCounterStyle, getGlobalFontFace } from "../config.js";
|
|
5
|
+
import { getEffectiveProperties, getGlobalConfigTokens, getGlobalCounterStyle, getGlobalFontFace, getGlobalStyles } from "../config.js";
|
|
5
6
|
import { formatPropertyCSS } from "./format-property.js";
|
|
6
|
-
import { formatGlobalRules } from "./format-global-rules.js";
|
|
7
7
|
import { formatRules } from "./format-rules.js";
|
|
8
|
+
import { formatGlobalRules } from "./format-global-rules.js";
|
|
8
9
|
//#region src/ssr/collector.ts
|
|
9
10
|
/**
|
|
10
11
|
* ServerStyleCollector — server-safe style collector for SSR.
|
|
11
12
|
*
|
|
12
13
|
* Accumulates CSS rules and cache metadata during server rendering.
|
|
13
14
|
* This is the server-side counterpart to StyleInjector: it allocates
|
|
14
|
-
*
|
|
15
|
-
* and
|
|
15
|
+
* hash-based class names (`t${hash}`), formats CSS rules into text,
|
|
16
|
+
* and tracks rendered class names for lightweight client transfer.
|
|
16
17
|
*
|
|
17
18
|
* One instance is created per HTTP request. Concurrent requests
|
|
18
19
|
* each get their own collector (via AsyncLocalStorage or React context).
|
|
19
20
|
*/
|
|
20
|
-
function generateClassName(
|
|
21
|
-
return `t${
|
|
21
|
+
function generateClassName(cacheKey) {
|
|
22
|
+
return `t${hashString(cacheKey)}`;
|
|
22
23
|
}
|
|
23
24
|
var ServerStyleCollector = class {
|
|
24
25
|
chunks = /* @__PURE__ */ new Map();
|
|
25
26
|
cacheKeyToClassName = /* @__PURE__ */ new Map();
|
|
26
|
-
classCounter = 0;
|
|
27
27
|
flushedKeys = /* @__PURE__ */ new Set();
|
|
28
28
|
propertyRules = /* @__PURE__ */ new Map();
|
|
29
29
|
flushedPropertyKeys = /* @__PURE__ */ new Set();
|
|
@@ -44,6 +44,10 @@ var ServerStyleCollector = class {
|
|
|
44
44
|
* Collect internal @property rules and :root token defaults.
|
|
45
45
|
* Mirrors markStylesGenerated() from the client-side injector.
|
|
46
46
|
* Called automatically on first chunk collection; idempotent.
|
|
47
|
+
*
|
|
48
|
+
* Internals are always emitted here — the RSC path deliberately
|
|
49
|
+
* defers to SSR so that tokens appear exactly once per page in
|
|
50
|
+
* <style data-tasty-ssr> (avoiding duplication of large token sets).
|
|
47
51
|
*/
|
|
48
52
|
collectInternals() {
|
|
49
53
|
if (this.internalsCollected) return;
|
|
@@ -74,6 +78,16 @@ var ServerStyleCollector = class {
|
|
|
74
78
|
const css = formatCounterStyleRule(name, descriptors);
|
|
75
79
|
this.collectCounterStyle(name, css);
|
|
76
80
|
}
|
|
81
|
+
const globalStyles = getGlobalStyles();
|
|
82
|
+
if (globalStyles) {
|
|
83
|
+
for (const [selector, styles] of Object.entries(globalStyles)) if (Object.keys(styles).length > 0) {
|
|
84
|
+
const rules = renderStyles(styles, selector);
|
|
85
|
+
if (rules.length > 0) {
|
|
86
|
+
const css = formatGlobalRules(rules);
|
|
87
|
+
if (css) this.collectGlobalStyles(`__global:styles:${selector}`, css);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
77
91
|
}
|
|
78
92
|
/**
|
|
79
93
|
* Allocate a className for a cache key, server-side.
|
|
@@ -85,7 +99,7 @@ var ServerStyleCollector = class {
|
|
|
85
99
|
className: existing,
|
|
86
100
|
isNewAllocation: false
|
|
87
101
|
};
|
|
88
|
-
const className = generateClassName(
|
|
102
|
+
const className = generateClassName(cacheKey);
|
|
89
103
|
this.cacheKeyToClassName.set(cacheKey, className);
|
|
90
104
|
return {
|
|
91
105
|
className,
|
|
@@ -201,16 +215,18 @@ var ServerStyleCollector = class {
|
|
|
201
215
|
}
|
|
202
216
|
return parts.join("\n");
|
|
203
217
|
}
|
|
218
|
+
flushedClassNames = /* @__PURE__ */ new Set();
|
|
204
219
|
/**
|
|
205
|
-
*
|
|
220
|
+
* Return class names rendered since the last call (for streaming).
|
|
221
|
+
* Used to emit lightweight class-list scripts for client hydration.
|
|
206
222
|
*/
|
|
207
|
-
|
|
208
|
-
const
|
|
209
|
-
for (const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
223
|
+
getRenderedClassNames() {
|
|
224
|
+
const names = [];
|
|
225
|
+
for (const className of this.cacheKeyToClassName.values()) if (!this.flushedClassNames.has(className)) {
|
|
226
|
+
this.flushedClassNames.add(className);
|
|
227
|
+
names.push(className);
|
|
228
|
+
}
|
|
229
|
+
return names;
|
|
214
230
|
}
|
|
215
231
|
};
|
|
216
232
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collector.js","names":[],"sources":["../../src/ssr/collector.ts"],"sourcesContent":["/**\n * ServerStyleCollector — server-safe style collector for SSR.\n *\n * Accumulates CSS rules and cache metadata during server rendering.\n * This is the server-side counterpart to StyleInjector: it allocates\n * sequential class names (t0, t1, …), formats CSS rules into text,\n * and serializes the cache state for client hydration.\n *\n * One instance is created per HTTP request. Concurrent requests\n * each get their own collector (via AsyncLocalStorage or React context).\n */\n\nimport {\n getEffectiveProperties,\n getGlobalCounterStyle,\n getGlobalFontFace,\n getGlobalConfigTokens,\n} from '../config';\nimport { formatCounterStyleRule } from '../counter-style';\nimport { fontFaceContentHash, formatFontFaceRule } from '../font-face';\nimport { renderStyles } from '../pipeline';\nimport type { StyleResult } from '../pipeline';\nimport { formatPropertyCSS } from './format-property';\nimport { formatGlobalRules } from './format-global-rules';\nimport { formatRules } from './format-rules';\n\n/**\n * Cache state serialized to the client for hydration.\n */\nexport interface SSRCacheState {\n /** cacheKey → className map, to pre-populate the client registry */\n entries: Record<string, string>;\n /** Counter value so client allocations don't collide with server ones */\n classCounter: number;\n}\n\nfunction generateClassName(counter: number): string {\n return `t${counter}`;\n}\n\nexport class ServerStyleCollector {\n private chunks = new Map<string, string>();\n private cacheKeyToClassName = new Map<string, string>();\n private classCounter = 0;\n private flushedKeys = new Set<string>();\n private propertyRules = new Map<string, string>();\n private flushedPropertyKeys = new Set<string>();\n private keyframeRules = new Map<string, string>();\n private flushedKeyframeKeys = new Set<string>();\n private globalStyles = new Map<string, string>();\n private flushedGlobalKeys = new Set<string>();\n private rawCSS = new Map<string, string>();\n private flushedRawKeys = new Set<string>();\n private fontFaceRules = new Map<string, string>();\n private flushedFontFaceKeys = new Set<string>();\n private counterStyleRules = new Map<string, string>();\n private flushedCounterStyleKeys = new Set<string>();\n private keyframesCounter = 0;\n private counterStyleCounter = 0;\n private internalsCollected = false;\n\n /**\n * Collect internal @property rules and :root token defaults.\n * Mirrors markStylesGenerated() from the client-side injector.\n * Called automatically on first chunk collection; idempotent.\n */\n collectInternals(): void {\n if (this.internalsCollected) return;\n this.internalsCollected = true;\n\n for (const [token, definition] of Object.entries(\n getEffectiveProperties(),\n )) {\n const css = formatPropertyCSS(token, definition);\n if (css) {\n this.collectProperty(`__prop:${token}`, css);\n }\n }\n\n // Inject configured tokens as :root CSS custom properties\n const tokenStyles = getGlobalConfigTokens();\n if (tokenStyles && Object.keys(tokenStyles).length > 0) {\n const tokenRules = renderStyles(tokenStyles, ':root') as StyleResult[];\n if (tokenRules.length > 0) {\n const css = formatGlobalRules(tokenRules);\n if (css) {\n this.collectGlobalStyles('__global:tokens', css);\n }\n }\n }\n\n // Inject global @font-face rules (mirrors markStylesGenerated)\n const globalFF = getGlobalFontFace();\n if (globalFF) {\n for (const [family, input] of Object.entries(globalFF)) {\n const descriptors = Array.isArray(input) ? input : [input];\n for (const desc of descriptors) {\n const hash = fontFaceContentHash(family, desc);\n const css = formatFontFaceRule(family, desc);\n this.collectFontFace(hash, css);\n }\n }\n }\n\n // Inject global @counter-style rules (mirrors markStylesGenerated)\n const globalCS = getGlobalCounterStyle();\n if (globalCS) {\n for (const [name, descriptors] of Object.entries(globalCS)) {\n const css = formatCounterStyleRule(name, descriptors);\n this.collectCounterStyle(name, css);\n }\n }\n }\n\n /**\n * Allocate a className for a cache key, server-side.\n * Mirrors StyleInjector.allocateClassName but without DOM access.\n */\n allocateClassName(cacheKey: string): {\n className: string;\n isNewAllocation: boolean;\n } {\n const existing = this.cacheKeyToClassName.get(cacheKey);\n if (existing) {\n return { className: existing, isNewAllocation: false };\n }\n\n const className = generateClassName(this.classCounter++);\n this.cacheKeyToClassName.set(cacheKey, className);\n\n return { className, isNewAllocation: true };\n }\n\n /**\n * Record CSS rules for a chunk.\n * Called by useStyles during server render.\n */\n collectChunk(\n cacheKey: string,\n className: string,\n rules: StyleResult[],\n ): void {\n if (this.chunks.has(cacheKey)) return;\n const css = formatRules(rules, className);\n if (css) {\n this.chunks.set(cacheKey, css);\n }\n }\n\n /**\n * Record a @property rule. Deduplicated by name.\n */\n collectProperty(name: string, css: string): void {\n if (!this.propertyRules.has(name)) {\n this.propertyRules.set(name, css);\n }\n }\n\n /**\n * Record a @keyframes rule. Deduplicated by name.\n */\n collectKeyframes(name: string, css: string): void {\n if (!this.keyframeRules.has(name)) {\n this.keyframeRules.set(name, css);\n }\n }\n\n /**\n * Allocate a keyframe name for SSR. Uses provided name or generates one.\n */\n allocateKeyframeName(providedName?: string): string {\n return providedName ?? `k${this.keyframesCounter++}`;\n }\n\n /**\n * Record a @font-face rule. Deduplicated by key (content hash).\n */\n collectFontFace(key: string, css: string): void {\n if (!this.fontFaceRules.has(key)) {\n this.fontFaceRules.set(key, css);\n }\n }\n\n /**\n * Record a @counter-style rule. Deduplicated by name.\n */\n collectCounterStyle(name: string, css: string): void {\n if (!this.counterStyleRules.has(name)) {\n this.counterStyleRules.set(name, css);\n }\n }\n\n /**\n * Allocate a counter-style name for SSR. Uses provided name or generates one.\n */\n allocateCounterStyleName(providedName?: string): string {\n return providedName ?? `cs${this.counterStyleCounter++}`;\n }\n\n /**\n * Record global styles (from useGlobalStyles). Deduplicated by key.\n */\n collectGlobalStyles(key: string, css: string): void {\n if (!this.globalStyles.has(key)) {\n this.globalStyles.set(key, css);\n }\n }\n\n /**\n * Record raw CSS text (from useRawCSS). Deduplicated by key.\n */\n collectRawCSS(key: string, css: string): void {\n if (!this.rawCSS.has(key)) {\n this.rawCSS.set(key, css);\n }\n }\n\n /**\n * Extract all CSS collected so far as a single string.\n * Includes @property and @keyframes rules.\n * Used for non-streaming SSR (renderToString).\n */\n getCSS(): string {\n const parts: string[] = [];\n\n for (const css of this.propertyRules.values()) {\n parts.push(css);\n }\n\n for (const css of this.fontFaceRules.values()) {\n parts.push(css);\n }\n\n for (const css of this.counterStyleRules.values()) {\n parts.push(css);\n }\n\n for (const css of this.rawCSS.values()) {\n parts.push(css);\n }\n\n for (const css of this.globalStyles.values()) {\n parts.push(css);\n }\n\n for (const css of this.chunks.values()) {\n parts.push(css);\n }\n\n for (const css of this.keyframeRules.values()) {\n parts.push(css);\n }\n\n return parts.join('\\n');\n }\n\n /**\n * Flush only newly collected CSS since the last flush.\n * Used for streaming SSR (renderToPipeableStream + useServerInsertedHTML).\n */\n flushCSS(): string {\n const parts: string[] = [];\n\n for (const [name, css] of this.propertyRules) {\n if (!this.flushedPropertyKeys.has(name)) {\n parts.push(css);\n this.flushedPropertyKeys.add(name);\n }\n }\n\n for (const [key, css] of this.fontFaceRules) {\n if (!this.flushedFontFaceKeys.has(key)) {\n parts.push(css);\n this.flushedFontFaceKeys.add(key);\n }\n }\n\n for (const [key, css] of this.counterStyleRules) {\n if (!this.flushedCounterStyleKeys.has(key)) {\n parts.push(css);\n this.flushedCounterStyleKeys.add(key);\n }\n }\n\n for (const [key, css] of this.rawCSS) {\n if (!this.flushedRawKeys.has(key)) {\n parts.push(css);\n this.flushedRawKeys.add(key);\n }\n }\n\n for (const [key, css] of this.globalStyles) {\n if (!this.flushedGlobalKeys.has(key)) {\n parts.push(css);\n this.flushedGlobalKeys.add(key);\n }\n }\n\n for (const [key, css] of this.chunks) {\n if (!this.flushedKeys.has(key)) {\n parts.push(css);\n this.flushedKeys.add(key);\n }\n }\n\n for (const [name, css] of this.keyframeRules) {\n if (!this.flushedKeyframeKeys.has(name)) {\n parts.push(css);\n this.flushedKeyframeKeys.add(name);\n }\n }\n\n return parts.join('\\n');\n }\n\n /**\n * Serialize the cache state for client hydration.\n */\n getCacheState(): SSRCacheState {\n const entries: Record<string, string> = {};\n for (const [cacheKey, className] of this.cacheKeyToClassName) {\n entries[cacheKey] = className;\n }\n return { entries, classCounter: this.classCounter };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"collector.js","names":[],"sources":["../../src/ssr/collector.ts"],"sourcesContent":["/**\n * ServerStyleCollector — server-safe style collector for SSR.\n *\n * Accumulates CSS rules and cache metadata during server rendering.\n * This is the server-side counterpart to StyleInjector: it allocates\n * hash-based class names (`t${hash}`), formats CSS rules into text,\n * and tracks rendered class names for lightweight client transfer.\n *\n * One instance is created per HTTP request. Concurrent requests\n * each get their own collector (via AsyncLocalStorage or React context).\n */\n\nimport {\n getEffectiveProperties,\n getGlobalStyles,\n getGlobalCounterStyle,\n getGlobalFontFace,\n getGlobalConfigTokens,\n} from '../config';\nimport { formatCounterStyleRule } from '../counter-style';\nimport { fontFaceContentHash, formatFontFaceRule } from '../font-face';\nimport { renderStyles } from '../pipeline';\nimport type { StyleResult } from '../pipeline';\nimport { hashString } from '../utils/hash';\nimport { formatPropertyCSS } from './format-property';\nimport { formatGlobalRules } from './format-global-rules';\nimport { formatRules } from './format-rules';\n\nfunction generateClassName(cacheKey: string): string {\n return `t${hashString(cacheKey)}`;\n}\n\nexport class ServerStyleCollector {\n private chunks = new Map<string, string>();\n private cacheKeyToClassName = new Map<string, string>();\n private flushedKeys = new Set<string>();\n private propertyRules = new Map<string, string>();\n private flushedPropertyKeys = new Set<string>();\n private keyframeRules = new Map<string, string>();\n private flushedKeyframeKeys = new Set<string>();\n private globalStyles = new Map<string, string>();\n private flushedGlobalKeys = new Set<string>();\n private rawCSS = new Map<string, string>();\n private flushedRawKeys = new Set<string>();\n private fontFaceRules = new Map<string, string>();\n private flushedFontFaceKeys = new Set<string>();\n private counterStyleRules = new Map<string, string>();\n private flushedCounterStyleKeys = new Set<string>();\n private keyframesCounter = 0;\n private counterStyleCounter = 0;\n private internalsCollected = false;\n\n /**\n * Collect internal @property rules and :root token defaults.\n * Mirrors markStylesGenerated() from the client-side injector.\n * Called automatically on first chunk collection; idempotent.\n *\n * Internals are always emitted here — the RSC path deliberately\n * defers to SSR so that tokens appear exactly once per page in\n * <style data-tasty-ssr> (avoiding duplication of large token sets).\n */\n collectInternals(): void {\n if (this.internalsCollected) return;\n this.internalsCollected = true;\n\n for (const [token, definition] of Object.entries(\n getEffectiveProperties(),\n )) {\n const css = formatPropertyCSS(token, definition);\n if (css) {\n this.collectProperty(`__prop:${token}`, css);\n }\n }\n\n const tokenStyles = getGlobalConfigTokens();\n if (tokenStyles && Object.keys(tokenStyles).length > 0) {\n const tokenRules = renderStyles(tokenStyles, ':root') as StyleResult[];\n if (tokenRules.length > 0) {\n const css = formatGlobalRules(tokenRules);\n if (css) {\n this.collectGlobalStyles('__global:tokens', css);\n }\n }\n }\n\n const globalFF = getGlobalFontFace();\n if (globalFF) {\n for (const [family, input] of Object.entries(globalFF)) {\n const descriptors = Array.isArray(input) ? input : [input];\n for (const desc of descriptors) {\n const hash = fontFaceContentHash(family, desc);\n const css = formatFontFaceRule(family, desc);\n this.collectFontFace(hash, css);\n }\n }\n }\n\n const globalCS = getGlobalCounterStyle();\n if (globalCS) {\n for (const [name, descriptors] of Object.entries(globalCS)) {\n const css = formatCounterStyleRule(name, descriptors);\n this.collectCounterStyle(name, css);\n }\n }\n\n const globalStyles = getGlobalStyles();\n if (globalStyles) {\n for (const [selector, styles] of Object.entries(globalStyles)) {\n if (Object.keys(styles).length > 0) {\n const rules = renderStyles(styles, selector) as StyleResult[];\n if (rules.length > 0) {\n const css = formatGlobalRules(rules);\n if (css) {\n this.collectGlobalStyles(`__global:styles:${selector}`, css);\n }\n }\n }\n }\n }\n }\n\n /**\n * Allocate a className for a cache key, server-side.\n * Mirrors StyleInjector.allocateClassName but without DOM access.\n */\n allocateClassName(cacheKey: string): {\n className: string;\n isNewAllocation: boolean;\n } {\n const existing = this.cacheKeyToClassName.get(cacheKey);\n if (existing) {\n return { className: existing, isNewAllocation: false };\n }\n\n const className = generateClassName(cacheKey);\n this.cacheKeyToClassName.set(cacheKey, className);\n\n return { className, isNewAllocation: true };\n }\n\n /**\n * Record CSS rules for a chunk.\n * Called by useStyles during server render.\n */\n collectChunk(\n cacheKey: string,\n className: string,\n rules: StyleResult[],\n ): void {\n if (this.chunks.has(cacheKey)) return;\n const css = formatRules(rules, className);\n if (css) {\n this.chunks.set(cacheKey, css);\n }\n }\n\n /**\n * Record a @property rule. Deduplicated by name.\n */\n collectProperty(name: string, css: string): void {\n if (!this.propertyRules.has(name)) {\n this.propertyRules.set(name, css);\n }\n }\n\n /**\n * Record a @keyframes rule. Deduplicated by name.\n */\n collectKeyframes(name: string, css: string): void {\n if (!this.keyframeRules.has(name)) {\n this.keyframeRules.set(name, css);\n }\n }\n\n /**\n * Allocate a keyframe name for SSR. Uses provided name or generates one.\n */\n allocateKeyframeName(providedName?: string): string {\n return providedName ?? `k${this.keyframesCounter++}`;\n }\n\n /**\n * Record a @font-face rule. Deduplicated by key (content hash).\n */\n collectFontFace(key: string, css: string): void {\n if (!this.fontFaceRules.has(key)) {\n this.fontFaceRules.set(key, css);\n }\n }\n\n /**\n * Record a @counter-style rule. Deduplicated by name.\n */\n collectCounterStyle(name: string, css: string): void {\n if (!this.counterStyleRules.has(name)) {\n this.counterStyleRules.set(name, css);\n }\n }\n\n /**\n * Allocate a counter-style name for SSR. Uses provided name or generates one.\n */\n allocateCounterStyleName(providedName?: string): string {\n return providedName ?? `cs${this.counterStyleCounter++}`;\n }\n\n /**\n * Record global styles (from useGlobalStyles). Deduplicated by key.\n */\n collectGlobalStyles(key: string, css: string): void {\n if (!this.globalStyles.has(key)) {\n this.globalStyles.set(key, css);\n }\n }\n\n /**\n * Record raw CSS text (from useRawCSS). Deduplicated by key.\n */\n collectRawCSS(key: string, css: string): void {\n if (!this.rawCSS.has(key)) {\n this.rawCSS.set(key, css);\n }\n }\n\n /**\n * Extract all CSS collected so far as a single string.\n * Includes @property and @keyframes rules.\n * Used for non-streaming SSR (renderToString).\n */\n getCSS(): string {\n const parts: string[] = [];\n\n for (const css of this.propertyRules.values()) {\n parts.push(css);\n }\n\n for (const css of this.fontFaceRules.values()) {\n parts.push(css);\n }\n\n for (const css of this.counterStyleRules.values()) {\n parts.push(css);\n }\n\n for (const css of this.rawCSS.values()) {\n parts.push(css);\n }\n\n for (const css of this.globalStyles.values()) {\n parts.push(css);\n }\n\n for (const css of this.chunks.values()) {\n parts.push(css);\n }\n\n for (const css of this.keyframeRules.values()) {\n parts.push(css);\n }\n\n return parts.join('\\n');\n }\n\n /**\n * Flush only newly collected CSS since the last flush.\n * Used for streaming SSR (renderToPipeableStream + useServerInsertedHTML).\n */\n flushCSS(): string {\n const parts: string[] = [];\n\n for (const [name, css] of this.propertyRules) {\n if (!this.flushedPropertyKeys.has(name)) {\n parts.push(css);\n this.flushedPropertyKeys.add(name);\n }\n }\n\n for (const [key, css] of this.fontFaceRules) {\n if (!this.flushedFontFaceKeys.has(key)) {\n parts.push(css);\n this.flushedFontFaceKeys.add(key);\n }\n }\n\n for (const [key, css] of this.counterStyleRules) {\n if (!this.flushedCounterStyleKeys.has(key)) {\n parts.push(css);\n this.flushedCounterStyleKeys.add(key);\n }\n }\n\n for (const [key, css] of this.rawCSS) {\n if (!this.flushedRawKeys.has(key)) {\n parts.push(css);\n this.flushedRawKeys.add(key);\n }\n }\n\n for (const [key, css] of this.globalStyles) {\n if (!this.flushedGlobalKeys.has(key)) {\n parts.push(css);\n this.flushedGlobalKeys.add(key);\n }\n }\n\n for (const [key, css] of this.chunks) {\n if (!this.flushedKeys.has(key)) {\n parts.push(css);\n this.flushedKeys.add(key);\n }\n }\n\n for (const [name, css] of this.keyframeRules) {\n if (!this.flushedKeyframeKeys.has(name)) {\n parts.push(css);\n this.flushedKeyframeKeys.add(name);\n }\n }\n\n return parts.join('\\n');\n }\n\n private flushedClassNames = new Set<string>();\n\n /**\n * Return class names rendered since the last call (for streaming).\n * Used to emit lightweight class-list scripts for client hydration.\n */\n getRenderedClassNames(): string[] {\n const names: string[] = [];\n for (const className of this.cacheKeyToClassName.values()) {\n if (!this.flushedClassNames.has(className)) {\n this.flushedClassNames.add(className);\n names.push(className);\n }\n }\n return names;\n }\n}\n"],"mappings":"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"}
|
package/dist/ssr/hydrate.d.ts
CHANGED
|
@@ -1,22 +1,29 @@
|
|
|
1
|
-
import { SSRCacheState } from "./collector.js";
|
|
2
|
-
|
|
3
1
|
//#region src/ssr/hydrate.d.ts
|
|
4
|
-
declare global {
|
|
5
|
-
interface Window {
|
|
6
|
-
__TASTY_SSR_CACHE__?: SSRCacheState;
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
2
|
/**
|
|
10
|
-
*
|
|
3
|
+
* Client-side cache hydration for SSR/RSC.
|
|
4
|
+
*
|
|
5
|
+
* Pre-populates the client injector's rules map with class names
|
|
6
|
+
* rendered on the server. With hash-based naming, the client derives
|
|
7
|
+
* the same class name from the same cache key, so only the class name
|
|
8
|
+
* list needs to cross the wire — no cache keys or counters.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Pre-populate the client-side style registry from the server's class name list.
|
|
11
12
|
*
|
|
12
13
|
* Call this before ReactDOM.hydrateRoot() or ensure it runs before
|
|
13
14
|
* any tasty() component renders on the client.
|
|
14
15
|
*
|
|
15
|
-
* When called without arguments, reads
|
|
16
|
-
*
|
|
17
|
-
|
|
16
|
+
* When called without arguments, reads the class list from `window.__TASTY__`
|
|
17
|
+
* (populated by inline scripts emitted during SSR/RSC streaming).
|
|
18
|
+
*/
|
|
19
|
+
declare function hydrateTastyClasses(classes?: string[]): void;
|
|
20
|
+
/**
|
|
21
|
+
* @deprecated Use `hydrateTastyClasses()` instead. This alias exists
|
|
22
|
+
* for backwards compatibility and will be removed in a future major version.
|
|
18
23
|
*/
|
|
19
|
-
declare function hydrateTastyCache(state?:
|
|
24
|
+
declare function hydrateTastyCache(state?: {
|
|
25
|
+
entries?: Record<string, string>;
|
|
26
|
+
}): void;
|
|
20
27
|
//#endregion
|
|
21
|
-
export { hydrateTastyCache };
|
|
28
|
+
export { hydrateTastyCache, hydrateTastyClasses };
|
|
22
29
|
//# sourceMappingURL=hydrate.d.ts.map
|
package/dist/ssr/hydrate.js
CHANGED
|
@@ -1,49 +1,45 @@
|
|
|
1
1
|
import { getGlobalInjector } from "../config.js";
|
|
2
2
|
//#region src/ssr/hydrate.ts
|
|
3
3
|
/**
|
|
4
|
-
* Client-side cache hydration for SSR.
|
|
4
|
+
* Client-side cache hydration for SSR/RSC.
|
|
5
5
|
*
|
|
6
|
-
* Pre-populates the client injector's
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Pre-populates the client injector's rules map with class names
|
|
7
|
+
* rendered on the server. With hash-based naming, the client derives
|
|
8
|
+
* the same class name from the same cache key, so only the class name
|
|
9
|
+
* list needs to cross the wire — no cache keys or counters.
|
|
9
10
|
*/
|
|
10
11
|
/**
|
|
11
|
-
* Pre-populate the client-side style
|
|
12
|
+
* Pre-populate the client-side style registry from the server's class name list.
|
|
12
13
|
*
|
|
13
14
|
* Call this before ReactDOM.hydrateRoot() or ensure it runs before
|
|
14
15
|
* any tasty() component renders on the client.
|
|
15
16
|
*
|
|
16
|
-
* When called without arguments, reads
|
|
17
|
-
*
|
|
18
|
-
* 2. `<script data-tasty-cache>` (non-streaming — JSON payload)
|
|
17
|
+
* When called without arguments, reads the class list from `window.__TASTY__`
|
|
18
|
+
* (populated by inline scripts emitted during SSR/RSC streaming).
|
|
19
19
|
*/
|
|
20
|
-
function
|
|
20
|
+
function hydrateTastyClasses(classes) {
|
|
21
21
|
if (typeof document === "undefined") return;
|
|
22
|
-
if (!
|
|
23
|
-
|
|
24
|
-
if (!state) {
|
|
25
|
-
const script = document.querySelector("script[data-tasty-cache]");
|
|
26
|
-
if (script) try {
|
|
27
|
-
state = JSON.parse(script.textContent);
|
|
28
|
-
} catch {
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
if (!state) return;
|
|
22
|
+
if (!classes) classes = typeof window !== "undefined" ? window.__TASTY__ : void 0;
|
|
23
|
+
if (!classes?.length) return;
|
|
34
24
|
const registry = getGlobalInjector()._sheetManager.getRegistry(document);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
registry.rules.set(className, {
|
|
39
|
-
className,
|
|
25
|
+
for (const cls of classes) if (!registry.rules.has(cls)) {
|
|
26
|
+
registry.rules.set(cls, {
|
|
27
|
+
className: cls,
|
|
40
28
|
ruleIndex: -2,
|
|
41
29
|
sheetIndex: -2
|
|
42
30
|
});
|
|
43
|
-
registry.refCounts.set(
|
|
31
|
+
registry.refCounts.set(cls, 0);
|
|
44
32
|
}
|
|
45
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* @deprecated Use `hydrateTastyClasses()` instead. This alias exists
|
|
36
|
+
* for backwards compatibility and will be removed in a future major version.
|
|
37
|
+
*/
|
|
38
|
+
function hydrateTastyCache(state) {
|
|
39
|
+
if (state?.entries) hydrateTastyClasses(Object.values(state.entries));
|
|
40
|
+
else hydrateTastyClasses();
|
|
41
|
+
}
|
|
46
42
|
//#endregion
|
|
47
|
-
export { hydrateTastyCache };
|
|
43
|
+
export { hydrateTastyCache, hydrateTastyClasses };
|
|
48
44
|
|
|
49
45
|
//# sourceMappingURL=hydrate.js.map
|
package/dist/ssr/hydrate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hydrate.js","names":[],"sources":["../../src/ssr/hydrate.ts"],"sourcesContent":["/**\n * Client-side cache hydration for SSR.\n *\n * Pre-populates the client injector's
|
|
1
|
+
{"version":3,"file":"hydrate.js","names":[],"sources":["../../src/ssr/hydrate.ts"],"sourcesContent":["/**\n * Client-side cache hydration for SSR/RSC.\n *\n * Pre-populates the client injector's rules map with class names\n * rendered on the server. With hash-based naming, the client derives\n * the same class name from the same cache key, so only the class name\n * list needs to cross the wire — no cache keys or counters.\n */\n\nimport { getGlobalInjector } from '../config';\nimport { HYDRATED_RULE_INDEX } from '../injector/types';\n\n/**\n * Pre-populate the client-side style registry from the server's class name list.\n *\n * Call this before ReactDOM.hydrateRoot() or ensure it runs before\n * any tasty() component renders on the client.\n *\n * When called without arguments, reads the class list from `window.__TASTY__`\n * (populated by inline scripts emitted during SSR/RSC streaming).\n */\nexport function hydrateTastyClasses(classes?: string[]): void {\n if (typeof document === 'undefined') return;\n\n if (!classes) {\n classes = typeof window !== 'undefined' ? window.__TASTY__ : undefined;\n }\n\n if (!classes?.length) return;\n\n const injector = getGlobalInjector();\n const registry = injector._sheetManager.getRegistry(document);\n\n for (const cls of classes) {\n if (!registry.rules.has(cls)) {\n registry.rules.set(cls, {\n className: cls,\n ruleIndex: HYDRATED_RULE_INDEX,\n sheetIndex: HYDRATED_RULE_INDEX,\n });\n registry.refCounts.set(cls, 0);\n }\n }\n}\n\n/**\n * @deprecated Use `hydrateTastyClasses()` instead. This alias exists\n * for backwards compatibility and will be removed in a future major version.\n */\nexport function hydrateTastyCache(state?: {\n entries?: Record<string, string>;\n}): void {\n if (state?.entries) {\n hydrateTastyClasses(Object.values(state.entries));\n } else {\n hydrateTastyClasses();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,oBAAoB,SAA0B;AAC5D,KAAI,OAAO,aAAa,YAAa;AAErC,KAAI,CAAC,QACH,WAAU,OAAO,WAAW,cAAc,OAAO,YAAY,KAAA;AAG/D,KAAI,CAAC,SAAS,OAAQ;CAGtB,MAAM,WADW,mBAAmB,CACV,cAAc,YAAY,SAAS;AAE7D,MAAK,MAAM,OAAO,QAChB,KAAI,CAAC,SAAS,MAAM,IAAI,IAAI,EAAE;AAC5B,WAAS,MAAM,IAAI,KAAK;GACtB,WAAW;GACX,WAAA;GACA,YAAA;GACD,CAAC;AACF,WAAS,UAAU,IAAI,KAAK,EAAE;;;;;;;AASpC,SAAgB,kBAAkB,OAEzB;AACP,KAAI,OAAO,QACT,qBAAoB,OAAO,OAAO,MAAM,QAAQ,CAAC;KAEjD,sBAAqB"}
|
package/dist/ssr/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ServerStyleCollector } from "./collector.js";
|
|
2
2
|
import { getSSRCollector, runWithCollector } from "./async-storage.js";
|
|
3
|
-
import { hydrateTastyCache } from "./hydrate.js";
|
|
4
|
-
export {
|
|
3
|
+
import { hydrateTastyCache, hydrateTastyClasses } from "./hydrate.js";
|
|
4
|
+
export { ServerStyleCollector, getSSRCollector, hydrateTastyCache, hydrateTastyClasses, runWithCollector };
|
package/dist/ssr/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { registerSSRCollectorGetterGlobal } from "./ssr-collector-ref.js";
|
|
2
2
|
import { ServerStyleCollector } from "./collector.js";
|
|
3
3
|
import { getSSRCollector, runWithCollector } from "./async-storage.js";
|
|
4
|
-
import { hydrateTastyCache } from "./hydrate.js";
|
|
4
|
+
import { hydrateTastyCache, hydrateTastyClasses } from "./hydrate.js";
|
|
5
5
|
//#region src/ssr/index.ts
|
|
6
6
|
registerSSRCollectorGetterGlobal(getSSRCollector);
|
|
7
7
|
//#endregion
|
|
8
|
-
export { ServerStyleCollector, getSSRCollector, hydrateTastyCache, runWithCollector };
|
|
8
|
+
export { ServerStyleCollector, getSSRCollector, hydrateTastyCache, hydrateTastyClasses, runWithCollector };
|
|
9
9
|
|
|
10
10
|
//# sourceMappingURL=index.js.map
|
package/dist/ssr/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/ssr/index.ts"],"sourcesContent":["/**\n * SSR entry point for @tenphi/tasty.\n *\n * Provides the core SSR infrastructure: ServerStyleCollector,\n * AsyncLocalStorage integration, and cache hydration.\n *\n * Import from '@tenphi/tasty/ssr'.\n */\n\n// Core collector\nexport { ServerStyleCollector } from './collector';\
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/ssr/index.ts"],"sourcesContent":["/**\n * SSR entry point for @tenphi/tasty.\n *\n * Provides the core SSR infrastructure: ServerStyleCollector,\n * AsyncLocalStorage integration, and cache hydration.\n *\n * Import from '@tenphi/tasty/ssr'.\n */\n\n// Core collector\nexport { ServerStyleCollector } from './collector';\n\n// AsyncLocalStorage integration for Astro / generic frameworks\nexport { runWithCollector, getSSRCollector } from './async-storage';\n\n// Client-side cache hydration\nexport { hydrateTastyClasses, hydrateTastyCache } from './hydrate';\n\n// Register the ALS getter so hooks can find the collector\n// without importing 'node:async_hooks' in the browser bundle.\n// Uses globalThis so the getter is visible across separate module graphs.\nimport { getSSRCollector } from './async-storage';\nimport { registerSSRCollectorGetterGlobal } from './ssr-collector-ref';\n\nregisterSSRCollectorGetterGlobal(getSSRCollector);\n"],"mappings":";;;;;AAwBA,iCAAiC,gBAAgB"}
|
package/dist/ssr/next.d.ts
CHANGED
|
@@ -6,9 +6,10 @@ import { ReactNode } from "react";
|
|
|
6
6
|
interface TastyRegistryProps {
|
|
7
7
|
children: ReactNode;
|
|
8
8
|
/**
|
|
9
|
-
* Whether to embed the
|
|
10
|
-
* Set to false to skip
|
|
11
|
-
*
|
|
9
|
+
* Whether to embed the class-list script for client hydration.
|
|
10
|
+
* Set to false to skip class transfer (e.g. for CSP restrictions).
|
|
11
|
+
* Without it, client components may re-inject CSS that already exists
|
|
12
|
+
* in server-rendered `<style>` tags. Default: true.
|
|
12
13
|
*/
|
|
13
14
|
transferCache?: boolean;
|
|
14
15
|
}
|
package/dist/ssr/next.js
CHANGED
|
@@ -3,7 +3,7 @@ import { getConfig } from "../config.js";
|
|
|
3
3
|
import { registerSSRCollectorGetter } from "./ssr-collector-ref.js";
|
|
4
4
|
import { TastySSRContext } from "./context.js";
|
|
5
5
|
import { ServerStyleCollector } from "./collector.js";
|
|
6
|
-
import {
|
|
6
|
+
import { hydrateTastyClasses } from "./hydrate.js";
|
|
7
7
|
import { Fragment, createElement, useState } from "react";
|
|
8
8
|
import { useServerInsertedHTML } from "next/navigation";
|
|
9
9
|
//#region src/ssr/next.ts
|
|
@@ -15,7 +15,7 @@ import { useServerInsertedHTML } from "next/navigation";
|
|
|
15
15
|
*
|
|
16
16
|
* Import from '@tenphi/tasty/ssr/next'.
|
|
17
17
|
*/
|
|
18
|
-
if (typeof window !== "undefined"
|
|
18
|
+
if (typeof window !== "undefined") hydrateTastyClasses();
|
|
19
19
|
/**
|
|
20
20
|
* Next.js App Router registry for Tasty SSR.
|
|
21
21
|
*
|
|
@@ -52,7 +52,7 @@ function TastyRegistry({ children, transferCache = true }) {
|
|
|
52
52
|
useServerInsertedHTML(() => {
|
|
53
53
|
if (!collector) return null;
|
|
54
54
|
const css = collector.flushCSS();
|
|
55
|
-
const
|
|
55
|
+
const classNames = collector.getRenderedClassNames();
|
|
56
56
|
if (!css) return null;
|
|
57
57
|
const styleEl = createElement("style", {
|
|
58
58
|
key: "tasty-ssr-styles",
|
|
@@ -60,11 +60,11 @@ function TastyRegistry({ children, transferCache = true }) {
|
|
|
60
60
|
nonce,
|
|
61
61
|
dangerouslySetInnerHTML: { __html: css }
|
|
62
62
|
});
|
|
63
|
-
if (!transferCache) return styleEl;
|
|
63
|
+
if (!transferCache || classNames.length === 0) return styleEl;
|
|
64
64
|
return createElement(Fragment, null, styleEl, createElement("script", {
|
|
65
65
|
key: "tasty-ssr-cache",
|
|
66
66
|
nonce,
|
|
67
|
-
dangerouslySetInnerHTML: { __html: `(window.
|
|
67
|
+
dangerouslySetInnerHTML: { __html: `(window.__TASTY__=window.__TASTY__||[]).push(${classNames.map((n) => `"${n}"`).join(",")})` }
|
|
68
68
|
}));
|
|
69
69
|
});
|
|
70
70
|
return createElement(TastySSRContext.Provider, { value: collector }, children);
|
package/dist/ssr/next.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"next.js","names":[],"sources":["../../src/ssr/next.ts"],"sourcesContent":["/**\n * Next.js integration for Tasty SSR.\n *\n * Provides TastyRegistry for App Router (streaming via useServerInsertedHTML)\n * and createTastySSRDocument for Pages Router (non-streaming).\n *\n * Import from '@tenphi/tasty/ssr/next'.\n */\n\n'use client';\n\n/// <reference path=\"./next-navigation.d.ts\" />\n\nimport { createElement, Fragment, useState, type ReactNode } from 'react';\nimport { useServerInsertedHTML } from 'next/navigation';\n\nimport { getConfig } from '../config';\nimport { ServerStyleCollector } from './collector';\nimport { TastySSRContext } from './context';\nimport {
|
|
1
|
+
{"version":3,"file":"next.js","names":[],"sources":["../../src/ssr/next.ts"],"sourcesContent":["/**\n * Next.js integration for Tasty SSR.\n *\n * Provides TastyRegistry for App Router (streaming via useServerInsertedHTML)\n * and createTastySSRDocument for Pages Router (non-streaming).\n *\n * Import from '@tenphi/tasty/ssr/next'.\n */\n\n'use client';\n\n/// <reference path=\"./next-navigation.d.ts\" />\n\nimport { createElement, Fragment, useState, type ReactNode } from 'react';\nimport { useServerInsertedHTML } from 'next/navigation';\n\nimport { getConfig } from '../config';\nimport { ServerStyleCollector } from './collector';\nimport { TastySSRContext } from './context';\nimport { hydrateTastyClasses } from './hydrate';\nimport { registerSSRCollectorGetter } from './ssr-collector-ref';\n\n// Auto-hydrate on module load (client only).\n// Reads the class name list from `window.__TASTY__` populated by streaming scripts.\nif (typeof window !== 'undefined') {\n hydrateTastyClasses();\n}\n\nexport interface TastyRegistryProps {\n children: ReactNode;\n /**\n * Whether to embed the class-list script for client hydration.\n * Set to false to skip class transfer (e.g. for CSP restrictions).\n * Without it, client components may re-inject CSS that already exists\n * in server-rendered `<style>` tags. Default: true.\n */\n transferCache?: boolean;\n}\n\n/**\n * Next.js App Router registry for Tasty SSR.\n *\n * Wraps the component tree with a ServerStyleCollector and flushes\n * collected CSS into the HTML stream via useServerInsertedHTML.\n *\n * @example\n * ```tsx\n * // app/tasty-registry.tsx\n * 'use client';\n * import { TastyRegistry } from '@tenphi/tasty/ssr/next';\n * export default function TastyStyleRegistry({ children }) {\n * return <TastyRegistry>{children}</TastyRegistry>;\n * }\n *\n * // app/layout.tsx\n * import TastyStyleRegistry from './tasty-registry';\n * export default function RootLayout({ children }) {\n * return <html><body>\n * <TastyStyleRegistry>{children}</TastyStyleRegistry>\n * </body></html>;\n * }\n * ```\n */\nexport function TastyRegistry({\n children,\n transferCache = true,\n}: TastyRegistryProps) {\n const isClient = typeof window !== 'undefined';\n\n const [collector] = useState(() => {\n if (isClient) return null;\n\n const instance = new ServerStyleCollector();\n\n registerSSRCollectorGetter(() => instance);\n\n return instance;\n });\n const nonce = getConfig().nonce;\n\n useServerInsertedHTML(() => {\n if (!collector) return null;\n\n const css = collector.flushCSS();\n const classNames = collector.getRenderedClassNames();\n\n if (!css) return null;\n\n const styleEl = createElement('style', {\n key: 'tasty-ssr-styles',\n 'data-tasty-ssr': '',\n nonce,\n dangerouslySetInnerHTML: { __html: css },\n });\n\n if (!transferCache || classNames.length === 0) return styleEl;\n\n const classListJSON = classNames.map((n) => `\"${n}\"`).join(',');\n\n const scriptEl = createElement('script', {\n key: 'tasty-ssr-cache',\n nonce,\n dangerouslySetInnerHTML: {\n __html: `(window.__TASTY__=window.__TASTY__||[]).push(${classListJSON})`,\n },\n });\n\n return createElement(Fragment, null, styleEl, scriptEl);\n });\n\n return createElement(\n TastySSRContext.Provider,\n { value: collector },\n children,\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAwBA,IAAI,OAAO,WAAW,YACpB,sBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;AAsCvB,SAAgB,cAAc,EAC5B,UACA,gBAAgB,QACK;CACrB,MAAM,WAAW,OAAO,WAAW;CAEnC,MAAM,CAAC,aAAa,eAAe;AACjC,MAAI,SAAU,QAAO;EAErB,MAAM,WAAW,IAAI,sBAAsB;AAE3C,mCAAiC,SAAS;AAE1C,SAAO;GACP;CACF,MAAM,QAAQ,WAAW,CAAC;AAE1B,6BAA4B;AAC1B,MAAI,CAAC,UAAW,QAAO;EAEvB,MAAM,MAAM,UAAU,UAAU;EAChC,MAAM,aAAa,UAAU,uBAAuB;AAEpD,MAAI,CAAC,IAAK,QAAO;EAEjB,MAAM,UAAU,cAAc,SAAS;GACrC,KAAK;GACL,kBAAkB;GAClB;GACA,yBAAyB,EAAE,QAAQ,KAAK;GACzC,CAAC;AAEF,MAAI,CAAC,iBAAiB,WAAW,WAAW,EAAG,QAAO;AAYtD,SAAO,cAAc,UAAU,MAAM,SARpB,cAAc,UAAU;GACvC,KAAK;GACL;GACA,yBAAyB,EACvB,QAAQ,gDANU,WAAW,KAAK,MAAM,IAAI,EAAE,GAAG,CAAC,KAAK,IAAI,CAMW,IACvE;GACF,CAAC,CAEqD;GACvD;AAEF,QAAO,cACL,gBAAgB,UAChB,EAAE,OAAO,WAAW,EACpB,SACD"}
|