@tenphi/tasty 3.5.0 → 3.7.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/dist/{astro-ib7E7V4Y.js → astro-CeYENy2x.js} +61 -67
- package/dist/astro-CeYENy2x.js.map +1 -0
- package/dist/{collector-C6TtL8HJ.js → collector-B3OsM252.js} +2 -2
- package/dist/{collector-C6TtL8HJ.js.map → collector-B3OsM252.js.map} +1 -1
- package/dist/core/index.js +1 -1
- package/dist/{core-Bq7w2kti.js → core-DGm0CFHP.js} +76 -30
- package/dist/{core-Bq7w2kti.js.map → core-DGm0CFHP.js.map} +1 -1
- package/dist/css-resources-Cyl_axbI.js +149 -0
- package/dist/css-resources-Cyl_axbI.js.map +1 -0
- package/dist/{format-rules-rCZ37rqY.js → format-rules-XRw9u7d4.js} +2 -28
- package/dist/format-rules-XRw9u7d4.js.map +1 -0
- package/dist/index.js +2 -2
- package/dist/ssr/astro-middleware-extract-static.js +1 -1
- package/dist/ssr/astro-middleware-extract.js +1 -1
- package/dist/ssr/astro-middleware-static.js +1 -1
- package/dist/ssr/astro-middleware.js +1 -1
- package/dist/ssr/astro.d.ts +9 -1
- package/dist/ssr/astro.js +1 -1
- package/dist/ssr/index.js +2 -2
- package/dist/ssr/next-config.d.ts +66 -0
- package/dist/ssr/next-config.js +115 -0
- package/dist/ssr/next-config.js.map +1 -0
- package/dist/ssr/next.d.ts +8 -1
- package/dist/ssr/next.js +24 -7
- package/dist/ssr/next.js.map +1 -1
- package/dist/ssr-collector-ref-COs_ioWl.js +29 -0
- package/dist/ssr-collector-ref-COs_ioWl.js.map +1 -0
- package/docs/debug.md +13 -0
- package/docs/runtime-benchmarks.md +185 -12
- package/docs/ssr.md +151 -31
- package/package.json +9 -1
- package/dist/astro-ib7E7V4Y.js.map +0 -1
- package/dist/format-rules-rCZ37rqY.js.map +0 -1
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
//#region src/ssr/css-resources.ts
|
|
2
|
+
/**
|
|
3
|
+
* Find CSS resource references that cannot safely move to a shared stylesheet.
|
|
4
|
+
*
|
|
5
|
+
* This is intentionally a small scanner rather than a CSS parser. It handles
|
|
6
|
+
* comments, strings, escaped identifiers, url(), string-valued image/src
|
|
7
|
+
* functions, and string @import rules without interpreting unrelated CSS.
|
|
8
|
+
*/
|
|
9
|
+
function skipCSSString(css, start, quote) {
|
|
10
|
+
for (let index = start + 1; index < css.length; index++) if (css[index] === "\\") index++;
|
|
11
|
+
else if (css[index] === quote) return index + 1;
|
|
12
|
+
return css.length;
|
|
13
|
+
}
|
|
14
|
+
function decodeCSSEscapes(value) {
|
|
15
|
+
return value.replace(/\\(?:([\da-f]{1,6})\s?|\r\n|[\n\r\f]|(.))/gi, (_match, hex, escaped) => {
|
|
16
|
+
if (hex) {
|
|
17
|
+
const codePoint = Number.parseInt(hex, 16);
|
|
18
|
+
return codePoint === 0 || codePoint > 1114111 ? "�" : String.fromCodePoint(codePoint);
|
|
19
|
+
}
|
|
20
|
+
return escaped ?? "";
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function readCSSIdentifier(css, start) {
|
|
24
|
+
let name = "";
|
|
25
|
+
let index = start;
|
|
26
|
+
while (index < css.length) {
|
|
27
|
+
const char = css[index];
|
|
28
|
+
if (/[-_a-z\d]/i.test(char) || char.charCodeAt(0) >= 128) {
|
|
29
|
+
name += char;
|
|
30
|
+
index++;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (char !== "\\" || index + 1 >= css.length) break;
|
|
34
|
+
const hex = css.slice(index + 1).match(/^[\da-f]{1,6}/i)?.[0];
|
|
35
|
+
if (hex) {
|
|
36
|
+
name += decodeCSSEscapes(`\\${hex}`);
|
|
37
|
+
index += hex.length + 1;
|
|
38
|
+
if (/\s/.test(css[index] ?? "")) index++;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (/\r|\n|\f/.test(css[index + 1])) break;
|
|
42
|
+
name += css[index + 1];
|
|
43
|
+
index += 2;
|
|
44
|
+
}
|
|
45
|
+
return index === start ? null : {
|
|
46
|
+
name,
|
|
47
|
+
end: index
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function skipCSSWhitespaceAndComments(css, start) {
|
|
51
|
+
let index = start;
|
|
52
|
+
for (;;) {
|
|
53
|
+
while (/\s/.test(css[index] ?? "")) index++;
|
|
54
|
+
if (css[index] !== "/" || css[index + 1] !== "*") return index;
|
|
55
|
+
const commentEnd = css.indexOf("*/", index + 2);
|
|
56
|
+
if (commentEnd === -1) return css.length;
|
|
57
|
+
index = commentEnd + 2;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function classifyCSSResource(rawURL, rejectRootRelative) {
|
|
61
|
+
const url = decodeCSSEscapes(rawURL).trim();
|
|
62
|
+
if (!url || url.startsWith("//") || /^[a-z][a-z\d+.-]*:/i.test(url)) return null;
|
|
63
|
+
if (url.startsWith("/")) return rejectRootRelative ? {
|
|
64
|
+
url: rawURL,
|
|
65
|
+
rootRelative: true
|
|
66
|
+
} : null;
|
|
67
|
+
return {
|
|
68
|
+
url: rawURL,
|
|
69
|
+
rootRelative: false
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function findUnsafeCSSResource(css, rejectRootRelative) {
|
|
73
|
+
const functionStack = [];
|
|
74
|
+
const stringResourceFunctions = new Set([
|
|
75
|
+
"image",
|
|
76
|
+
"image-set",
|
|
77
|
+
"-webkit-image-set",
|
|
78
|
+
"src"
|
|
79
|
+
]);
|
|
80
|
+
for (let index = 0; index < css.length; index++) {
|
|
81
|
+
if (css[index] === "/" && css[index + 1] === "*") {
|
|
82
|
+
const commentEnd = css.indexOf("*/", index + 2);
|
|
83
|
+
index = commentEnd === -1 ? css.length : commentEnd + 1;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const quote = css[index];
|
|
87
|
+
if (quote === "\"" || quote === "'") {
|
|
88
|
+
const stringEnd = skipCSSString(css, index, quote);
|
|
89
|
+
if (stringResourceFunctions.has(functionStack.at(-1) ?? "")) {
|
|
90
|
+
const unsafe = classifyCSSResource(css.slice(index + 1, stringEnd - 1), rejectRootRelative);
|
|
91
|
+
if (unsafe) return unsafe;
|
|
92
|
+
}
|
|
93
|
+
index = stringEnd - 1;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (css[index] === ")") {
|
|
97
|
+
functionStack.pop();
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (css[index] === "(") {
|
|
101
|
+
functionStack.push(null);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (css[index] === "@") {
|
|
105
|
+
const atRule = readCSSIdentifier(css, index + 1);
|
|
106
|
+
if (atRule?.name.toLowerCase() === "import") {
|
|
107
|
+
const valueStart = skipCSSWhitespaceAndComments(css, atRule.end);
|
|
108
|
+
const importQuote = css[valueStart];
|
|
109
|
+
if (importQuote === "\"" || importQuote === "'") {
|
|
110
|
+
const valueEnd = skipCSSString(css, valueStart, importQuote);
|
|
111
|
+
const unsafe = classifyCSSResource(css.slice(valueStart + 1, valueEnd - 1), rejectRootRelative);
|
|
112
|
+
if (unsafe) return unsafe;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const identifier = readCSSIdentifier(css, index);
|
|
118
|
+
if (!identifier || css[identifier.end] !== "(") continue;
|
|
119
|
+
const functionName = identifier.name.toLowerCase();
|
|
120
|
+
if (functionName !== "url") {
|
|
121
|
+
functionStack.push(functionName);
|
|
122
|
+
index = identifier.end;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const valueStart = skipCSSWhitespaceAndComments(css, identifier.end + 1);
|
|
126
|
+
const urlQuote = css[valueStart];
|
|
127
|
+
const quoted = urlQuote === "\"" || urlQuote === "'";
|
|
128
|
+
let valueEnd;
|
|
129
|
+
if (quoted) {
|
|
130
|
+
valueEnd = skipCSSString(css, valueStart, urlQuote) - 1;
|
|
131
|
+
index = css.indexOf(")", valueEnd + 1);
|
|
132
|
+
} else {
|
|
133
|
+
valueEnd = valueStart;
|
|
134
|
+
while (valueEnd < css.length && css[valueEnd] !== ")") {
|
|
135
|
+
if (css[valueEnd] === "\\") valueEnd++;
|
|
136
|
+
valueEnd++;
|
|
137
|
+
}
|
|
138
|
+
index = valueEnd;
|
|
139
|
+
}
|
|
140
|
+
if (index === -1) return null;
|
|
141
|
+
const unsafe = classifyCSSResource(css.slice(valueStart + (quoted ? 1 : 0), valueEnd), rejectRootRelative);
|
|
142
|
+
if (unsafe) return unsafe;
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
//#endregion
|
|
147
|
+
export { findUnsafeCSSResource as t };
|
|
148
|
+
|
|
149
|
+
//# sourceMappingURL=css-resources-Cyl_axbI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"css-resources-Cyl_axbI.js","names":[],"sources":["../src/ssr/css-resources.ts"],"sourcesContent":["/**\n * Find CSS resource references that cannot safely move to a shared stylesheet.\n *\n * This is intentionally a small scanner rather than a CSS parser. It handles\n * comments, strings, escaped identifiers, url(), string-valued image/src\n * functions, and string @import rules without interpreting unrelated CSS.\n */\n\nfunction skipCSSString(css: string, start: number, quote: string): number {\n for (let index = start + 1; index < css.length; index++) {\n if (css[index] === '\\\\') {\n index++;\n } else if (css[index] === quote) {\n return index + 1;\n }\n }\n return css.length;\n}\n\nfunction decodeCSSEscapes(value: string): string {\n return value.replace(\n /\\\\(?:([\\da-f]{1,6})\\s?|\\r\\n|[\\n\\r\\f]|(.))/gi,\n (_match, hex: string | undefined, escaped: string | undefined) => {\n if (hex) {\n const codePoint = Number.parseInt(hex, 16);\n return codePoint === 0 || codePoint > 0x10ffff\n ? '\\ufffd'\n : String.fromCodePoint(codePoint);\n }\n return escaped ?? '';\n },\n );\n}\n\nfunction readCSSIdentifier(\n css: string,\n start: number,\n): { name: string; end: number } | null {\n let name = '';\n let index = start;\n\n while (index < css.length) {\n const char = css[index];\n if (/[-_a-z\\d]/i.test(char) || char.charCodeAt(0) >= 0x80) {\n name += char;\n index++;\n continue;\n }\n if (char !== '\\\\' || index + 1 >= css.length) break;\n\n const hex = css.slice(index + 1).match(/^[\\da-f]{1,6}/i)?.[0];\n if (hex) {\n name += decodeCSSEscapes(`\\\\${hex}`);\n index += hex.length + 1;\n if (/\\s/.test(css[index] ?? '')) index++;\n continue;\n }\n\n if (/\\r|\\n|\\f/.test(css[index + 1])) break;\n name += css[index + 1];\n index += 2;\n }\n\n return index === start ? null : { name, end: index };\n}\n\nfunction skipCSSWhitespaceAndComments(css: string, start: number): number {\n let index = start;\n for (;;) {\n while (/\\s/.test(css[index] ?? '')) index++;\n if (css[index] !== '/' || css[index + 1] !== '*') return index;\n const commentEnd = css.indexOf('*/', index + 2);\n if (commentEnd === -1) return css.length;\n index = commentEnd + 2;\n }\n}\n\nexport interface UnsafeCSSResource {\n url: string;\n rootRelative: boolean;\n}\n\nfunction classifyCSSResource(\n rawURL: string,\n rejectRootRelative: boolean,\n): UnsafeCSSResource | null {\n const url = decodeCSSEscapes(rawURL).trim();\n if (!url || url.startsWith('//') || /^[a-z][a-z\\d+.-]*:/i.test(url)) {\n return null;\n }\n if (url.startsWith('/')) {\n return rejectRootRelative ? { url: rawURL, rootRelative: true } : null;\n }\n return { url: rawURL, rootRelative: false };\n}\n\nexport function findUnsafeCSSResource(\n css: string,\n rejectRootRelative: boolean,\n): UnsafeCSSResource | null {\n const functionStack: (string | null)[] = [];\n const stringResourceFunctions = new Set([\n 'image',\n 'image-set',\n '-webkit-image-set',\n 'src',\n ]);\n\n for (let index = 0; index < css.length; index++) {\n if (css[index] === '/' && css[index + 1] === '*') {\n const commentEnd = css.indexOf('*/', index + 2);\n index = commentEnd === -1 ? css.length : commentEnd + 1;\n continue;\n }\n\n const quote = css[index];\n if (quote === '\"' || quote === \"'\") {\n const stringEnd = skipCSSString(css, index, quote);\n if (stringResourceFunctions.has(functionStack.at(-1) ?? '')) {\n const unsafe = classifyCSSResource(\n css.slice(index + 1, stringEnd - 1),\n rejectRootRelative,\n );\n if (unsafe) return unsafe;\n }\n index = stringEnd - 1;\n continue;\n }\n\n if (css[index] === ')') {\n functionStack.pop();\n continue;\n }\n\n if (css[index] === '(') {\n functionStack.push(null);\n continue;\n }\n\n if (css[index] === '@') {\n const atRule = readCSSIdentifier(css, index + 1);\n if (atRule?.name.toLowerCase() === 'import') {\n const valueStart = skipCSSWhitespaceAndComments(css, atRule.end);\n const importQuote = css[valueStart];\n if (importQuote === '\"' || importQuote === \"'\") {\n const valueEnd = skipCSSString(css, valueStart, importQuote);\n const unsafe = classifyCSSResource(\n css.slice(valueStart + 1, valueEnd - 1),\n rejectRootRelative,\n );\n if (unsafe) return unsafe;\n }\n }\n continue;\n }\n\n const identifier = readCSSIdentifier(css, index);\n if (!identifier || css[identifier.end] !== '(') continue;\n\n const functionName = identifier.name.toLowerCase();\n if (functionName !== 'url') {\n functionStack.push(functionName);\n index = identifier.end;\n continue;\n }\n\n const valueStart = skipCSSWhitespaceAndComments(css, identifier.end + 1);\n const urlQuote = css[valueStart];\n const quoted = urlQuote === '\"' || urlQuote === \"'\";\n let valueEnd: number;\n if (quoted) {\n valueEnd = skipCSSString(css, valueStart, urlQuote) - 1;\n index = css.indexOf(')', valueEnd + 1);\n } else {\n valueEnd = valueStart;\n while (valueEnd < css.length && css[valueEnd] !== ')') {\n if (css[valueEnd] === '\\\\') valueEnd++;\n valueEnd++;\n }\n index = valueEnd;\n }\n\n if (index === -1) return null;\n const unsafe = classifyCSSResource(\n css.slice(valueStart + (quoted ? 1 : 0), valueEnd),\n rejectRootRelative,\n );\n if (unsafe) return unsafe;\n }\n\n return null;\n}\n"],"mappings":"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"}
|
|
@@ -1,30 +1,4 @@
|
|
|
1
1
|
import { Gt as parseStyle, yt as getEffectiveDefinition } from "./config-B3gPdCqd.js";
|
|
2
|
-
//#region src/ssr/ssr-collector-ref.ts
|
|
3
|
-
const GETTER_KEY = "__tasty_ssr_collector_getter__";
|
|
4
|
-
let _getSSRCollector = null;
|
|
5
|
-
/**
|
|
6
|
-
* Register the collector getter in the current module graph only.
|
|
7
|
-
* Used by Next.js TastyRegistry.
|
|
8
|
-
*/
|
|
9
|
-
function registerSSRCollectorGetter(fn) {
|
|
10
|
-
_getSSRCollector = fn;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Register the collector getter on globalThis so it is visible across
|
|
14
|
-
* separate module graphs (e.g. Astro middleware ↔ page components).
|
|
15
|
-
*/
|
|
16
|
-
function registerSSRCollectorGetterGlobal(fn) {
|
|
17
|
-
globalThis[GETTER_KEY] = fn;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Retrieve the SSR collector: module-level first, globalThis fallback.
|
|
21
|
-
*/
|
|
22
|
-
function getRegisteredSSRCollector() {
|
|
23
|
-
if (_getSSRCollector) return _getSSRCollector();
|
|
24
|
-
const getter = globalThis[GETTER_KEY];
|
|
25
|
-
return getter ? getter() : null;
|
|
26
|
-
}
|
|
27
|
-
//#endregion
|
|
28
2
|
//#region src/ssr/format-property.ts
|
|
29
3
|
/**
|
|
30
4
|
* Format a single @property rule as a CSS string.
|
|
@@ -125,6 +99,6 @@ function formatRules(rules, className) {
|
|
|
125
99
|
return cssRules.join("\n");
|
|
126
100
|
}
|
|
127
101
|
//#endregion
|
|
128
|
-
export {
|
|
102
|
+
export { formatPropertyCSS as n, formatRules as t };
|
|
129
103
|
|
|
130
|
-
//# sourceMappingURL=format-rules-
|
|
104
|
+
//# sourceMappingURL=format-rules-XRw9u7d4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-rules-XRw9u7d4.js","names":[],"sources":["../src/ssr/format-property.ts","../src/ssr/format-rules.ts"],"sourcesContent":["/**\n * Format @property CSS rules for SSR output.\n *\n * Replicates the CSS construction from StyleInjector.property()\n * but returns a CSS string instead of inserting into the DOM.\n */\n\nimport type { PropertyDefinition } from '../injector/types';\nimport { getEffectiveDefinition } from '../properties';\nimport type { StyleValue } from '../utils/styles';\nimport { parseStyle } from '../utils/styles';\n\n/**\n * Format a single @property rule as a CSS string.\n *\n * Returns the full `@property --name { ... }` text, or empty string\n * if the token is invalid.\n */\nexport function formatPropertyCSS(\n token: string,\n definition: PropertyDefinition,\n): string {\n const result = getEffectiveDefinition(token, definition);\n if (!result.isValid) return '';\n\n return buildPropertyRule(result.cssName, result.definition);\n}\n\nfunction buildPropertyRule(\n cssName: string,\n definition: PropertyDefinition,\n): string {\n const parts: string[] = [];\n\n if (definition.syntax != null) {\n let syntax = String(definition.syntax).trim();\n if (!/^['\"]/u.test(syntax)) syntax = `\"${syntax}\"`;\n parts.push(`syntax: ${syntax};`);\n }\n\n const inherits = definition.inherits ?? true;\n parts.push(`inherits: ${inherits ? 'true' : 'false'};`);\n\n if (definition.initialValue != null) {\n let initialValueStr: string;\n if (typeof definition.initialValue === 'number') {\n initialValueStr = String(definition.initialValue);\n } else {\n initialValueStr = parseStyle(\n definition.initialValue as StyleValue,\n ).output;\n }\n parts.push(`initial-value: ${initialValueStr};`);\n }\n\n const declarations = parts.join(' ').trim();\n return `@property ${cssName} { ${declarations} }`;\n}\n","/**\n * Shared CSS rule formatting utility.\n *\n * Extracted from SheetManager to allow both the DOM-based injector (client)\n * and the ServerStyleCollector (server) to produce identical CSS text\n * from StyleResult arrays.\n */\n\nimport type { StyleResult } from '../pipeline';\n\n/**\n * Resolve selectors for a rule, applying className-based specificity doubling\n * and rootPrefix handling. Mirrors the logic in StyleInjector.inject().\n */\nfunction resolveSelector(rule: StyleResult, className: string): string {\n let selector = rule.selector;\n\n if (rule.needsClassName) {\n const selectorParts = selector ? selector.split('|||') : [''];\n const classPrefix = `.${className}.${className}`;\n\n selector = selectorParts\n .map((part) => {\n const classSelector = part ? `${classPrefix}${part}` : classPrefix;\n\n if (rule.rootPrefix) {\n return `${rule.rootPrefix} ${classSelector}`;\n }\n return classSelector;\n })\n .join(', ');\n }\n\n return selector;\n}\n\ninterface GroupedRule {\n selector: string;\n declarations: string;\n atRules?: string[];\n startingStyle?: boolean;\n}\n\n/**\n * Group rules by selector + at-rules + startingStyle and merge their declarations.\n * Mirrors the grouping logic in SheetManager.insertRule().\n */\nfunction groupRules(rules: GroupedRule[]): GroupedRule[] {\n const groupMap = new Map<string, GroupedRule>();\n const order: string[] = [];\n\n const atKey = (at?: string[]) => (at && at.length ? at.join('|') : '');\n\n for (const r of rules) {\n const key = `${atKey(r.atRules)}||${r.selector}||${r.startingStyle ? '1' : '0'}`;\n const existing = groupMap.get(key);\n if (existing) {\n existing.declarations = existing.declarations\n ? `${existing.declarations} ${r.declarations}`\n : r.declarations;\n } else {\n groupMap.set(key, {\n selector: r.selector,\n atRules: r.atRules,\n startingStyle: r.startingStyle,\n declarations: r.declarations,\n });\n order.push(key);\n }\n }\n\n return order.map((key) => groupMap.get(key)!);\n}\n\n/**\n * Format an array of StyleResult rules into a CSS text string.\n *\n * Applies className-based specificity doubling (.cls.cls),\n * groups rules by selector + at-rules, and wraps with at-rule blocks.\n *\n * Produces the same CSS text as SheetManager.insertRule() would insert\n * into the DOM, but as a plain string suitable for SSR output.\n */\nexport function formatRules(rules: StyleResult[], className: string): string {\n if (rules.length === 0) return '';\n\n const resolvedRules = rules.map((rule) => ({\n selector: resolveSelector(rule, className),\n declarations: rule.declarations,\n atRules: rule.atRules,\n startingStyle: rule.startingStyle,\n }));\n\n const grouped = groupRules(resolvedRules);\n const cssRules: string[] = [];\n\n for (const rule of grouped) {\n const innerContent = rule.startingStyle\n ? `@starting-style { ${rule.declarations} }`\n : rule.declarations;\n const baseRule = `${rule.selector} { ${innerContent} }`;\n\n let fullRule = baseRule;\n if (rule.atRules && rule.atRules.length > 0) {\n fullRule = rule.atRules.reduce(\n (css, atRule) => `${atRule} { ${css} }`,\n baseRule,\n );\n }\n\n cssRules.push(fullRule);\n }\n\n return cssRules.join('\\n');\n}\n"],"mappings":";;;;;;;;AAkBA,SAAgB,kBACd,OACA,YACQ;CACR,MAAM,SAAS,uBAAuB,OAAO,UAAU;CACvD,IAAI,CAAC,OAAO,SAAS,OAAO;CAE5B,OAAO,kBAAkB,OAAO,SAAS,OAAO,UAAU;AAC5D;AAEA,SAAS,kBACP,SACA,YACQ;CACR,MAAM,QAAkB,CAAC;CAEzB,IAAI,WAAW,UAAU,MAAM;EAC7B,IAAI,SAAS,OAAO,WAAW,MAAM,CAAC,CAAC,KAAK;EAC5C,IAAI,CAAC,SAAS,KAAK,MAAM,GAAG,SAAS,IAAI,OAAO;EAChD,MAAM,KAAK,WAAW,OAAO,EAAE;CACjC;CAEA,MAAM,WAAW,WAAW,YAAY;CACxC,MAAM,KAAK,aAAa,WAAW,SAAS,QAAQ,EAAE;CAEtD,IAAI,WAAW,gBAAgB,MAAM;EACnC,IAAI;EACJ,IAAI,OAAO,WAAW,iBAAiB,UACrC,kBAAkB,OAAO,WAAW,YAAY;OAEhD,kBAAkB,WAChB,WAAW,YACb,CAAC,CAAC;EAEJ,MAAM,KAAK,kBAAkB,gBAAgB,EAAE;CACjD;CAGA,OAAO,aAAa,QAAQ,KADP,MAAM,KAAK,GAAG,CAAC,CAAC,KACO,EAAE;AAChD;;;;;;;AC3CA,SAAS,gBAAgB,MAAmB,WAA2B;CACrE,IAAI,WAAW,KAAK;CAEpB,IAAI,KAAK,gBAAgB;EACvB,MAAM,gBAAgB,WAAW,SAAS,MAAM,KAAK,IAAI,CAAC,EAAE;EAC5D,MAAM,cAAc,IAAI,UAAU,GAAG;EAErC,WAAW,cACR,KAAK,SAAS;GACb,MAAM,gBAAgB,OAAO,GAAG,cAAc,SAAS;GAEvD,IAAI,KAAK,YACP,OAAO,GAAG,KAAK,WAAW,GAAG;GAE/B,OAAO;EACT,CAAC,CAAC,CACD,KAAK,IAAI;CACd;CAEA,OAAO;AACT;;;;;AAaA,SAAS,WAAW,OAAqC;CACvD,MAAM,2BAAW,IAAI,IAAyB;CAC9C,MAAM,QAAkB,CAAC;CAEzB,MAAM,SAAS,OAAmB,MAAM,GAAG,SAAS,GAAG,KAAK,GAAG,IAAI;CAEnE,KAAK,MAAM,KAAK,OAAO;EACrB,MAAM,MAAM,GAAG,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,IAAI,EAAE,gBAAgB,MAAM;EAC3E,MAAM,WAAW,SAAS,IAAI,GAAG;EACjC,IAAI,UACF,SAAS,eAAe,SAAS,eAC7B,GAAG,SAAS,aAAa,GAAG,EAAE,iBAC9B,EAAE;OACD;GACL,SAAS,IAAI,KAAK;IAChB,UAAU,EAAE;IACZ,SAAS,EAAE;IACX,eAAe,EAAE;IACjB,cAAc,EAAE;GAClB,CAAC;GACD,MAAM,KAAK,GAAG;EAChB;CACF;CAEA,OAAO,MAAM,KAAK,QAAQ,SAAS,IAAI,GAAG,CAAE;AAC9C;;;;;;;;;;AAWA,SAAgB,YAAY,OAAsB,WAA2B;CAC3E,IAAI,MAAM,WAAW,GAAG,OAAO;CAS/B,MAAM,UAAU,WAPM,MAAM,KAAK,UAAU;EACzC,UAAU,gBAAgB,MAAM,SAAS;EACzC,cAAc,KAAK;EACnB,SAAS,KAAK;EACd,eAAe,KAAK;CACtB,EAEuC,CAAC;CACxC,MAAM,WAAqB,CAAC;CAE5B,KAAK,MAAM,QAAQ,SAAS;EAC1B,MAAM,eAAe,KAAK,gBACtB,qBAAqB,KAAK,aAAa,MACvC,KAAK;EACT,MAAM,WAAW,GAAG,KAAK,SAAS,KAAK,aAAa;EAEpD,IAAI,WAAW;EACf,IAAI,KAAK,WAAW,KAAK,QAAQ,SAAS,GACxC,WAAW,KAAK,QAAQ,QACrB,KAAK,WAAW,GAAG,OAAO,KAAK,IAAI,KACpC,QACF;EAGF,SAAS,KAAK,QAAQ;CACxB;CAEA,OAAO,SAAS,KAAK,IAAI;AAC3B"}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { $ as SheetManager, $t as StyleParser, A as FLOW_STYLES, At as STYLE_TO_CHUNK, B as createStateParserContext, C as BASE_STYLES, Ct as APPEARANCE_CHUNK_STYLES, D as COLOR_STYLES, Dt as FONT_CHUNK_STYLES, E as BLOCK_STYLES, Et as DISPLAY_CHUNK_STYLES, Ft as registerFunctionPolyfill, G as StyleInjector, Gt as parseStyle, Ht as getGlobalPredefinedTokens, I as isSelector, Jt as okhstPlugin, Kt as stringifyStyles, L as renderStyles, Lt as CUSTOM_UNITS, M as OUTER_STYLES, Mt as formatFunctionRule, N as POSITION_STYLES, O as CONTAINER_STYLES, Ot as LAYOUT_CHUNK_STYLES, P as TEXT_STYLES, Pt as parseFunctionName, R as parseStateKey, Rt as DIRECTIONS, S as baseStylePropsRegistry, St as propHandlerRegistry, T as BLOCK_OUTER_STYLES, Tt as DIMENSION_CHUNK_STYLES, U as getGlobalPredefinedStates, Ut as normalizeColorTokenValue, Vt as getGlobalParser, W as setGlobalPredefinedStates, Wt as parseColor, X as fontFaceContentHash, Xt as okhslPlugin, Yt as okhslFunction, Z as formatFontFaceRule, Zt as createColorFunc, _ as isFunctionsPolyfillEnabled, _t as hashString, a as getGlobalCounterStyles, an as hslToRgbValues, at as closeBatchWindow, b as resetConfig, c as getGlobalInjector, ct as openBatchWindow, dt as DEFAULT_ZERO_NAME_PREFIX, f as getNamePrefix, g as isConfigLocked, h as hasStylesGenerated, in as hexToRgb, j as INNER_STYLES, k as DIMENSION_STYLES, kt as POSITION_CHUNK_STYLES, l as getGlobalKeyframes, lt as resetStyleBatch, m as hasGlobalRecipes, mt as makeKeyframeName, n as getConfig, nn as getNamedColorHex, nt as styleHandlers, o as getGlobalFontFaces, on as strToRgb, ot as flushStyles, p as hasGlobalKeyframes, pt as makeCounterStyleName, q as formatCounterStyleRule, qt as okhstFunction, rn as getRgbValuesFromRgbaString, s as getGlobalFunctions, st as hasPendingStyleWrites, t as configure, tt as defineHandler, u as getGlobalRecipes, ut as DEFAULT_NAME_PREFIX, v as isTestEnvironment, w as BLOCK_INNER_STYLES, wt as CHUNK_NAMES, x as generateTypographyTokens, zt as filterMods } from "./config-B3gPdCqd.js";
|
|
2
|
-
import { A as keyframes, C as getRawCSSText, D as injectRawCSS, E as injectGlobal, F as chunkSheetRegistry, I as resolveFunctionColor, M as property, N as touch, O as injector, P as ChunkSheetRegistry, S as getCSSTextForNode, T as inject, _ as destroy, a as color, b as gc, c as hasKeys, d as collectAutoInferredPropertiesRSC, f as getStyleTarget, g as createInjector, h as counterStyle, i as _modAttrs, j as ownKeyframes, k as isPropertyDefined, l as formatKeyframesCSS, m as cleanup, n as processTokens, o as filterBaseProps, p as pushRSCCSS, r as dotize, s as computeStyles, t as tastyDebug, u as collectAutoInferredProperties, v as fontFace, w as holdKeyframes, x as getCSSText, y as func } from "./core-
|
|
2
|
+
import { A as keyframes, C as getRawCSSText, D as injectRawCSS, E as injectGlobal, F as chunkSheetRegistry, I as resolveFunctionColor, M as property, N as touch, O as injector, P as ChunkSheetRegistry, S as getCSSTextForNode, T as inject, _ as destroy, a as color, b as gc, c as hasKeys, d as collectAutoInferredPropertiesRSC, f as getStyleTarget, g as createInjector, h as counterStyle, i as _modAttrs, j as ownKeyframes, k as isPropertyDefined, l as formatKeyframesCSS, m as cleanup, n as processTokens, o as filterBaseProps, p as pushRSCCSS, r as dotize, s as computeStyles, t as tastyDebug, u as collectAutoInferredProperties, v as fontFace, w as holdKeyframes, x as getCSSText, y as func } from "./core-DGm0CFHP.js";
|
|
3
3
|
import { d as categorizeStyleKeys } from "./keyframes-DE-OE76F.js";
|
|
4
|
-
import { n as formatPropertyCSS } from "./format-rules-
|
|
4
|
+
import { n as formatPropertyCSS } from "./format-rules-XRw9u7d4.js";
|
|
5
5
|
import { t as mergeStyles } from "./merge-styles-DuoZEsm9.js";
|
|
6
6
|
import { t as resolveRecipes } from "./resolve-recipes-H9NqOQuP.js";
|
|
7
7
|
import { t as getTastySSRContext } from "./context-CA8YKeMn.js";
|
package/dist/ssr/astro.d.ts
CHANGED
|
@@ -47,7 +47,13 @@ declare function tastyMiddleware(options?: TastyMiddlewareOptions): (context: {
|
|
|
47
47
|
isPrerendered?: boolean;
|
|
48
48
|
}, next: () => Promise<Response>) => Promise<Response>;
|
|
49
49
|
interface TastyIntegrationCSSOptions {
|
|
50
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* CSS delivery mode. Extraction only applies to prerendered builds.
|
|
52
|
+
* Extracted CSS preserves resource URLs verbatim, so use absolute URLs or
|
|
53
|
+
* data URLs. Root-relative URLs are also supported unless `assetsPrefix`
|
|
54
|
+
* sends CSS to an external origin. The build rejects resource URLs whose
|
|
55
|
+
* targets would change after extraction.
|
|
56
|
+
*/
|
|
51
57
|
mode?: 'inline' | 'extract';
|
|
52
58
|
}
|
|
53
59
|
interface TastyIntegrationOptions {
|
|
@@ -110,8 +116,10 @@ declare function tastyIntegration(options?: TastyIntegrationOptions): {
|
|
|
110
116
|
}: {
|
|
111
117
|
config: {
|
|
112
118
|
base?: string;
|
|
119
|
+
site?: URL;
|
|
113
120
|
build?: {
|
|
114
121
|
assets?: string;
|
|
122
|
+
assetsPrefix?: string | Record<string, string>;
|
|
115
123
|
};
|
|
116
124
|
};
|
|
117
125
|
}) => void;
|
package/dist/ssr/astro.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as tastyMiddleware, t as tastyIntegration } from "../astro-
|
|
1
|
+
import { n as tastyMiddleware, t as tastyIntegration } from "../astro-CeYENy2x.js";
|
|
2
2
|
export { tastyIntegration, tastyMiddleware };
|
package/dist/ssr/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as createServerStyleCollector, t as ServerStyleCollector } from "../collector-
|
|
1
|
+
import { r as registerSSRCollectorGetterGlobal } from "../ssr-collector-ref-COs_ioWl.js";
|
|
2
|
+
import { n as createServerStyleCollector, t as ServerStyleCollector } from "../collector-B3OsM252.js";
|
|
3
3
|
import { n as runWithCollector, t as getSSRCollector } from "../async-storage-DKK-wTD4.js";
|
|
4
4
|
import { t as hydrateTastyClasses } from "../hydrate-CNOmZprz.js";
|
|
5
5
|
//#region src/ssr/index.ts
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { t as TastyConfig } from "../config-LfIDmVHx.js";
|
|
2
|
+
|
|
3
|
+
//#region src/ssr/next-config.d.ts
|
|
4
|
+
interface NextConfig {
|
|
5
|
+
basePath?: string;
|
|
6
|
+
env?: Record<string, string | undefined>;
|
|
7
|
+
headers?: () => Promise<NextHeader[]>;
|
|
8
|
+
output?: string;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
interface NextHeader {
|
|
12
|
+
source: string;
|
|
13
|
+
headers: {
|
|
14
|
+
key: string;
|
|
15
|
+
value: string;
|
|
16
|
+
}[];
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
}
|
|
19
|
+
interface TastyNextOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Next.js project directory. Useful when the build command runs from a
|
|
22
|
+
* monorepo root rather than the app directory.
|
|
23
|
+
*
|
|
24
|
+
* @default process.cwd()
|
|
25
|
+
*/
|
|
26
|
+
rootDir?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Tasty configuration used to generate the shared stylesheet.
|
|
29
|
+
* `config` takes precedence when both config sources are provided.
|
|
30
|
+
*/
|
|
31
|
+
config?: TastyConfig;
|
|
32
|
+
/**
|
|
33
|
+
* Project-relative path to a TypeScript/JavaScript module whose default
|
|
34
|
+
* export is a Tasty configuration object.
|
|
35
|
+
*
|
|
36
|
+
* @example './app/tasty.config.ts'
|
|
37
|
+
*/
|
|
38
|
+
configFile?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Filesystem directory for generated stylesheets. When it is inside the
|
|
41
|
+
* project's `public` directory, its public URL is inferred automatically.
|
|
42
|
+
*
|
|
43
|
+
* @default 'public/_tasty'
|
|
44
|
+
*/
|
|
45
|
+
outputDir?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Root-relative URL path corresponding to `outputDir`. This is required
|
|
48
|
+
* when `outputDir` is outside the project's `public` directory.
|
|
49
|
+
* The Next.js `basePath` is prepended automatically.
|
|
50
|
+
*
|
|
51
|
+
* @default inferred from outputDir
|
|
52
|
+
* @example '/assets/tasty'
|
|
53
|
+
*/
|
|
54
|
+
publicPath?: string;
|
|
55
|
+
/** Whether to generate and register the shared stylesheet. @default true */
|
|
56
|
+
enabled?: boolean;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Add a cacheable, content-hashed stylesheet for Tasty configuration globals.
|
|
60
|
+
* Route-dependent component and hook styles continue to stream through
|
|
61
|
+
* `TastyRegistry` so App Router navigation and Suspense remain correct.
|
|
62
|
+
*/
|
|
63
|
+
declare function withTastyNext(options: TastyNextOptions): (nextConfig?: NextConfig) => NextConfig;
|
|
64
|
+
//#endregion
|
|
65
|
+
export { TastyNextOptions, withTastyNext };
|
|
66
|
+
//# sourceMappingURL=next-config.d.ts.map
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { b as resetConfig, t as configure } from "../config-B3gPdCqd.js";
|
|
2
|
+
import { t as ServerStyleCollector } from "../collector-B3OsM252.js";
|
|
3
|
+
import { t as findUnsafeCSSResource } from "../css-resources-Cyl_axbI.js";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { createRequire } from "node:module";
|
|
7
|
+
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
8
|
+
//#region src/ssr/next-config.ts
|
|
9
|
+
/**
|
|
10
|
+
* Next.js configuration wrapper for extracting Tasty's configured global CSS.
|
|
11
|
+
*
|
|
12
|
+
* Import this Node-only module from `next.config.ts`. The client-safe registry
|
|
13
|
+
* remains available from `@tenphi/tasty/ssr/next`.
|
|
14
|
+
*/
|
|
15
|
+
const ENV_KEY = "TASTY_NEXT_SHARED_CSS_HREF";
|
|
16
|
+
const packageRequire = createRequire(import.meta.url);
|
|
17
|
+
function loadConfig(projectDir, options) {
|
|
18
|
+
if (options.config) return options.config;
|
|
19
|
+
if (!options.configFile) throw new Error("[Tasty] withTastyNext() requires either `config` or `configFile`.");
|
|
20
|
+
const configPath = resolve(projectDir, options.configFile);
|
|
21
|
+
let jitiPath;
|
|
22
|
+
try {
|
|
23
|
+
jitiPath = packageRequire.resolve("jiti");
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if (error instanceof Error && "code" in error && error.code === "MODULE_NOT_FOUND") throw new Error("[Tasty] `configFile` requires the optional `jiti` package. Install `jiti` or pass the imported config through `config` instead.", { cause: error });
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
const { createJiti } = packageRequire(jitiPath);
|
|
29
|
+
const loaded = createJiti(projectDir, { moduleCache: false })(configPath);
|
|
30
|
+
return loaded && typeof loaded === "object" && "default" in loaded ? loaded.default : loaded;
|
|
31
|
+
}
|
|
32
|
+
function collectSharedCSS(config) {
|
|
33
|
+
resetConfig();
|
|
34
|
+
try {
|
|
35
|
+
configure(config);
|
|
36
|
+
const collector = new ServerStyleCollector();
|
|
37
|
+
collector.collectInternals();
|
|
38
|
+
const artifacts = collector.getArtifacts();
|
|
39
|
+
for (const artifact of artifacts) {
|
|
40
|
+
const unsafe = findUnsafeCSSResource(artifact.css, false);
|
|
41
|
+
if (unsafe) throw new Error(`[Tasty] Next.js shared CSS extraction cannot preserve page-relative CSS URL "${unsafe.url}" in ${artifact.kind} artifact ${artifact.id}. Use an absolute URL, a data URL, or a root-relative URL such as url(/path/to/asset).`);
|
|
42
|
+
}
|
|
43
|
+
return artifacts.map(({ css }) => css).join("\n");
|
|
44
|
+
} finally {
|
|
45
|
+
resetConfig();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function inferPublicPath(projectDir, outputDir) {
|
|
49
|
+
const relativePath = relative(resolve(projectDir, "public"), outputDir);
|
|
50
|
+
if (relativePath === ".." || relativePath.startsWith(`..${sep}`) || isAbsolute(relativePath)) throw new Error("[Tasty] `publicPath` is required when `outputDir` is outside the Next.js public directory.");
|
|
51
|
+
return `/${relativePath.split(sep).filter(Boolean).join("/")}`;
|
|
52
|
+
}
|
|
53
|
+
function normalizeURLPath(path, option) {
|
|
54
|
+
if (!path.startsWith("/") || path.startsWith("//") || path.includes("?") || path.includes("#") || path.includes("\\")) throw new Error(`[Tasty] \`${option}\` must be a root-relative URL path.`);
|
|
55
|
+
let end = path.length;
|
|
56
|
+
while (end > 0 && path[end - 1] === "/") end--;
|
|
57
|
+
return path.slice(0, end);
|
|
58
|
+
}
|
|
59
|
+
function writeSharedCSS(outputDir, css) {
|
|
60
|
+
mkdirSync(outputDir, { recursive: true });
|
|
61
|
+
const filename = `tasty.shared.${createHash("sha256").update(css).digest("hex").slice(0, 12)}.css`;
|
|
62
|
+
const outputPath = join(outputDir, filename);
|
|
63
|
+
let existing;
|
|
64
|
+
try {
|
|
65
|
+
existing = readFileSync(outputPath, "utf8");
|
|
66
|
+
} catch {}
|
|
67
|
+
if (existing !== css) writeFileSync(outputPath, css);
|
|
68
|
+
return filename;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Add a cacheable, content-hashed stylesheet for Tasty configuration globals.
|
|
72
|
+
* Route-dependent component and hook styles continue to stream through
|
|
73
|
+
* `TastyRegistry` so App Router navigation and Suspense remain correct.
|
|
74
|
+
*/
|
|
75
|
+
function withTastyNext(options) {
|
|
76
|
+
return (nextConfig = {}) => {
|
|
77
|
+
if (options.enabled === false) return nextConfig;
|
|
78
|
+
const projectDir = resolve(process.cwd(), options.rootDir ?? ".");
|
|
79
|
+
const outputDir = resolve(projectDir, options.outputDir ?? "public/_tasty");
|
|
80
|
+
const publicPath = normalizeURLPath(options.publicPath ?? inferPublicPath(projectDir, outputDir), "publicPath");
|
|
81
|
+
const basePath = normalizeURLPath(nextConfig.basePath || "/", "basePath");
|
|
82
|
+
const css = collectSharedCSS(loadConfig(projectDir, options));
|
|
83
|
+
if (!css) return nextConfig;
|
|
84
|
+
const filename = writeSharedCSS(outputDir, css);
|
|
85
|
+
const href = `${basePath}${publicPath}/${filename}`;
|
|
86
|
+
const headerSource = `${publicPath}/${filename}`;
|
|
87
|
+
const existingEnv = nextConfig.env ?? {};
|
|
88
|
+
const existingHeaders = nextConfig.headers;
|
|
89
|
+
if (existingEnv[ENV_KEY] && existingEnv[ENV_KEY] !== href) throw new Error(`[Tasty] Next.js env key ${ENV_KEY} is reserved by withTastyNext().`);
|
|
90
|
+
const generatedConfig = {
|
|
91
|
+
...nextConfig,
|
|
92
|
+
env: {
|
|
93
|
+
...existingEnv,
|
|
94
|
+
[ENV_KEY]: href
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
if (nextConfig.output === "export") return generatedConfig;
|
|
98
|
+
return {
|
|
99
|
+
...generatedConfig,
|
|
100
|
+
async headers() {
|
|
101
|
+
return [...existingHeaders ? await existingHeaders() : [], {
|
|
102
|
+
source: headerSource,
|
|
103
|
+
headers: [{
|
|
104
|
+
key: "Cache-Control",
|
|
105
|
+
value: "public, max-age=31536000, immutable"
|
|
106
|
+
}]
|
|
107
|
+
}];
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
export { withTastyNext };
|
|
114
|
+
|
|
115
|
+
//# sourceMappingURL=next-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"next-config.js","names":[],"sources":["../../src/ssr/next-config.ts"],"sourcesContent":["/**\n * Next.js configuration wrapper for extracting Tasty's configured global CSS.\n *\n * Import this Node-only module from `next.config.ts`. The client-safe registry\n * remains available from `@tenphi/tasty/ssr/next`.\n */\n\nimport { createHash } from 'node:crypto';\nimport { mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { isAbsolute, join, relative, resolve, sep } from 'node:path';\nimport type { createJiti as createJitiType } from 'jiti';\n\nimport { configure, resetConfig, type TastyConfig } from '../config';\nimport { ServerStyleCollector } from './collector';\nimport { findUnsafeCSSResource } from './css-resources';\n\nconst ENV_KEY = 'TASTY_NEXT_SHARED_CSS_HREF';\nconst packageRequire = createRequire(import.meta.url);\n\ninterface NextConfig {\n basePath?: string;\n env?: Record<string, string | undefined>;\n headers?: () => Promise<NextHeader[]>;\n output?: string;\n [key: string]: unknown;\n}\n\ninterface NextHeader {\n source: string;\n headers: { key: string; value: string }[];\n [key: string]: unknown;\n}\n\nexport interface TastyNextOptions {\n /**\n * Next.js project directory. Useful when the build command runs from a\n * monorepo root rather than the app directory.\n *\n * @default process.cwd()\n */\n rootDir?: string;\n\n /**\n * Tasty configuration used to generate the shared stylesheet.\n * `config` takes precedence when both config sources are provided.\n */\n config?: TastyConfig;\n\n /**\n * Project-relative path to a TypeScript/JavaScript module whose default\n * export is a Tasty configuration object.\n *\n * @example './app/tasty.config.ts'\n */\n configFile?: string;\n\n /**\n * Filesystem directory for generated stylesheets. When it is inside the\n * project's `public` directory, its public URL is inferred automatically.\n *\n * @default 'public/_tasty'\n */\n outputDir?: string;\n\n /**\n * Root-relative URL path corresponding to `outputDir`. This is required\n * when `outputDir` is outside the project's `public` directory.\n * The Next.js `basePath` is prepended automatically.\n *\n * @default inferred from outputDir\n * @example '/assets/tasty'\n */\n publicPath?: string;\n\n /** Whether to generate and register the shared stylesheet. @default true */\n enabled?: boolean;\n}\n\nfunction loadConfig(\n projectDir: string,\n options: TastyNextOptions,\n): TastyConfig {\n if (options.config) return options.config;\n if (!options.configFile) {\n throw new Error(\n '[Tasty] withTastyNext() requires either `config` or `configFile`.',\n );\n }\n\n const configPath = resolve(projectDir, options.configFile);\n let jitiPath: string;\n try {\n jitiPath = packageRequire.resolve('jiti');\n } catch (error) {\n if (\n error instanceof Error &&\n 'code' in error &&\n error.code === 'MODULE_NOT_FOUND'\n ) {\n throw new Error(\n '[Tasty] `configFile` requires the optional `jiti` package. Install `jiti` or pass the imported config through `config` instead.',\n { cause: error },\n );\n }\n throw error;\n }\n const { createJiti } = packageRequire(jitiPath) as {\n createJiti: typeof createJitiType;\n };\n const jiti = createJiti(projectDir, { moduleCache: false });\n const loaded = jiti(configPath) as TastyConfig | { default: TastyConfig };\n\n return loaded && typeof loaded === 'object' && 'default' in loaded\n ? loaded.default\n : loaded;\n}\n\nfunction collectSharedCSS(config: TastyConfig): string {\n resetConfig();\n try {\n configure(config);\n const collector = new ServerStyleCollector();\n collector.collectInternals();\n const artifacts = collector.getArtifacts();\n\n for (const artifact of artifacts) {\n const unsafe = findUnsafeCSSResource(artifact.css, false);\n if (unsafe) {\n throw new Error(\n `[Tasty] Next.js shared CSS extraction cannot preserve page-relative CSS URL \"${unsafe.url}\" in ${artifact.kind} artifact ${artifact.id}. Use an absolute URL, a data URL, or a root-relative URL such as url(/path/to/asset).`,\n );\n }\n }\n\n return artifacts.map(({ css }) => css).join('\\n');\n } finally {\n resetConfig();\n }\n}\n\nfunction inferPublicPath(projectDir: string, outputDir: string): string {\n const publicDir = resolve(projectDir, 'public');\n const relativePath = relative(publicDir, outputDir);\n if (\n relativePath === '..' ||\n relativePath.startsWith(`..${sep}`) ||\n isAbsolute(relativePath)\n ) {\n throw new Error(\n '[Tasty] `publicPath` is required when `outputDir` is outside the Next.js public directory.',\n );\n }\n\n return `/${relativePath.split(sep).filter(Boolean).join('/')}`;\n}\n\nfunction normalizeURLPath(path: string, option: string): string {\n if (\n !path.startsWith('/') ||\n path.startsWith('//') ||\n path.includes('?') ||\n path.includes('#') ||\n path.includes('\\\\')\n ) {\n throw new Error(`[Tasty] \\`${option}\\` must be a root-relative URL path.`);\n }\n\n let end = path.length;\n while (end > 0 && path[end - 1] === '/') end--;\n return path.slice(0, end);\n}\n\nfunction writeSharedCSS(outputDir: string, css: string): string {\n mkdirSync(outputDir, { recursive: true });\n const hash = createHash('sha256').update(css).digest('hex').slice(0, 12);\n const filename = `tasty.shared.${hash}.css`;\n\n const outputPath = join(outputDir, filename);\n let existing: string | undefined;\n try {\n existing = readFileSync(outputPath, 'utf8');\n } catch {\n // The file does not exist yet (or is unreadable); write it below.\n }\n if (existing !== css) writeFileSync(outputPath, css);\n\n return filename;\n}\n\n/**\n * Add a cacheable, content-hashed stylesheet for Tasty configuration globals.\n * Route-dependent component and hook styles continue to stream through\n * `TastyRegistry` so App Router navigation and Suspense remain correct.\n */\nexport function withTastyNext(options: TastyNextOptions) {\n return (nextConfig: NextConfig = {}): NextConfig => {\n if (options.enabled === false) return nextConfig;\n\n const projectDir = resolve(process.cwd(), options.rootDir ?? '.');\n const outputDir = resolve(projectDir, options.outputDir ?? 'public/_tasty');\n const publicPath = normalizeURLPath(\n options.publicPath ?? inferPublicPath(projectDir, outputDir),\n 'publicPath',\n );\n const basePath = normalizeURLPath(nextConfig.basePath || '/', 'basePath');\n const css = collectSharedCSS(loadConfig(projectDir, options));\n\n if (!css) return nextConfig;\n\n const filename = writeSharedCSS(outputDir, css);\n const href = `${basePath}${publicPath}/${filename}`;\n const headerSource = `${publicPath}/${filename}`;\n const existingEnv = nextConfig.env ?? {};\n const existingHeaders = nextConfig.headers;\n if (existingEnv[ENV_KEY] && existingEnv[ENV_KEY] !== href) {\n throw new Error(\n `[Tasty] Next.js env key ${ENV_KEY} is reserved by withTastyNext().`,\n );\n }\n\n const generatedConfig: NextConfig = {\n ...nextConfig,\n env: {\n ...existingEnv,\n [ENV_KEY]: href,\n },\n };\n\n // `headers()` is not emitted by Next's static export. Avoid introducing an\n // unsupported-config warning; the content hash still makes the asset safe\n // for the static host to cache immutably.\n if (nextConfig.output === 'export') return generatedConfig;\n\n return {\n ...generatedConfig,\n async headers() {\n const headers = existingHeaders ? await existingHeaders() : [];\n return [\n ...headers,\n {\n source: headerSource,\n headers: [\n {\n key: 'Cache-Control',\n value: 'public, max-age=31536000, immutable',\n },\n ],\n },\n ];\n },\n };\n };\n}\n"],"mappings":"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"}
|
package/dist/ssr/next.d.ts
CHANGED
|
@@ -11,6 +11,12 @@ interface TastyRegistryProps {
|
|
|
11
11
|
* in server-rendered `<style>` tags. Default: true.
|
|
12
12
|
*/
|
|
13
13
|
transferCache?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* URL of a stylesheet containing Tasty's configured global CSS.
|
|
16
|
+
* `withTastyNext()` sets this automatically. Pass false to disable it for
|
|
17
|
+
* this registry, or a URL to use a manually generated stylesheet.
|
|
18
|
+
*/
|
|
19
|
+
sharedStylesheet?: string | false;
|
|
14
20
|
}
|
|
15
21
|
/**
|
|
16
22
|
* Next.js App Router registry for Tasty SSR.
|
|
@@ -38,7 +44,8 @@ interface TastyRegistryProps {
|
|
|
38
44
|
*/
|
|
39
45
|
declare function TastyRegistry({
|
|
40
46
|
children,
|
|
41
|
-
transferCache
|
|
47
|
+
transferCache,
|
|
48
|
+
sharedStylesheet
|
|
42
49
|
}: TastyRegistryProps): import("react").FunctionComponentElement<import("react").ProviderProps<ServerStyleCollector | null>>;
|
|
43
50
|
//#endregion
|
|
44
51
|
export { TastyRegistry, TastyRegistryProps };
|
package/dist/ssr/next.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { n as getConfig } from "../config-B3gPdCqd.js";
|
|
3
|
-
import {
|
|
3
|
+
import { n as registerSSRCollectorGetter } from "../ssr-collector-ref-COs_ioWl.js";
|
|
4
4
|
import { t as getTastySSRContext } from "../context-CA8YKeMn.js";
|
|
5
|
-
import { t as ServerStyleCollector } from "../collector-
|
|
5
|
+
import { t as ServerStyleCollector } from "../collector-B3OsM252.js";
|
|
6
6
|
import { t as hydrateTastyClasses } from "../hydrate-CNOmZprz.js";
|
|
7
7
|
import { Fragment, createElement, useState } from "react";
|
|
8
8
|
import { useServerInsertedHTML } from "next/navigation";
|
|
@@ -39,32 +39,49 @@ if (typeof window !== "undefined") hydrateTastyClasses();
|
|
|
39
39
|
* }
|
|
40
40
|
* ```
|
|
41
41
|
*/
|
|
42
|
-
function TastyRegistry({ children, transferCache = true }) {
|
|
42
|
+
function TastyRegistry({ children, transferCache = true, sharedStylesheet = process.env.TASTY_NEXT_SHARED_CSS_HREF }) {
|
|
43
43
|
const isClient = typeof window !== "undefined";
|
|
44
44
|
const [collector] = useState(() => {
|
|
45
45
|
if (isClient) return null;
|
|
46
46
|
const instance = new ServerStyleCollector();
|
|
47
47
|
registerSSRCollectorGetter(() => instance);
|
|
48
|
+
if (sharedStylesheet) {
|
|
49
|
+
instance.collectInternals();
|
|
50
|
+
instance.flushCSS();
|
|
51
|
+
}
|
|
48
52
|
return instance;
|
|
49
53
|
});
|
|
54
|
+
const [streamState] = useState(() => ({ sharedStylesheetFlushed: false }));
|
|
50
55
|
const nonce = getConfig().nonce;
|
|
51
56
|
useServerInsertedHTML(() => {
|
|
52
57
|
if (!collector) return null;
|
|
53
58
|
const css = collector.flushCSS();
|
|
54
59
|
const classNames = collector.getRenderedClassNames();
|
|
55
|
-
|
|
60
|
+
let linkEl = null;
|
|
61
|
+
if (sharedStylesheet && !streamState.sharedStylesheetFlushed) {
|
|
62
|
+
streamState.sharedStylesheetFlushed = true;
|
|
63
|
+
linkEl = createElement("link", {
|
|
64
|
+
key: "tasty-shared-styles",
|
|
65
|
+
rel: "stylesheet",
|
|
66
|
+
href: sharedStylesheet,
|
|
67
|
+
"data-tasty-ssr": "",
|
|
68
|
+
nonce
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
if (!css) return linkEl;
|
|
56
72
|
const styleEl = createElement("style", {
|
|
57
73
|
key: "tasty-ssr-styles",
|
|
58
74
|
"data-tasty-ssr": "",
|
|
59
75
|
nonce,
|
|
60
76
|
dangerouslySetInnerHTML: { __html: css }
|
|
61
77
|
});
|
|
62
|
-
if (!transferCache || classNames.length === 0) return styleEl;
|
|
63
|
-
|
|
78
|
+
if (!transferCache || classNames.length === 0) return linkEl ? createElement(Fragment, null, linkEl, styleEl) : styleEl;
|
|
79
|
+
const scriptEl = createElement("script", {
|
|
64
80
|
key: "tasty-ssr-cache",
|
|
65
81
|
nonce,
|
|
66
82
|
dangerouslySetInnerHTML: { __html: `(window.__TASTY__=window.__TASTY__||[]).push(${classNames.map((n) => `"${n}"`).join(",")})` }
|
|
67
|
-
})
|
|
83
|
+
});
|
|
84
|
+
return createElement(Fragment, null, linkEl, styleEl, scriptEl);
|
|
68
85
|
});
|
|
69
86
|
return createElement(getTastySSRContext().Provider, { value: collector }, children);
|
|
70
87
|
}
|
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 *\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 { getTastySSRContext } 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
|
|
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 *\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 { getTastySSRContext } 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 * URL of a stylesheet containing Tasty's configured global CSS.\n * `withTastyNext()` sets this automatically. Pass false to disable it for\n * this registry, or a URL to use a manually generated stylesheet.\n */\n sharedStylesheet?: string | false;\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 sharedStylesheet = process.env.TASTY_NEXT_SHARED_CSS_HREF,\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 // The generated stylesheet already contains configured global artifacts.\n // Mark that first batch as flushed so only request-specific styles stream.\n if (sharedStylesheet) {\n instance.collectInternals();\n instance.flushCSS();\n }\n\n return instance;\n });\n const [streamState] = useState(() => ({ sharedStylesheetFlushed: false }));\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 let linkEl = null;\n if (sharedStylesheet && !streamState.sharedStylesheetFlushed) {\n streamState.sharedStylesheetFlushed = true;\n linkEl = createElement('link', {\n key: 'tasty-shared-styles',\n rel: 'stylesheet',\n href: sharedStylesheet,\n 'data-tasty-ssr': '',\n nonce,\n });\n }\n\n if (!css) return linkEl;\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) {\n return linkEl ? createElement(Fragment, null, linkEl, styleEl) : styleEl;\n }\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, linkEl, styleEl, scriptEl);\n });\n\n return createElement(\n getTastySSRContext().Provider,\n { value: collector },\n children,\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAuBA,IAAI,OAAO,WAAW,aACpB,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;AA4CtB,SAAgB,cAAc,EAC5B,UACA,gBAAgB,MAChB,mBAAmB,QAAQ,IAAI,8BACV;CACrB,MAAM,WAAW,OAAO,WAAW;CAEnC,MAAM,CAAC,aAAa,eAAe;EACjC,IAAI,UAAU,OAAO;EAErB,MAAM,WAAW,IAAI,qBAAqB;EAE1C,iCAAiC,QAAQ;EAIzC,IAAI,kBAAkB;GACpB,SAAS,iBAAiB;GAC1B,SAAS,SAAS;EACpB;EAEA,OAAO;CACT,CAAC;CACD,MAAM,CAAC,eAAe,gBAAgB,EAAE,yBAAyB,MAAM,EAAE;CACzE,MAAM,QAAQ,UAAU,CAAC,CAAC;CAE1B,4BAA4B;EAC1B,IAAI,CAAC,WAAW,OAAO;EAEvB,MAAM,MAAM,UAAU,SAAS;EAC/B,MAAM,aAAa,UAAU,sBAAsB;EAEnD,IAAI,SAAS;EACb,IAAI,oBAAoB,CAAC,YAAY,yBAAyB;GAC5D,YAAY,0BAA0B;GACtC,SAAS,cAAc,QAAQ;IAC7B,KAAK;IACL,KAAK;IACL,MAAM;IACN,kBAAkB;IAClB;GACF,CAAC;EACH;EAEA,IAAI,CAAC,KAAK,OAAO;EAEjB,MAAM,UAAU,cAAc,SAAS;GACrC,KAAK;GACL,kBAAkB;GAClB;GACA,yBAAyB,EAAE,QAAQ,IAAI;EACzC,CAAC;EAED,IAAI,CAAC,iBAAiB,WAAW,WAAW,GAC1C,OAAO,SAAS,cAAc,UAAU,MAAM,QAAQ,OAAO,IAAI;EAKnE,MAAM,WAAW,cAAc,UAAU;GACvC,KAAK;GACL;GACA,yBAAyB,EACvB,QAAQ,gDANU,WAAW,KAAK,MAAM,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,GAMa,EAAE,GACxE;EACF,CAAC;EAED,OAAO,cAAc,UAAU,MAAM,QAAQ,SAAS,QAAQ;CAChE,CAAC;CAED,OAAO,cACL,mBAAmB,CAAC,CAAC,UACrB,EAAE,OAAO,UAAU,GACnB,QACF;AACF"}
|