@tenphi/tasty 0.15.3 → 0.16.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/chunks/definitions.js +1 -1
- package/dist/chunks/definitions.js.map +1 -1
- package/dist/config.d.ts +45 -2
- package/dist/config.js +59 -22
- package/dist/config.js.map +1 -1
- package/dist/core/index.d.ts +4 -4
- package/dist/core/index.js +3 -3
- package/dist/counter-style/index.js +51 -0
- package/dist/counter-style/index.js.map +1 -0
- package/dist/font-face/index.js +63 -0
- package/dist/font-face/index.js.map +1 -0
- package/dist/hooks/resolve-ssr-collector.js +15 -0
- package/dist/hooks/resolve-ssr-collector.js.map +1 -0
- package/dist/hooks/useCounterStyle.d.ts +50 -0
- package/dist/hooks/useCounterStyle.js +47 -0
- package/dist/hooks/useCounterStyle.js.map +1 -0
- package/dist/hooks/useFontFace.d.ts +43 -0
- package/dist/hooks/useFontFace.js +71 -0
- package/dist/hooks/useFontFace.js.map +1 -0
- package/dist/hooks/useGlobalStyles.js +1 -5
- package/dist/hooks/useGlobalStyles.js.map +1 -1
- package/dist/hooks/useKeyframes.js +1 -5
- package/dist/hooks/useKeyframes.js.map +1 -1
- package/dist/hooks/useProperty.js +1 -5
- package/dist/hooks/useProperty.js.map +1 -1
- package/dist/hooks/useRawCSS.js +1 -5
- package/dist/hooks/useRawCSS.js.map +1 -1
- package/dist/hooks/useStyles.js +33 -12
- package/dist/hooks/useStyles.js.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.js +5 -3
- package/dist/injector/index.d.ts +20 -2
- package/dist/injector/index.js +19 -1
- package/dist/injector/index.js.map +1 -1
- package/dist/injector/injector.d.ts +19 -1
- package/dist/injector/injector.js +35 -0
- package/dist/injector/injector.js.map +1 -1
- package/dist/injector/sheet-manager.js +2 -0
- package/dist/injector/sheet-manager.js.map +1 -1
- package/dist/injector/types.d.ts +61 -1
- package/dist/ssr/collector.d.ts +17 -0
- package/dist/ssr/collector.js +50 -6
- package/dist/ssr/collector.js.map +1 -1
- package/dist/ssr/index.js +1 -1
- package/dist/states/index.js +2 -0
- package/dist/states/index.js.map +1 -1
- package/dist/styles/border.js +2 -2
- package/dist/styles/border.js.map +1 -1
- package/dist/styles/preset.js +23 -37
- package/dist/styles/preset.js.map +1 -1
- package/dist/styles/types.d.ts +25 -3
- package/dist/tasty.d.ts +1 -1
- package/dist/utils/styles.d.ts +0 -1
- package/dist/utils/styles.js +0 -1
- package/dist/utils/styles.js.map +1 -1
- package/dist/zero/babel.d.ts +11 -1
- package/dist/zero/babel.js +13 -7
- package/dist/zero/babel.js.map +1 -1
- package/dist/zero/extractor.js +53 -1
- package/dist/zero/extractor.js.map +1 -1
- package/docs/configuration.md +61 -0
- package/docs/design-system.md +17 -0
- package/docs/dsl.md +91 -1
- package/docs/runtime.md +88 -0
- package/docs/ssr.md +2 -0
- package/docs/tasty-static.md +2 -0
- package/package.json +2 -2
package/dist/utils/styles.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"styles.js","names":[],"sources":["../../src/utils/styles.ts"],"sourcesContent":["import { StyleParser } from '../parser/parser';\nimport { okhslFunc } from '../plugins/okhsl-plugin';\n\nimport type { ProcessedStyle, StyleDetails } from '../parser/types';\n\nimport { getNamedColorHex } from './color-math';\n\nexport {\n getNamedColorHex,\n getRgbValuesFromRgbaString,\n hexToRgb,\n strToRgb,\n} from './color-math';\n\nexport type StyleValue<T = string> = T | boolean | number | null | undefined;\n\n/**\n * Normalize a color token value.\n * - Boolean `true` is converted to `'transparent'`\n * - Boolean `false` returns `null` (signals the token should be skipped)\n * - Other values are returned as-is\n *\n * @param value - The raw token value\n * @returns Normalized value or null if the token should be skipped\n */\nexport function normalizeColorTokenValue<T>(\n value: T | boolean,\n): T | 'transparent' | null {\n if (value === true) {\n return 'transparent';\n }\n if (value === false) {\n return null;\n }\n return value as T;\n}\n\nexport type StyleValueStateMap<T = string> = Record<\n string,\n StyleValue<T> | '@inherit'\n>;\n\n/**\n * Combined type for style values that can be either a direct value or a state map.\n * Use this for component props that accept style values.\n */\nexport type StylePropValue<T = string> = StyleValue<T> | StyleValueStateMap<T>;\n\nexport type CSSMap = { $?: string | string[] } & Record<\n string,\n string | string[]\n>;\n\nexport type StyleHandlerResult = CSSMap | CSSMap[] | void;\n\nexport type RawStyleHandler = (value: StyleValueStateMap) => StyleHandlerResult;\n\nexport type StyleHandler = RawStyleHandler & {\n __lookupStyles: string[];\n};\n\n/**\n * Handler definition forms for configure() and plugins.\n * - Function only: lookup styles inferred from key name\n * - Single property tuple: ['styleName', handler]\n * - Multi-property tuple: [['style1', 'style2'], handler]\n */\nexport type StyleHandlerDefinition =\n | RawStyleHandler\n | [string, RawStyleHandler]\n | [string[], RawStyleHandler];\n\nexport interface ParsedColor {\n color?: string;\n name?: string;\n opacity?: number;\n}\n\nexport type StyleMap = Record<string, StyleValue | StyleValueStateMap>;\n\nconst devMode = process.env.NODE_ENV !== 'production';\n\n// Precompiled regex patterns for parseColor optimization\n// Match var(--name-color...) and extract the name, regardless of fallbacks\nconst COLOR_VAR_PATTERN = /var\\(--([a-z0-9-]+)-color/;\nconst COLOR_VAR_COMPONENTS_PATTERN =\n /var\\(--([a-z0-9-]+)-color-(?:rgb|hsl|oklch)/;\nconst RGB_ALPHA_PATTERN = /\\/\\s*([0-9.]+)\\)/;\nconst RE_HEX_COLOR = /^#[0-9a-fA-F]{3,8}$/;\nconst RE_VAR_COLOR = /^var\\(--[a-z0-9-]+-color/;\n\nfunction isSimpleColorFast(val: string): boolean {\n const c0 = val.charCodeAt(0);\n\n switch (c0) {\n case 35: // '#'\n return RE_HEX_COLOR.test(val);\n case 114: // 'r'\n return val.charCodeAt(1) === 103 && val.charCodeAt(2) === 98; // 'rgb'\n case 104: // 'h'\n return val.charCodeAt(1) === 115 && val.charCodeAt(2) === 108; // 'hsl'\n case 108: // 'l'\n return val.charCodeAt(1) === 99 && val.charCodeAt(2) === 104; // 'lch'\n case 111: // 'o'\n return val.startsWith('oklch(') || val.startsWith('okhsl(');\n case 118: // 'v'\n return RE_VAR_COLOR.test(val);\n case 99: // 'c'\n return val === 'currentColor' || val === 'currentcolor';\n case 116: // 't'\n return val === 'transparent';\n default:\n return getNamedColorHex().has(val.toLowerCase());\n }\n}\n\n// Rate limiting for dev warnings to avoid spam\nlet colorWarningCount = 0;\nconst MAX_COLOR_WARNINGS = 10;\n\nexport const CUSTOM_UNITS = {\n r: '6px',\n cr: '10px',\n bw: '1px',\n ow: '3px',\n x: '8px',\n fs: 'var(--font-size)',\n lh: 'var(--line-height)',\n sf: function sf(num: number) {\n return `minmax(0, ${num}fr)`;\n },\n};\n\nexport const DIRECTIONS = ['top', 'right', 'bottom', 'left'];\n\n// Lazy-initialized to break the circular dependency:\n// parser.ts → classify.ts → utils/styles.ts → parser.ts\nlet __tastyParser: StyleParser | null = null;\n\nfunction getOrCreateParser(): StyleParser {\n if (!__tastyParser) {\n __tastyParser = new StyleParser({ units: CUSTOM_UNITS });\n __tastyParser.setFuncs(__tastyFuncs);\n }\n return __tastyParser;\n}\n\n// Registry for user-provided custom functions that the parser can call.\n// It is updated through the `customFunc` helper exported below.\n// okhsl is registered as a built-in function so it works regardless of\n// tree-shaking or module initialization order.\nconst __tastyFuncs: Record<string, (groups: StyleDetails[]) => string> = {\n okhsl: okhslFunc,\n};\n\nexport function customFunc(\n name: string,\n fn: (groups: StyleDetails[]) => string,\n) {\n __tastyFuncs[name] = fn;\n getOrCreateParser().setFuncs(__tastyFuncs);\n}\n\n/**\n * Get the global StyleParser instance.\n * Used by configure() to apply parser configuration.\n */\nexport function getGlobalParser(): StyleParser {\n return getOrCreateParser();\n}\n\n/**\n * Get the current custom functions registry.\n * Used by configure() to merge with new functions.\n */\nexport function getGlobalFuncs(): Record<\n string,\n (groups: StyleDetails[]) => string\n> {\n return __tastyFuncs;\n}\n\n// ============================================================================\n// Global Predefined Tokens\n// ============================================================================\n\n/**\n * Storage for predefined tokens that are replaced during style parsing.\n * Keys are token names (with $ or # prefix), values are pre-processed CSS values.\n */\nlet __globalPredefinedTokens: Record<string, string> | null = null;\n\n/**\n * Set global predefined tokens.\n * Called from configure() after processing token values.\n * Merges with existing tokens (new tokens override existing ones with same key).\n * Keys are normalized to lowercase (parser lowercases input before classification).\n * @internal\n */\nexport function setGlobalPredefinedTokens(\n tokens: Record<string, string>,\n): void {\n // Normalize keys to lowercase for case-insensitive matching\n const normalizedTokens: Record<string, string> = {};\n for (const [key, value] of Object.entries(tokens)) {\n const lowerKey = key.toLowerCase();\n const lowerValue = value.toLowerCase();\n\n // Warn if trying to use bare #current to define other color tokens\n // #current represents currentcolor which cannot be used as a base for recursive token resolution\n // Note: #current.5 (with opacity) is allowed since it resolves to a concrete color-mix value\n if (lowerKey.startsWith('#') && lowerValue === '#current') {\n console.warn(\n `Tasty: Using #current to define color token \"${key}\" is not supported. ` +\n `The #current token represents currentcolor which cannot be used as a base for other tokens.`,\n );\n continue; // Skip this token\n }\n\n normalizedTokens[lowerKey] = value;\n }\n // Merge with existing tokens (consistent with how states, units, funcs are handled)\n __globalPredefinedTokens = __globalPredefinedTokens\n ? { ...__globalPredefinedTokens, ...normalizedTokens }\n : normalizedTokens;\n // Clear parser cache since token values affect parsing\n getOrCreateParser().clearCache();\n}\n\n/**\n * Get the current global predefined tokens.\n * Returns null if no tokens are configured.\n */\nexport function getGlobalPredefinedTokens(): Record<string, string> | null {\n return __globalPredefinedTokens;\n}\n\n/**\n * Reset global predefined tokens.\n * Used for testing.\n * @internal\n */\nexport function resetGlobalPredefinedTokens(): void {\n __globalPredefinedTokens = null;\n // Clear parser cache since token availability affects parsing\n getOrCreateParser().clearCache();\n}\n\n/**\n *\n * @param {String} value\n * @param {Number} mode\n * @returns {Object<String,String|Array>}\n */\nexport function parseStyle(value: StyleValue): ProcessedStyle {\n let str: string;\n\n if (typeof value === 'string') {\n str = value;\n } else if (typeof value === 'number') {\n str = String(value);\n } else {\n // boolean, null, undefined, objects etc. → empty string\n str = '';\n }\n\n return getOrCreateParser().process(str);\n}\n\n/**\n * Parse color. Find it value, name and opacity.\n * Optimized to avoid heavy parseStyle calls for simple color patterns.\n */\nexport function parseColor(val: string, ignoreError = false): ParsedColor {\n // Early return for non-strings or empty values\n if (typeof val !== 'string') {\n val = String(val ?? '');\n }\n\n val = val.trim();\n if (!val) return {};\n\n // Fast path: Check if it's a simple color pattern that doesn't need full parsing\n const isSimpleColor = isSimpleColorFast(val);\n\n let firstColor: string;\n if (isSimpleColor) {\n // For simple colors, use the value directly without parsing\n firstColor = val;\n } else {\n const processed = parseStyle(val);\n const extractedColor = processed.groups.find((g) => g.colors.length)\n ?.colors[0];\n\n if (!extractedColor) {\n // Rate-limited warning to avoid spam\n if (!ignoreError && devMode && colorWarningCount < MAX_COLOR_WARNINGS) {\n console.warn('Tasty: unable to parse color:', val);\n colorWarningCount++;\n if (colorWarningCount === MAX_COLOR_WARNINGS) {\n console.warn(\n 'Tasty: color parsing warnings will be suppressed from now on',\n );\n }\n }\n return {};\n }\n\n firstColor = extractedColor;\n }\n\n // Extract color name (if present) from variable pattern using precompiled regex\n let nameMatch = firstColor.match(COLOR_VAR_PATTERN);\n if (!nameMatch) {\n nameMatch = firstColor.match(COLOR_VAR_COMPONENTS_PATTERN);\n }\n\n let opacity: number | undefined;\n if (\n firstColor.startsWith('rgb') ||\n firstColor.startsWith('hsl') ||\n firstColor.startsWith('lch') ||\n firstColor.startsWith('oklch') ||\n firstColor.startsWith('okhsl')\n ) {\n const alphaMatch = firstColor.match(RGB_ALPHA_PATTERN);\n if (alphaMatch) {\n const v = parseFloat(alphaMatch[1]);\n if (!isNaN(v)) opacity = v * 100;\n }\n }\n\n return {\n color: firstColor,\n name: nameMatch ? nameMatch[1] : undefined,\n opacity,\n };\n}\n\nexport function filterMods(mods: string[], allowedMods: string[]): string[] {\n return mods.filter((mod) => allowedMods.includes(mod));\n}\n\n// ============================================================================\n// Style Stringification\n// ============================================================================\n\nconst _innerCache = new WeakMap();\nconst _topLevelCache = new WeakMap<object, string>();\n\nexport function stringifyStyles(styles: unknown): string {\n if (styles == null || typeof styles !== 'object') return '';\n\n const cached = _topLevelCache.get(styles as object);\n if (cached !== undefined) return cached;\n\n const obj = styles as Record<string, unknown>;\n const keys = Object.keys(obj).sort();\n const parts: string[] = [];\n for (const k of keys) {\n const v = obj[k];\n if (v === undefined || typeof v === 'function' || typeof v === 'symbol')\n continue;\n\n const c0 = k.charCodeAt(0);\n const needsInnerSort =\n ((c0 >= 65 && c0 <= 90) || c0 === 38) &&\n v &&\n typeof v === 'object' &&\n !Array.isArray(v);\n\n let sv: string;\n if (needsInnerSort) {\n sv = _innerCache.get(v);\n if (sv === undefined) {\n const innerObj = v as Record<string, unknown>;\n const innerKeys = Object.keys(innerObj).sort();\n const innerParts: string[] = [];\n for (const ik of innerKeys) {\n const ivs = JSON.stringify(innerObj[ik]);\n if (ivs !== undefined)\n innerParts.push(JSON.stringify(ik) + ':' + ivs);\n }\n sv = '{' + innerParts.join(',') + '}';\n _innerCache.set(v, sv);\n }\n } else {\n sv = JSON.stringify(v);\n }\n parts.push(JSON.stringify(k) + ':' + sv);\n }\n const result = '{' + parts.join(',') + '}';\n _topLevelCache.set(styles as object, result);\n return result;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"styles.js","names":[],"sources":["../../src/utils/styles.ts"],"sourcesContent":["import { StyleParser } from '../parser/parser';\nimport { okhslFunc } from '../plugins/okhsl-plugin';\n\nimport type { ProcessedStyle, StyleDetails } from '../parser/types';\n\nimport { getNamedColorHex } from './color-math';\n\nexport {\n getNamedColorHex,\n getRgbValuesFromRgbaString,\n hexToRgb,\n strToRgb,\n} from './color-math';\n\nexport type StyleValue<T = string> = T | boolean | number | null | undefined;\n\n/**\n * Normalize a color token value.\n * - Boolean `true` is converted to `'transparent'`\n * - Boolean `false` returns `null` (signals the token should be skipped)\n * - Other values are returned as-is\n *\n * @param value - The raw token value\n * @returns Normalized value or null if the token should be skipped\n */\nexport function normalizeColorTokenValue<T>(\n value: T | boolean,\n): T | 'transparent' | null {\n if (value === true) {\n return 'transparent';\n }\n if (value === false) {\n return null;\n }\n return value as T;\n}\n\nexport type StyleValueStateMap<T = string> = Record<\n string,\n StyleValue<T> | '@inherit'\n>;\n\n/**\n * Combined type for style values that can be either a direct value or a state map.\n * Use this for component props that accept style values.\n */\nexport type StylePropValue<T = string> = StyleValue<T> | StyleValueStateMap<T>;\n\nexport type CSSMap = { $?: string | string[] } & Record<\n string,\n string | string[]\n>;\n\nexport type StyleHandlerResult = CSSMap | CSSMap[] | void;\n\nexport type RawStyleHandler = (value: StyleValueStateMap) => StyleHandlerResult;\n\nexport type StyleHandler = RawStyleHandler & {\n __lookupStyles: string[];\n};\n\n/**\n * Handler definition forms for configure() and plugins.\n * - Function only: lookup styles inferred from key name\n * - Single property tuple: ['styleName', handler]\n * - Multi-property tuple: [['style1', 'style2'], handler]\n */\nexport type StyleHandlerDefinition =\n | RawStyleHandler\n | [string, RawStyleHandler]\n | [string[], RawStyleHandler];\n\nexport interface ParsedColor {\n color?: string;\n name?: string;\n opacity?: number;\n}\n\nexport type StyleMap = Record<string, StyleValue | StyleValueStateMap>;\n\nconst devMode = process.env.NODE_ENV !== 'production';\n\n// Precompiled regex patterns for parseColor optimization\n// Match var(--name-color...) and extract the name, regardless of fallbacks\nconst COLOR_VAR_PATTERN = /var\\(--([a-z0-9-]+)-color/;\nconst COLOR_VAR_COMPONENTS_PATTERN =\n /var\\(--([a-z0-9-]+)-color-(?:rgb|hsl|oklch)/;\nconst RGB_ALPHA_PATTERN = /\\/\\s*([0-9.]+)\\)/;\nconst RE_HEX_COLOR = /^#[0-9a-fA-F]{3,8}$/;\nconst RE_VAR_COLOR = /^var\\(--[a-z0-9-]+-color/;\n\nfunction isSimpleColorFast(val: string): boolean {\n const c0 = val.charCodeAt(0);\n\n switch (c0) {\n case 35: // '#'\n return RE_HEX_COLOR.test(val);\n case 114: // 'r'\n return val.charCodeAt(1) === 103 && val.charCodeAt(2) === 98; // 'rgb'\n case 104: // 'h'\n return val.charCodeAt(1) === 115 && val.charCodeAt(2) === 108; // 'hsl'\n case 108: // 'l'\n return val.charCodeAt(1) === 99 && val.charCodeAt(2) === 104; // 'lch'\n case 111: // 'o'\n return val.startsWith('oklch(') || val.startsWith('okhsl(');\n case 118: // 'v'\n return RE_VAR_COLOR.test(val);\n case 99: // 'c'\n return val === 'currentColor' || val === 'currentcolor';\n case 116: // 't'\n return val === 'transparent';\n default:\n return getNamedColorHex().has(val.toLowerCase());\n }\n}\n\n// Rate limiting for dev warnings to avoid spam\nlet colorWarningCount = 0;\nconst MAX_COLOR_WARNINGS = 10;\n\nexport const CUSTOM_UNITS = {\n r: '6px',\n cr: '10px',\n bw: '1px',\n ow: '3px',\n x: '8px',\n lh: 'var(--line-height)',\n sf: function sf(num: number) {\n return `minmax(0, ${num}fr)`;\n },\n};\n\nexport const DIRECTIONS = ['top', 'right', 'bottom', 'left'];\n\n// Lazy-initialized to break the circular dependency:\n// parser.ts → classify.ts → utils/styles.ts → parser.ts\nlet __tastyParser: StyleParser | null = null;\n\nfunction getOrCreateParser(): StyleParser {\n if (!__tastyParser) {\n __tastyParser = new StyleParser({ units: CUSTOM_UNITS });\n __tastyParser.setFuncs(__tastyFuncs);\n }\n return __tastyParser;\n}\n\n// Registry for user-provided custom functions that the parser can call.\n// It is updated through the `customFunc` helper exported below.\n// okhsl is registered as a built-in function so it works regardless of\n// tree-shaking or module initialization order.\nconst __tastyFuncs: Record<string, (groups: StyleDetails[]) => string> = {\n okhsl: okhslFunc,\n};\n\nexport function customFunc(\n name: string,\n fn: (groups: StyleDetails[]) => string,\n) {\n __tastyFuncs[name] = fn;\n getOrCreateParser().setFuncs(__tastyFuncs);\n}\n\n/**\n * Get the global StyleParser instance.\n * Used by configure() to apply parser configuration.\n */\nexport function getGlobalParser(): StyleParser {\n return getOrCreateParser();\n}\n\n/**\n * Get the current custom functions registry.\n * Used by configure() to merge with new functions.\n */\nexport function getGlobalFuncs(): Record<\n string,\n (groups: StyleDetails[]) => string\n> {\n return __tastyFuncs;\n}\n\n// ============================================================================\n// Global Predefined Tokens\n// ============================================================================\n\n/**\n * Storage for predefined tokens that are replaced during style parsing.\n * Keys are token names (with $ or # prefix), values are pre-processed CSS values.\n */\nlet __globalPredefinedTokens: Record<string, string> | null = null;\n\n/**\n * Set global predefined tokens.\n * Called from configure() after processing token values.\n * Merges with existing tokens (new tokens override existing ones with same key).\n * Keys are normalized to lowercase (parser lowercases input before classification).\n * @internal\n */\nexport function setGlobalPredefinedTokens(\n tokens: Record<string, string>,\n): void {\n // Normalize keys to lowercase for case-insensitive matching\n const normalizedTokens: Record<string, string> = {};\n for (const [key, value] of Object.entries(tokens)) {\n const lowerKey = key.toLowerCase();\n const lowerValue = value.toLowerCase();\n\n // Warn if trying to use bare #current to define other color tokens\n // #current represents currentcolor which cannot be used as a base for recursive token resolution\n // Note: #current.5 (with opacity) is allowed since it resolves to a concrete color-mix value\n if (lowerKey.startsWith('#') && lowerValue === '#current') {\n console.warn(\n `Tasty: Using #current to define color token \"${key}\" is not supported. ` +\n `The #current token represents currentcolor which cannot be used as a base for other tokens.`,\n );\n continue; // Skip this token\n }\n\n normalizedTokens[lowerKey] = value;\n }\n // Merge with existing tokens (consistent with how states, units, funcs are handled)\n __globalPredefinedTokens = __globalPredefinedTokens\n ? { ...__globalPredefinedTokens, ...normalizedTokens }\n : normalizedTokens;\n // Clear parser cache since token values affect parsing\n getOrCreateParser().clearCache();\n}\n\n/**\n * Get the current global predefined tokens.\n * Returns null if no tokens are configured.\n */\nexport function getGlobalPredefinedTokens(): Record<string, string> | null {\n return __globalPredefinedTokens;\n}\n\n/**\n * Reset global predefined tokens.\n * Used for testing.\n * @internal\n */\nexport function resetGlobalPredefinedTokens(): void {\n __globalPredefinedTokens = null;\n // Clear parser cache since token availability affects parsing\n getOrCreateParser().clearCache();\n}\n\n/**\n *\n * @param {String} value\n * @param {Number} mode\n * @returns {Object<String,String|Array>}\n */\nexport function parseStyle(value: StyleValue): ProcessedStyle {\n let str: string;\n\n if (typeof value === 'string') {\n str = value;\n } else if (typeof value === 'number') {\n str = String(value);\n } else {\n // boolean, null, undefined, objects etc. → empty string\n str = '';\n }\n\n return getOrCreateParser().process(str);\n}\n\n/**\n * Parse color. Find it value, name and opacity.\n * Optimized to avoid heavy parseStyle calls for simple color patterns.\n */\nexport function parseColor(val: string, ignoreError = false): ParsedColor {\n // Early return for non-strings or empty values\n if (typeof val !== 'string') {\n val = String(val ?? '');\n }\n\n val = val.trim();\n if (!val) return {};\n\n // Fast path: Check if it's a simple color pattern that doesn't need full parsing\n const isSimpleColor = isSimpleColorFast(val);\n\n let firstColor: string;\n if (isSimpleColor) {\n // For simple colors, use the value directly without parsing\n firstColor = val;\n } else {\n const processed = parseStyle(val);\n const extractedColor = processed.groups.find((g) => g.colors.length)\n ?.colors[0];\n\n if (!extractedColor) {\n // Rate-limited warning to avoid spam\n if (!ignoreError && devMode && colorWarningCount < MAX_COLOR_WARNINGS) {\n console.warn('Tasty: unable to parse color:', val);\n colorWarningCount++;\n if (colorWarningCount === MAX_COLOR_WARNINGS) {\n console.warn(\n 'Tasty: color parsing warnings will be suppressed from now on',\n );\n }\n }\n return {};\n }\n\n firstColor = extractedColor;\n }\n\n // Extract color name (if present) from variable pattern using precompiled regex\n let nameMatch = firstColor.match(COLOR_VAR_PATTERN);\n if (!nameMatch) {\n nameMatch = firstColor.match(COLOR_VAR_COMPONENTS_PATTERN);\n }\n\n let opacity: number | undefined;\n if (\n firstColor.startsWith('rgb') ||\n firstColor.startsWith('hsl') ||\n firstColor.startsWith('lch') ||\n firstColor.startsWith('oklch') ||\n firstColor.startsWith('okhsl')\n ) {\n const alphaMatch = firstColor.match(RGB_ALPHA_PATTERN);\n if (alphaMatch) {\n const v = parseFloat(alphaMatch[1]);\n if (!isNaN(v)) opacity = v * 100;\n }\n }\n\n return {\n color: firstColor,\n name: nameMatch ? nameMatch[1] : undefined,\n opacity,\n };\n}\n\nexport function filterMods(mods: string[], allowedMods: string[]): string[] {\n return mods.filter((mod) => allowedMods.includes(mod));\n}\n\n// ============================================================================\n// Style Stringification\n// ============================================================================\n\nconst _innerCache = new WeakMap();\nconst _topLevelCache = new WeakMap<object, string>();\n\nexport function stringifyStyles(styles: unknown): string {\n if (styles == null || typeof styles !== 'object') return '';\n\n const cached = _topLevelCache.get(styles as object);\n if (cached !== undefined) return cached;\n\n const obj = styles as Record<string, unknown>;\n const keys = Object.keys(obj).sort();\n const parts: string[] = [];\n for (const k of keys) {\n const v = obj[k];\n if (v === undefined || typeof v === 'function' || typeof v === 'symbol')\n continue;\n\n const c0 = k.charCodeAt(0);\n const needsInnerSort =\n ((c0 >= 65 && c0 <= 90) || c0 === 38) &&\n v &&\n typeof v === 'object' &&\n !Array.isArray(v);\n\n let sv: string;\n if (needsInnerSort) {\n sv = _innerCache.get(v);\n if (sv === undefined) {\n const innerObj = v as Record<string, unknown>;\n const innerKeys = Object.keys(innerObj).sort();\n const innerParts: string[] = [];\n for (const ik of innerKeys) {\n const ivs = JSON.stringify(innerObj[ik]);\n if (ivs !== undefined)\n innerParts.push(JSON.stringify(ik) + ':' + ivs);\n }\n sv = '{' + innerParts.join(',') + '}';\n _innerCache.set(v, sv);\n }\n } else {\n sv = JSON.stringify(v);\n }\n parts.push(JSON.stringify(k) + ':' + sv);\n }\n const result = '{' + parts.join(',') + '}';\n _topLevelCache.set(styles as object, result);\n return result;\n}\n"],"mappings":"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"}
|
package/dist/zero/babel.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { KeyframesSteps } from "../injector/types.js";
|
|
1
|
+
import { CounterStyleDescriptors, FontFaceInput, KeyframesSteps } from "../injector/types.js";
|
|
2
2
|
import { StyleDetails, UnitHandler } from "../parser/types.js";
|
|
3
3
|
import { StyleHandlerDefinition } from "../utils/styles.js";
|
|
4
4
|
import { ConfigTokens, RecipeStyles } from "../styles/types.js";
|
|
@@ -63,6 +63,16 @@ interface TastyZeroConfig {
|
|
|
63
63
|
* @example { fadeIn: { from: { opacity: 0 }, to: { opacity: 1 } } }
|
|
64
64
|
*/
|
|
65
65
|
keyframes?: Record<string, KeyframesSteps>;
|
|
66
|
+
/**
|
|
67
|
+
* Global @font-face definitions for static extraction.
|
|
68
|
+
* Keys are font-family names, values are descriptors or arrays of descriptors.
|
|
69
|
+
*/
|
|
70
|
+
fontFace?: Record<string, FontFaceInput>;
|
|
71
|
+
/**
|
|
72
|
+
* Global @counter-style definitions for static extraction.
|
|
73
|
+
* Keys are counter-style names, values are descriptor objects.
|
|
74
|
+
*/
|
|
75
|
+
counterStyle?: Record<string, CounterStyleDescriptors>;
|
|
66
76
|
/**
|
|
67
77
|
* Custom style handlers that transform style properties into CSS declarations.
|
|
68
78
|
* Handlers replace built-in handlers for the same style name.
|
package/dist/zero/babel.js
CHANGED
|
@@ -2,7 +2,7 @@ import { __require } from "../_virtual/_rolldown/runtime.js";
|
|
|
2
2
|
import { configure, getGlobalConfigTokens, resetConfig } from "../config.js";
|
|
3
3
|
import { mergeStyles } from "../utils/merge-styles.js";
|
|
4
4
|
import { resolveRecipes } from "../utils/resolve-recipes.js";
|
|
5
|
-
import { extractKeyframesFromStyles, extractPropertiesFromStyles, extractStylesForSelector, extractStylesWithChunks } from "./extractor.js";
|
|
5
|
+
import { extractCounterStyleFromStyles, extractFontFaceFromStyles, extractKeyframesFromStyles, extractPropertiesFromStyles, extractStylesForSelector, extractStylesWithChunks } from "./extractor.js";
|
|
6
6
|
import { CSSWriter } from "./css-writer.js";
|
|
7
7
|
import * as fs from "fs";
|
|
8
8
|
import * as path from "path";
|
|
@@ -130,9 +130,9 @@ var babel_default = declare((api, options) => {
|
|
|
130
130
|
const args = path.node.arguments;
|
|
131
131
|
if (args.length === 0) throw path.buildCodeFrameError("tastyStatic() requires at least one argument");
|
|
132
132
|
const firstArg = args[0];
|
|
133
|
-
if (t.isStringLiteral(firstArg)) handleSelectorMode(path, args, cssWriter, state.sourceFile, config.keyframes, config.autoPropertyTypes);
|
|
134
|
-
else if (t.isObjectExpression(firstArg)) handleStylesMode(path, args, cssWriter, state, globalRegistry, config.keyframes, config.autoPropertyTypes);
|
|
135
|
-
else if (t.isIdentifier(firstArg)) handleExtensionMode(path, args, cssWriter, state, globalRegistry, config.keyframes, config.autoPropertyTypes);
|
|
133
|
+
if (t.isStringLiteral(firstArg)) handleSelectorMode(path, args, cssWriter, state.sourceFile, config.keyframes, config.autoPropertyTypes, config.fontFace, config.counterStyle);
|
|
134
|
+
else if (t.isObjectExpression(firstArg)) handleStylesMode(path, args, cssWriter, state, globalRegistry, config.keyframes, config.autoPropertyTypes, config.fontFace, config.counterStyle);
|
|
135
|
+
else if (t.isIdentifier(firstArg)) handleExtensionMode(path, args, cssWriter, state, globalRegistry, config.keyframes, config.autoPropertyTypes, config.fontFace, config.counterStyle);
|
|
136
136
|
else throw path.buildCodeFrameError("tastyStatic() first argument must be an object (styles), identifier (base StaticStyle), or string (selector)");
|
|
137
137
|
},
|
|
138
138
|
VariableDeclarator(path, state) {
|
|
@@ -185,7 +185,7 @@ function extractClassNameFromStaticStyleObject(node) {
|
|
|
185
185
|
/**
|
|
186
186
|
* Handle tastyStatic(styles) - returns StaticStyle object
|
|
187
187
|
*/
|
|
188
|
-
function handleStylesMode(path, args, cssWriter, state, globalRegistry, globalKeyframes, autoPropertyTypes) {
|
|
188
|
+
function handleStylesMode(path, args, cssWriter, state, globalRegistry, globalKeyframes, autoPropertyTypes, globalFontFace, globalCounterStyle) {
|
|
189
189
|
const stylesArg = args[0];
|
|
190
190
|
if (!t.isObjectExpression(stylesArg)) throw path.buildCodeFrameError("tastyStatic(styles) argument must be a static object literal");
|
|
191
191
|
const styles = resolveRecipes(evaluateObjectExpression(stylesArg, path));
|
|
@@ -193,6 +193,8 @@ function handleStylesMode(path, args, cssWriter, state, globalRegistry, globalKe
|
|
|
193
193
|
for (const kf of keyframes) cssWriter.add(kf.css, kf.css, state.sourceFile);
|
|
194
194
|
const properties = extractPropertiesFromStyles(styles, { autoPropertyTypes });
|
|
195
195
|
for (const prop of properties) cssWriter.add(prop.css, prop.css, state.sourceFile);
|
|
196
|
+
for (const ff of extractFontFaceFromStyles(styles, globalFontFace)) cssWriter.add(ff.css, ff.css, state.sourceFile);
|
|
197
|
+
for (const cs of extractCounterStyleFromStyles(styles, globalCounterStyle)) cssWriter.add(cs.css, cs.css, state.sourceFile);
|
|
196
198
|
const chunks = extractStylesWithChunks(styles);
|
|
197
199
|
for (const chunk of chunks) {
|
|
198
200
|
const css = nameMap.size > 0 ? replaceAnimationNamesInCSS(chunk.css, nameMap) : chunk.css;
|
|
@@ -206,7 +208,7 @@ function handleStylesMode(path, args, cssWriter, state, globalRegistry, globalKe
|
|
|
206
208
|
/**
|
|
207
209
|
* Handle tastyStatic(base, styles) - extends base with additional styles
|
|
208
210
|
*/
|
|
209
|
-
function handleExtensionMode(path, args, cssWriter, state, globalRegistry, globalKeyframes, autoPropertyTypes) {
|
|
211
|
+
function handleExtensionMode(path, args, cssWriter, state, globalRegistry, globalKeyframes, autoPropertyTypes, globalFontFace, globalCounterStyle) {
|
|
210
212
|
if (args.length < 2) throw path.buildCodeFrameError("tastyStatic(base, styles) requires two arguments");
|
|
211
213
|
const baseArg = args[0];
|
|
212
214
|
const stylesArg = args[1];
|
|
@@ -221,6 +223,8 @@ function handleExtensionMode(path, args, cssWriter, state, globalRegistry, globa
|
|
|
221
223
|
for (const kf of keyframes) cssWriter.add(kf.css, kf.css, state.sourceFile);
|
|
222
224
|
const properties = extractPropertiesFromStyles(mergedStyles, { autoPropertyTypes });
|
|
223
225
|
for (const prop of properties) cssWriter.add(prop.css, prop.css, state.sourceFile);
|
|
226
|
+
for (const ff of extractFontFaceFromStyles(mergedStyles, globalFontFace)) cssWriter.add(ff.css, ff.css, state.sourceFile);
|
|
227
|
+
for (const cs of extractCounterStyleFromStyles(mergedStyles, globalCounterStyle)) cssWriter.add(cs.css, cs.css, state.sourceFile);
|
|
224
228
|
const chunks = extractStylesWithChunks(mergedStyles);
|
|
225
229
|
for (const chunk of chunks) {
|
|
226
230
|
const css = nameMap.size > 0 ? replaceAnimationNamesInCSS(chunk.css, nameMap) : chunk.css;
|
|
@@ -234,7 +238,7 @@ function handleExtensionMode(path, args, cssWriter, state, globalRegistry, globa
|
|
|
234
238
|
/**
|
|
235
239
|
* Handle tastyStatic(selector, styles) - removes the call entirely
|
|
236
240
|
*/
|
|
237
|
-
function handleSelectorMode(path, args, cssWriter, sourceFile, globalKeyframes, autoPropertyTypes) {
|
|
241
|
+
function handleSelectorMode(path, args, cssWriter, sourceFile, globalKeyframes, autoPropertyTypes, globalFontFace, globalCounterStyle) {
|
|
238
242
|
if (args.length < 2) throw path.buildCodeFrameError("tastyStatic(selector, styles) requires two arguments");
|
|
239
243
|
const selectorArg = args[0];
|
|
240
244
|
const stylesArg = args[1];
|
|
@@ -246,6 +250,8 @@ function handleSelectorMode(path, args, cssWriter, sourceFile, globalKeyframes,
|
|
|
246
250
|
for (const kf of keyframes) cssWriter.add(kf.css, kf.css, sourceFile);
|
|
247
251
|
const properties = extractPropertiesFromStyles(styles, { autoPropertyTypes });
|
|
248
252
|
for (const prop of properties) cssWriter.add(prop.css, prop.css, sourceFile);
|
|
253
|
+
for (const ff of extractFontFaceFromStyles(styles, globalFontFace)) cssWriter.add(ff.css, ff.css, sourceFile);
|
|
254
|
+
for (const cs of extractCounterStyleFromStyles(styles, globalCounterStyle)) cssWriter.add(cs.css, cs.css, sourceFile);
|
|
249
255
|
const result = extractStylesForSelector(selector, styles);
|
|
250
256
|
const css = nameMap.size > 0 ? replaceAnimationNamesInCSS(result.css, nameMap) : result.css;
|
|
251
257
|
cssWriter.add(selector, css, sourceFile);
|
package/dist/zero/babel.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"babel.js","names":[],"sources":["../../src/zero/babel.ts"],"sourcesContent":["/**\n * Babel plugin for zero-runtime tasty static site generation.\n *\n * Transforms:\n * - `tastyStatic(styles)` → StaticStyle object { className, styles, toString() }\n * - `tastyStatic(base, styles)` → StaticStyle object with merged styles\n * - `tastyStatic(selector, styles)` → removed entirely\n *\n * Usage:\n * ```javascript\n * // babel.config.js\n * module.exports = {\n * plugins: [\n * ['@tenphi/tasty/babel-plugin', { output: 'public/tasty.css' }]\n * ]\n * };\n * ```\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nimport { declare } from '@babel/helper-plugin-utils';\nimport * as t from '@babel/types';\nimport { createJiti } from 'jiti';\n\nimport { configure, getGlobalConfigTokens, resetConfig } from '../config';\nimport type { RecipeStyles, Styles, ConfigTokens } from '../styles/types';\nimport { mergeStyles } from '../utils/merge-styles';\nimport { resolveRecipes } from '../utils/resolve-recipes';\nimport type { StyleHandlerDefinition } from '../utils/styles';\n\nimport { CSSWriter } from './css-writer';\nimport {\n extractKeyframesFromStyles,\n extractPropertiesFromStyles,\n extractStylesForSelector,\n extractStylesWithChunks,\n} from './extractor';\n\nimport type { NodePath, PluginPass } from '@babel/core';\nimport type { KeyframesSteps } from '../injector/types';\nimport type { StyleDetails, UnitHandler } from '../parser/types';\nimport type { TastyPlugin } from '../plugins/types';\n\n/**\n * Build-time configuration for zero-runtime mode.\n * Subset of TastyConfig that applies at build time.\n */\nexport interface TastyZeroConfig {\n /**\n * Global predefined states for advanced state mapping.\n * Example: { '@mobile': '@media(w < 768px)', '@dark': '@root(theme=dark)' }\n */\n states?: Record<string, string>;\n /**\n * Enable development mode features: source comments in generated CSS.\n * Default: false\n */\n devMode?: boolean;\n /**\n * Parser LRU cache size (default: 1000).\n * Larger values improve performance for builds with many unique style values.\n */\n parserCacheSize?: number;\n /**\n * Custom units for the style parser (merged with built-in units).\n * Units transform numeric values like `2x` → `calc(2 * var(--gap))`.\n * @example { em: 'em', vw: 'vw', custom: (n) => `${n * 10}px` }\n */\n units?: Record<string, string | UnitHandler>;\n /**\n * Custom functions for the style parser (merged with existing).\n * Functions process parsed style groups and return CSS values.\n * @example { myFunc: (groups) => groups.map(g => g.output).join(' ') }\n */\n funcs?: Record<string, (groups: StyleDetails[]) => string>;\n /**\n * Plugins that extend tasty with custom functions, units, states, and handlers.\n * Plugins are processed in order, with later plugins overriding earlier ones.\n * @example\n * ```ts\n * import { okhslPlugin } from '@tenphi/tasty';\n *\n * // babel.config.js\n * module.exports = {\n * plugins: [\n * ['@tenphi/tasty/babel-plugin', {\n * config: { plugins: [okhslPlugin()] }\n * }]\n * ]\n * };\n * ```\n */\n plugins?: TastyPlugin[];\n /**\n * Global keyframes definitions for static extraction.\n * Keys are animation names, values are keyframes step definitions.\n * @example { fadeIn: { from: { opacity: 0 }, to: { opacity: 1 } } }\n */\n keyframes?: Record<string, KeyframesSteps>;\n /**\n * Custom style handlers that transform style properties into CSS declarations.\n * Handlers replace built-in handlers for the same style name.\n * @example\n * ```ts\n * handlers: {\n * fill: ({ fill }) => fill ? { 'background-color': fill } : undefined,\n * elevation: ({ elevation }) => ({\n * 'box-shadow': `0 ${elevation}px ${elevation * 2}px rgba(0,0,0,0.1)`,\n * }),\n * }\n * ```\n */\n handlers?: Record<string, StyleHandlerDefinition>;\n /**\n * Design tokens injected as CSS custom properties on `:root`.\n * Values are parsed through the Tasty DSL. Supports state maps.\n * @example { '$gap': '4px', '#primary': { '': '#purple', '@dark': '#light-purple' } }\n */\n tokens?: ConfigTokens;\n /**\n * Predefined tokens replaced during style parsing (parse-time substitution).\n * Use `$name` for custom properties and `#name` for color tokens.\n * @example { $spacing: '2x', '#accent': '#purple' }\n */\n replaceTokens?: Record<`$${string}` | `#${string}`, string | number>;\n /**\n * Predefined style recipes -- named style bundles that can be applied via `recipe` style property.\n * Recipe values are flat tasty styles (no sub-element keys).\n * @example\n * ```ts\n * recipes: {\n * card: { padding: '4x', fill: '#surface', radius: '1r', border: true },\n * elevated: { shadow: '2x 2x 4x #shadow' },\n * }\n * ```\n */\n recipes?: Record<string, RecipeStyles>;\n /**\n * Automatically infer and register CSS @property declarations from values.\n * @default true\n */\n autoPropertyTypes?: boolean;\n}\n\nexport interface TastyZeroBabelOptions {\n /** Output path for generated CSS (default: 'tasty.css') */\n output?: string;\n /**\n * Tasty configuration for build-time processing.\n * Can be a static object or a factory function that returns fresh config.\n * A factory is called on each plugin invocation, enabling hot reload\n * of config values that depend on external files (e.g. theme tokens).\n */\n config?: TastyZeroConfig | (() => TastyZeroConfig);\n /**\n * Absolute path to a TypeScript/JavaScript module that default-exports\n * a `TastyZeroConfig` object. The module is loaded via jiti on each\n * plugin invocation, enabling hot reload when the file changes.\n *\n * This option is JSON-serializable and is the primary way Turbopack\n * passes config to the Babel plugin (since Turbopack loader options\n * must be plain primitives/objects/arrays).\n *\n * When both `config` and `configFile` are set, `config` takes precedence.\n *\n * @example '/absolute/path/to/tasty-zero.config.ts'\n */\n configFile?: string;\n /**\n * Absolute file paths whose content affects the generated CSS.\n * When any of these files change, babel-loader invalidates its cache\n * and re-runs the plugin with fresh config values.\n *\n * Typically includes theme files that define Glaze palettes or token values.\n * Paths must be absolute (resolved by the Next.js wrapper).\n */\n configDeps?: string[];\n /**\n * Automatically replace `@tenphi/tasty/static` imports with an import\n * of the generated CSS file. This eliminates the need for users to\n * manually import the CSS in their app entry point.\n *\n * @default true\n */\n injectImport?: boolean;\n}\n\n/**\n * Registry to track StaticStyle objects by their variable names.\n * Used to resolve base styles when extending.\n */\ntype StaticStyleRegistry = Record<\n string,\n {\n styles: Styles;\n className: string;\n }\n>;\n\ninterface PluginState extends PluginPass {\n staticStyleRegistry: StaticStyleRegistry;\n /** Current source file path (for devMode source comments) */\n sourceFile?: string;\n /** Whether this file added CSS blocks to the writer (via tastyStatic calls) */\n _fileAddedCSS?: boolean;\n}\n\nfunction mtime(filePath: string): number | null {\n try {\n return fs.statSync(filePath).mtimeMs;\n } catch {\n return null;\n }\n}\n\nfunction clearRequireCacheTree(filePath: string): void {\n let resolved: string;\n\n try {\n resolved = require.resolve(filePath);\n } catch {\n return;\n }\n\n const mod = require.cache[resolved];\n\n if (!mod) return;\n\n const dir = resolved.substring(0, resolved.lastIndexOf('/'));\n\n if (mod.children) {\n for (const child of mod.children) {\n if (child.id.startsWith(dir) && !child.id.includes('node_modules')) {\n clearRequireCacheTree(child.id);\n }\n }\n }\n\n delete require.cache[resolved];\n}\n\n// Shared CSSWriter cache keyed by resolved output path.\n// Persists across per-file Babel invocations (Turbopack model) so that\n// CSS from all files accumulates instead of being overwritten.\ninterface WriterCacheEntry {\n writer: CSSWriter;\n configKey: string;\n registry: StaticStyleRegistry;\n config: TastyZeroConfig;\n}\nconst writerCache = new Map<string, WriterCacheEntry>();\n\n/** Clear the shared CSSWriter cache. Exposed for testing. */\nexport function clearWriterCache(): void {\n writerCache.clear();\n}\n\n// @ts-expect-error PluginState vs PluginPass type mismatch in @babel/helper-plugin-utils\nexport default declare<TastyZeroBabelOptions>((api, options) => {\n api.assertVersion(7);\n\n const outputPath = options.output || 'tasty.css';\n const resolvedOutputPath = path.resolve(outputPath);\n const injectImport = options.injectImport ?? true;\n\n if (injectImport) {\n const dir = path.dirname(resolvedOutputPath);\n\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n if (!fs.existsSync(resolvedOutputPath)) {\n fs.writeFileSync(\n resolvedOutputPath,\n '/* Generated by @tenphi/tasty/zero - DO NOT EDIT */\\n',\n );\n }\n }\n\n const configDeps = [\n ...(options.configFile ? [options.configFile] : []),\n ...(options.configDeps || []),\n ];\n\n // Fingerprint for config deps — used to detect config changes\n // and invalidate the shared CSSWriter cache.\n const configKey =\n configDeps.length > 0 ? configDeps.map(mtime).join(',') : '';\n\n // Register external dependencies for babel-loader cache invalidation.\n // When any configDeps file changes, babel-loader discards the cached\n // transform result and re-runs the plugin, picking up fresh config.\n if (configDeps.length > 0) {\n api.cache.using(() => configKey);\n\n for (const dep of configDeps) {\n try {\n (\n api as unknown as { addExternalDependency(path: string): void }\n ).addExternalDependency(dep);\n } catch {\n // addExternalDependency may not be available in all environments\n }\n }\n } else {\n api.cache.forever();\n }\n\n // When configDeps are set, clear the require cache so we get fresh values.\n if (configDeps.length > 0) {\n for (const dep of configDeps) {\n clearRequireCacheTree(dep);\n }\n }\n\n // Look up or create the shared CSSWriter for this output path.\n // When config deps change (different configKey), discard the old writer\n // and reset pipeline state so configure() can run again.\n const cached = writerCache.get(resolvedOutputPath);\n const configChanged = !cached || cached.configKey !== configKey;\n\n if (configChanged) {\n const configOption = options.config;\n let resolvedConfig: TastyZeroConfig;\n\n if (configOption) {\n resolvedConfig =\n typeof configOption === 'function' ? configOption() : configOption;\n } else if (options.configFile) {\n const jiti = createJiti(path.dirname(options.configFile), {\n moduleCache: false,\n });\n\n resolvedConfig = jiti(options.configFile) as TastyZeroConfig;\n } else {\n resolvedConfig = {};\n }\n\n const devMode = resolvedConfig.devMode ?? false;\n\n if (cached) {\n resetConfig();\n }\n\n configure(resolvedConfig);\n\n const newWriter = new CSSWriter(outputPath, { devMode });\n\n // Emit configured tokens as :root CSS custom properties\n const tokenStyles = getGlobalConfigTokens();\n if (tokenStyles && Object.keys(tokenStyles).length > 0) {\n const result = extractStylesForSelector(':root', tokenStyles);\n if (result.css) {\n newWriter.add(':root:tokens', result.css);\n }\n }\n\n writerCache.set(resolvedOutputPath, {\n writer: newWriter,\n configKey,\n registry: {},\n config: resolvedConfig,\n });\n }\n\n const entry = writerCache.get(resolvedOutputPath)!;\n const cssWriter = entry.writer;\n const globalRegistry = entry.registry;\n const config = entry.config;\n const devMode = config.devMode ?? false;\n\n return {\n name: 'tasty-zero',\n\n pre(this: PluginState) {\n // Initialize per-file registry\n this.staticStyleRegistry = {};\n this._fileAddedCSS = false;\n // Extract source filename for devMode comments\n if (devMode && this.filename) {\n // Get relative path or just filename\n this.sourceFile = this.filename.split('/').pop() || this.filename;\n }\n },\n\n visitor: {\n ImportDeclaration(\n nodePath: NodePath<t.ImportDeclaration>,\n state: PluginState,\n ) {\n const source = nodePath.node.source.value;\n\n if (\n source === '@tenphi/tasty/static' ||\n source.endsWith('/tasty/static')\n ) {\n if (injectImport) {\n let importPath = resolvedOutputPath;\n\n if (state.filename) {\n const sourceDir = path.dirname(state.filename);\n importPath = path.relative(sourceDir, resolvedOutputPath);\n\n if (!importPath.startsWith('.')) {\n importPath = './' + importPath;\n }\n }\n\n nodePath.replaceWith(\n t.importDeclaration([], t.stringLiteral(importPath)),\n );\n } else {\n nodePath.remove();\n }\n }\n },\n\n // Transform tastyStatic() calls\n CallExpression(path: NodePath<t.CallExpression>, state: PluginState) {\n const callee = path.node.callee;\n\n // Match tastyStatic(...) calls\n if (!t.isIdentifier(callee, { name: 'tastyStatic' })) {\n return;\n }\n\n state._fileAddedCSS = true;\n\n const args = path.node.arguments;\n\n if (args.length === 0) {\n throw path.buildCodeFrameError(\n 'tastyStatic() requires at least one argument',\n );\n }\n\n const firstArg = args[0];\n\n if (t.isStringLiteral(firstArg)) {\n // Selector mode: tastyStatic(selector, styles)\n handleSelectorMode(\n path,\n args,\n cssWriter,\n state.sourceFile,\n config.keyframes,\n config.autoPropertyTypes,\n );\n } else if (t.isObjectExpression(firstArg)) {\n // Styles mode: tastyStatic(styles)\n handleStylesMode(\n path,\n args,\n cssWriter,\n state,\n globalRegistry,\n config.keyframes,\n config.autoPropertyTypes,\n );\n } else if (t.isIdentifier(firstArg)) {\n // Extension mode: tastyStatic(base, styles)\n handleExtensionMode(\n path,\n args,\n cssWriter,\n state,\n globalRegistry,\n config.keyframes,\n config.autoPropertyTypes,\n );\n } else {\n throw path.buildCodeFrameError(\n 'tastyStatic() first argument must be an object (styles), ' +\n 'identifier (base StaticStyle), or string (selector)',\n );\n }\n },\n\n // Track variable declarations to register StaticStyle objects\n VariableDeclarator(\n path: NodePath<t.VariableDeclarator>,\n state: PluginState,\n ) {\n const init = path.node.init;\n const id = path.node.id;\n\n // Check if this is a StaticStyle object (has className and styles properties)\n if (\n t.isIdentifier(id) &&\n t.isObjectExpression(init) &&\n isStaticStyleObject(init)\n ) {\n const variableName = id.name;\n const styles = extractStylesFromStaticStyleObject(init, path);\n const className = extractClassNameFromStaticStyleObject(init);\n\n if (styles && className) {\n state.staticStyleRegistry[variableName] = { styles, className };\n globalRegistry[variableName] = { styles, className };\n }\n }\n },\n },\n\n post(this: PluginState) {\n // Only write when this file contributed CSS (had tastyStatic calls).\n // In Turbopack, separate workers each have their own CSSWriter with\n // only token CSS. Letting those workers write would overwrite the\n // complete CSS produced by the worker that processed tastyStatic files.\n if (this._fileAddedCSS && cssWriter.size > 0) {\n cssWriter.write();\n }\n },\n };\n});\n\n/**\n * Check if an object expression looks like a StaticStyle object\n */\nfunction isStaticStyleObject(node: t.ObjectExpression): boolean {\n const hasClassName = node.properties.some(\n (p) =>\n t.isObjectProperty(p) && t.isIdentifier(p.key, { name: 'className' }),\n );\n const hasStyles = node.properties.some(\n (p) => t.isObjectProperty(p) && t.isIdentifier(p.key, { name: 'styles' }),\n );\n return hasClassName && hasStyles;\n}\n\n/**\n * Extract styles object from a StaticStyle object expression\n */\nfunction extractStylesFromStaticStyleObject(\n node: t.ObjectExpression,\n path: NodePath,\n): Styles | null {\n for (const prop of node.properties) {\n if (\n t.isObjectProperty(prop) &&\n t.isIdentifier(prop.key, { name: 'styles' }) &&\n t.isObjectExpression(prop.value)\n ) {\n return evaluateObjectExpression(prop.value, path) as Styles;\n }\n }\n return null;\n}\n\n/**\n * Extract className from a StaticStyle object expression\n */\nfunction extractClassNameFromStaticStyleObject(\n node: t.ObjectExpression,\n): string | null {\n for (const prop of node.properties) {\n if (\n t.isObjectProperty(prop) &&\n t.isIdentifier(prop.key, { name: 'className' }) &&\n t.isStringLiteral(prop.value)\n ) {\n return prop.value.value;\n }\n }\n return null;\n}\n\n/**\n * Handle tastyStatic(styles) - returns StaticStyle object\n */\nfunction handleStylesMode(\n path: NodePath<t.CallExpression>,\n args: t.CallExpression['arguments'],\n cssWriter: CSSWriter,\n state: PluginState,\n globalRegistry: StaticStyleRegistry,\n globalKeyframes?: Record<string, KeyframesSteps>,\n autoPropertyTypes?: boolean,\n): void {\n const stylesArg = args[0];\n\n if (!t.isObjectExpression(stylesArg)) {\n throw path.buildCodeFrameError(\n 'tastyStatic(styles) argument must be a static object literal',\n );\n }\n\n // Evaluate styles object at build time\n const rawStyles = evaluateObjectExpression(stylesArg, path) as Styles;\n\n // Resolve recipes before extraction\n const styles = resolveRecipes(rawStyles);\n\n // Extract keyframes (deduplicated by content)\n const { keyframes, nameMap } = extractKeyframesFromStyles(\n styles,\n globalKeyframes,\n );\n\n // Add keyframes CSS\n for (const kf of keyframes) {\n cssWriter.add(kf.css, kf.css, state.sourceFile);\n }\n\n // Extract and add auto-inferred @property rules\n const properties = extractPropertiesFromStyles(styles, { autoPropertyTypes });\n for (const prop of properties) {\n cssWriter.add(prop.css, prop.css, state.sourceFile);\n }\n\n // Extract styles with chunking\n const chunks = extractStylesWithChunks(styles);\n\n // Add CSS to writer, replacing animation names if needed\n for (const chunk of chunks) {\n const css =\n nameMap.size > 0\n ? replaceAnimationNamesInCSS(chunk.css, nameMap)\n : chunk.css;\n cssWriter.add(chunk.className, css, state.sourceFile);\n }\n\n // Generate className\n const className =\n chunks.length > 0 ? chunks.map((c) => c.className).join(' ') : '';\n\n // Replace call with StaticStyle object\n const staticStyleObject = createStaticStyleAST(className, styles);\n path.replaceWith(staticStyleObject);\n\n // Register if this is being assigned to a variable\n registerIfVariableDeclaration(path, className, styles, state, globalRegistry);\n}\n\n/**\n * Handle tastyStatic(base, styles) - extends base with additional styles\n */\nfunction handleExtensionMode(\n path: NodePath<t.CallExpression>,\n args: t.CallExpression['arguments'],\n cssWriter: CSSWriter,\n state: PluginState,\n globalRegistry: StaticStyleRegistry,\n globalKeyframes?: Record<string, KeyframesSteps>,\n autoPropertyTypes?: boolean,\n): void {\n if (args.length < 2) {\n throw path.buildCodeFrameError(\n 'tastyStatic(base, styles) requires two arguments',\n );\n }\n\n const baseArg = args[0];\n const stylesArg = args[1];\n\n if (!t.isIdentifier(baseArg)) {\n throw path.buildCodeFrameError(\n 'tastyStatic(base, styles) first argument must be an identifier',\n );\n }\n\n if (!t.isObjectExpression(stylesArg)) {\n throw path.buildCodeFrameError(\n 'tastyStatic(base, styles) second argument must be a static object literal',\n );\n }\n\n const baseName = baseArg.name;\n\n // Look up base styles in registry\n const baseEntry =\n state.staticStyleRegistry[baseName] || globalRegistry[baseName];\n\n if (!baseEntry) {\n throw path.buildCodeFrameError(\n `Cannot find base StaticStyle '${baseName}'. ` +\n 'Make sure it is defined before being extended.',\n );\n }\n\n // Evaluate override styles\n const overrideStyles = evaluateObjectExpression(stylesArg, path) as Styles;\n\n // Merge styles using mergeStyles, then resolve recipes\n const mergedStyles = resolveRecipes(\n mergeStyles(baseEntry.styles, overrideStyles),\n );\n\n // Extract keyframes (deduplicated by content)\n const { keyframes, nameMap } = extractKeyframesFromStyles(\n mergedStyles,\n globalKeyframes,\n );\n\n // Add keyframes CSS\n for (const kf of keyframes) {\n cssWriter.add(kf.css, kf.css, state.sourceFile);\n }\n\n // Extract and add auto-inferred @property rules\n const properties = extractPropertiesFromStyles(mergedStyles, {\n autoPropertyTypes,\n });\n for (const prop of properties) {\n cssWriter.add(prop.css, prop.css, state.sourceFile);\n }\n\n // Extract styles with chunking\n const chunks = extractStylesWithChunks(mergedStyles);\n\n // Add CSS to writer, replacing animation names if needed\n for (const chunk of chunks) {\n const css =\n nameMap.size > 0\n ? replaceAnimationNamesInCSS(chunk.css, nameMap)\n : chunk.css;\n cssWriter.add(chunk.className, css, state.sourceFile);\n }\n\n // Generate className\n const className =\n chunks.length > 0 ? chunks.map((c) => c.className).join(' ') : '';\n\n // Replace call with StaticStyle object\n const staticStyleObject = createStaticStyleAST(className, mergedStyles);\n path.replaceWith(staticStyleObject);\n\n // Register if this is being assigned to a variable\n registerIfVariableDeclaration(\n path,\n className,\n mergedStyles,\n state,\n globalRegistry,\n );\n}\n\n/**\n * Handle tastyStatic(selector, styles) - removes the call entirely\n */\nfunction handleSelectorMode(\n path: NodePath<t.CallExpression>,\n args: t.CallExpression['arguments'],\n cssWriter: CSSWriter,\n sourceFile?: string,\n globalKeyframes?: Record<string, KeyframesSteps>,\n autoPropertyTypes?: boolean,\n): void {\n if (args.length < 2) {\n throw path.buildCodeFrameError(\n 'tastyStatic(selector, styles) requires two arguments',\n );\n }\n\n const selectorArg = args[0];\n const stylesArg = args[1];\n\n if (!t.isStringLiteral(selectorArg)) {\n throw path.buildCodeFrameError(\n 'tastyStatic(selector, styles) first argument must be a string literal',\n );\n }\n\n if (!t.isObjectExpression(stylesArg)) {\n throw path.buildCodeFrameError(\n 'tastyStatic(selector, styles) second argument must be a static object literal',\n );\n }\n\n const selector = selectorArg.value;\n const rawStyles = evaluateObjectExpression(stylesArg, path) as Styles;\n\n // Resolve recipes before extraction\n const styles = resolveRecipes(rawStyles);\n\n // Extract keyframes (deduplicated by content)\n const { keyframes, nameMap } = extractKeyframesFromStyles(\n styles,\n globalKeyframes,\n );\n\n // Add keyframes CSS\n for (const kf of keyframes) {\n cssWriter.add(kf.css, kf.css, sourceFile);\n }\n\n // Extract and add auto-inferred @property rules\n const properties = extractPropertiesFromStyles(styles, { autoPropertyTypes });\n for (const prop of properties) {\n cssWriter.add(prop.css, prop.css, sourceFile);\n }\n\n // Extract styles for selector\n const result = extractStylesForSelector(selector, styles);\n\n // Replace animation names if needed and add CSS\n const css =\n nameMap.size > 0\n ? replaceAnimationNamesInCSS(result.css, nameMap)\n : result.css;\n cssWriter.add(selector, css, sourceFile);\n\n // Remove the entire statement\n const parent = path.parentPath;\n if (parent && t.isExpressionStatement(parent.node)) {\n parent.remove();\n } else {\n // If used in an expression context (which would be incorrect usage),\n // replace with undefined\n path.replaceWith(t.identifier('undefined'));\n }\n}\n\n/**\n * Create a StaticStyle object AST node\n */\nfunction createStaticStyleAST(\n className: string,\n styles: Styles,\n): t.ObjectExpression {\n return t.objectExpression([\n t.objectProperty(t.identifier('className'), t.stringLiteral(className)),\n t.objectProperty(t.identifier('styles'), valueToAST(styles)),\n t.objectMethod(\n 'method',\n t.identifier('toString'),\n [],\n t.blockStatement([\n t.returnStatement(\n t.memberExpression(t.thisExpression(), t.identifier('className')),\n ),\n ]),\n ),\n ]);\n}\n\n/**\n * Register a StaticStyle in the registry if it's being assigned to a variable\n */\nfunction registerIfVariableDeclaration(\n path: NodePath,\n className: string,\n styles: Styles,\n state: PluginState,\n globalRegistry: StaticStyleRegistry,\n): void {\n const parent = path.parentPath;\n if (parent && t.isVariableDeclarator(parent.node)) {\n const id = parent.node.id;\n if (t.isIdentifier(id)) {\n const variableName = id.name;\n state.staticStyleRegistry[variableName] = { styles, className };\n globalRegistry[variableName] = { styles, className };\n }\n }\n}\n\n/**\n * Convert a JavaScript value to an AST node\n */\nfunction valueToAST(value: unknown): t.Expression {\n if (value === null) {\n return t.nullLiteral();\n }\n if (value === undefined) {\n return t.identifier('undefined');\n }\n if (typeof value === 'string') {\n return t.stringLiteral(value);\n }\n if (typeof value === 'number') {\n return t.numericLiteral(value);\n }\n if (typeof value === 'boolean') {\n return t.booleanLiteral(value);\n }\n if (Array.isArray(value)) {\n return t.arrayExpression(value.map(valueToAST));\n }\n if (typeof value === 'object') {\n const properties = Object.entries(value).map(([key, val]) =>\n t.objectProperty(\n /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key)\n ? t.identifier(key)\n : t.stringLiteral(key),\n valueToAST(val),\n ),\n );\n return t.objectExpression(properties);\n }\n // Fallback for unsupported types\n return t.identifier('undefined');\n}\n\n/**\n * Evaluate an ObjectExpression to a plain JavaScript object.\n * Only supports static values that can be determined at build time.\n */\nfunction evaluateObjectExpression(\n node: t.ObjectExpression,\n path: NodePath,\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n\n for (const prop of node.properties) {\n if (t.isSpreadElement(prop)) {\n throw path.buildCodeFrameError(\n 'Spread elements are not supported in tastyStatic() - styles must be fully static',\n );\n }\n\n if (!t.isObjectProperty(prop)) {\n throw path.buildCodeFrameError(\n 'Only object properties are supported in tastyStatic()',\n );\n }\n\n // Get key\n let key: string;\n if (t.isIdentifier(prop.key)) {\n key = prop.key.name;\n } else if (t.isStringLiteral(prop.key)) {\n key = prop.key.value;\n } else {\n throw path.buildCodeFrameError(\n 'Dynamic property keys are not supported in tastyStatic()',\n );\n }\n\n // Get value\n const value = evaluateExpression(prop.value, path);\n result[key] = value;\n }\n\n return result;\n}\n\n/**\n * Evaluate an expression to a JavaScript value.\n */\nfunction evaluateExpression(node: t.Node, path: NodePath): unknown {\n if (t.isStringLiteral(node)) {\n return node.value;\n }\n\n if (t.isNumericLiteral(node)) {\n return node.value;\n }\n\n if (t.isBooleanLiteral(node)) {\n return node.value;\n }\n\n if (t.isNullLiteral(node)) {\n return null;\n }\n\n if (t.isIdentifier(node, { name: 'undefined' })) {\n return undefined;\n }\n\n if (t.isArrayExpression(node)) {\n return node.elements.map((el) => {\n if (el === null) return null;\n if (t.isSpreadElement(el)) {\n throw path.buildCodeFrameError(\n 'Spread elements are not supported in tastyStatic()',\n );\n }\n return evaluateExpression(el, path);\n });\n }\n\n if (t.isObjectExpression(node)) {\n return evaluateObjectExpression(node, path);\n }\n\n if (t.isTemplateLiteral(node)) {\n // Only support template literals without expressions\n if (node.expressions.length > 0) {\n throw path.buildCodeFrameError(\n 'Template literals with expressions are not supported in tastyStatic()',\n );\n }\n return node.quasis.map((q) => q.value.cooked).join('');\n }\n\n if (t.isUnaryExpression(node, { operator: '-' })) {\n const arg = evaluateExpression(node.argument, path);\n if (typeof arg === 'number') {\n return -arg;\n }\n }\n\n throw path.buildCodeFrameError(\n `Dynamic expressions are not supported in tastyStatic() - got ${node.type}. ` +\n 'All values must be static literals.',\n );\n}\n\n/**\n * Replace animation names in CSS string.\n * Wraps the keyframes replaceAnimationNames to work on full CSS blocks.\n */\nfunction replaceAnimationNamesInCSS(\n css: string,\n nameMap: Map<string, string>,\n): string {\n if (nameMap.size === 0) return css;\n\n // The CSS contains full rules like \".class { animation: name 1s; }\"\n // We need to replace animation names within declaration blocks\n return css.replace(\n /(animation(?:-name)?)\\s*:\\s*([^;}]+)/gi,\n (match, prop, value) => {\n let newValue = value;\n for (const [original, replacement] of nameMap) {\n // Word boundary replacement\n const pattern = new RegExp(`\\\\b${escapeRegex(original)}\\\\b`, 'g');\n newValue = newValue.replace(pattern, replacement);\n }\n return `${prop}: ${newValue}`;\n },\n );\n}\n\n/**\n * Escape special regex characters.\n */\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"babel.js","names":[],"sources":["../../src/zero/babel.ts"],"sourcesContent":["/**\n * Babel plugin for zero-runtime tasty static site generation.\n *\n * Transforms:\n * - `tastyStatic(styles)` → StaticStyle object { className, styles, toString() }\n * - `tastyStatic(base, styles)` → StaticStyle object with merged styles\n * - `tastyStatic(selector, styles)` → removed entirely\n *\n * Usage:\n * ```javascript\n * // babel.config.js\n * module.exports = {\n * plugins: [\n * ['@tenphi/tasty/babel-plugin', { output: 'public/tasty.css' }]\n * ]\n * };\n * ```\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nimport { declare } from '@babel/helper-plugin-utils';\nimport * as t from '@babel/types';\nimport { createJiti } from 'jiti';\n\nimport { configure, getGlobalConfigTokens, resetConfig } from '../config';\nimport type { RecipeStyles, Styles, ConfigTokens } from '../styles/types';\nimport { mergeStyles } from '../utils/merge-styles';\nimport { resolveRecipes } from '../utils/resolve-recipes';\nimport type { StyleHandlerDefinition } from '../utils/styles';\n\nimport { CSSWriter } from './css-writer';\nimport {\n extractCounterStyleFromStyles,\n extractFontFaceFromStyles,\n extractKeyframesFromStyles,\n extractPropertiesFromStyles,\n extractStylesForSelector,\n extractStylesWithChunks,\n} from './extractor';\n\nimport type { NodePath, PluginPass } from '@babel/core';\nimport type {\n CounterStyleDescriptors,\n FontFaceInput,\n KeyframesSteps,\n} from '../injector/types';\nimport type { StyleDetails, UnitHandler } from '../parser/types';\nimport type { TastyPlugin } from '../plugins/types';\n\n/**\n * Build-time configuration for zero-runtime mode.\n * Subset of TastyConfig that applies at build time.\n */\nexport interface TastyZeroConfig {\n /**\n * Global predefined states for advanced state mapping.\n * Example: { '@mobile': '@media(w < 768px)', '@dark': '@root(theme=dark)' }\n */\n states?: Record<string, string>;\n /**\n * Enable development mode features: source comments in generated CSS.\n * Default: false\n */\n devMode?: boolean;\n /**\n * Parser LRU cache size (default: 1000).\n * Larger values improve performance for builds with many unique style values.\n */\n parserCacheSize?: number;\n /**\n * Custom units for the style parser (merged with built-in units).\n * Units transform numeric values like `2x` → `calc(2 * var(--gap))`.\n * @example { em: 'em', vw: 'vw', custom: (n) => `${n * 10}px` }\n */\n units?: Record<string, string | UnitHandler>;\n /**\n * Custom functions for the style parser (merged with existing).\n * Functions process parsed style groups and return CSS values.\n * @example { myFunc: (groups) => groups.map(g => g.output).join(' ') }\n */\n funcs?: Record<string, (groups: StyleDetails[]) => string>;\n /**\n * Plugins that extend tasty with custom functions, units, states, and handlers.\n * Plugins are processed in order, with later plugins overriding earlier ones.\n * @example\n * ```ts\n * import { okhslPlugin } from '@tenphi/tasty';\n *\n * // babel.config.js\n * module.exports = {\n * plugins: [\n * ['@tenphi/tasty/babel-plugin', {\n * config: { plugins: [okhslPlugin()] }\n * }]\n * ]\n * };\n * ```\n */\n plugins?: TastyPlugin[];\n /**\n * Global keyframes definitions for static extraction.\n * Keys are animation names, values are keyframes step definitions.\n * @example { fadeIn: { from: { opacity: 0 }, to: { opacity: 1 } } }\n */\n keyframes?: Record<string, KeyframesSteps>;\n /**\n * Global @font-face definitions for static extraction.\n * Keys are font-family names, values are descriptors or arrays of descriptors.\n */\n fontFace?: Record<string, FontFaceInput>;\n /**\n * Global @counter-style definitions for static extraction.\n * Keys are counter-style names, values are descriptor objects.\n */\n counterStyle?: Record<string, CounterStyleDescriptors>;\n /**\n * Custom style handlers that transform style properties into CSS declarations.\n * Handlers replace built-in handlers for the same style name.\n * @example\n * ```ts\n * handlers: {\n * fill: ({ fill }) => fill ? { 'background-color': fill } : undefined,\n * elevation: ({ elevation }) => ({\n * 'box-shadow': `0 ${elevation}px ${elevation * 2}px rgba(0,0,0,0.1)`,\n * }),\n * }\n * ```\n */\n handlers?: Record<string, StyleHandlerDefinition>;\n /**\n * Design tokens injected as CSS custom properties on `:root`.\n * Values are parsed through the Tasty DSL. Supports state maps.\n * @example { '$gap': '4px', '#primary': { '': '#purple', '@dark': '#light-purple' } }\n */\n tokens?: ConfigTokens;\n /**\n * Predefined tokens replaced during style parsing (parse-time substitution).\n * Use `$name` for custom properties and `#name` for color tokens.\n * @example { $spacing: '2x', '#accent': '#purple' }\n */\n replaceTokens?: Record<`$${string}` | `#${string}`, string | number>;\n /**\n * Predefined style recipes -- named style bundles that can be applied via `recipe` style property.\n * Recipe values are flat tasty styles (no sub-element keys).\n * @example\n * ```ts\n * recipes: {\n * card: { padding: '4x', fill: '#surface', radius: '1r', border: true },\n * elevated: { shadow: '2x 2x 4x #shadow' },\n * }\n * ```\n */\n recipes?: Record<string, RecipeStyles>;\n /**\n * Automatically infer and register CSS @property declarations from values.\n * @default true\n */\n autoPropertyTypes?: boolean;\n}\n\nexport interface TastyZeroBabelOptions {\n /** Output path for generated CSS (default: 'tasty.css') */\n output?: string;\n /**\n * Tasty configuration for build-time processing.\n * Can be a static object or a factory function that returns fresh config.\n * A factory is called on each plugin invocation, enabling hot reload\n * of config values that depend on external files (e.g. theme tokens).\n */\n config?: TastyZeroConfig | (() => TastyZeroConfig);\n /**\n * Absolute path to a TypeScript/JavaScript module that default-exports\n * a `TastyZeroConfig` object. The module is loaded via jiti on each\n * plugin invocation, enabling hot reload when the file changes.\n *\n * This option is JSON-serializable and is the primary way Turbopack\n * passes config to the Babel plugin (since Turbopack loader options\n * must be plain primitives/objects/arrays).\n *\n * When both `config` and `configFile` are set, `config` takes precedence.\n *\n * @example '/absolute/path/to/tasty-zero.config.ts'\n */\n configFile?: string;\n /**\n * Absolute file paths whose content affects the generated CSS.\n * When any of these files change, babel-loader invalidates its cache\n * and re-runs the plugin with fresh config values.\n *\n * Typically includes theme files that define Glaze palettes or token values.\n * Paths must be absolute (resolved by the Next.js wrapper).\n */\n configDeps?: string[];\n /**\n * Automatically replace `@tenphi/tasty/static` imports with an import\n * of the generated CSS file. This eliminates the need for users to\n * manually import the CSS in their app entry point.\n *\n * @default true\n */\n injectImport?: boolean;\n}\n\n/**\n * Registry to track StaticStyle objects by their variable names.\n * Used to resolve base styles when extending.\n */\ntype StaticStyleRegistry = Record<\n string,\n {\n styles: Styles;\n className: string;\n }\n>;\n\ninterface PluginState extends PluginPass {\n staticStyleRegistry: StaticStyleRegistry;\n /** Current source file path (for devMode source comments) */\n sourceFile?: string;\n /** Whether this file added CSS blocks to the writer (via tastyStatic calls) */\n _fileAddedCSS?: boolean;\n}\n\nfunction mtime(filePath: string): number | null {\n try {\n return fs.statSync(filePath).mtimeMs;\n } catch {\n return null;\n }\n}\n\nfunction clearRequireCacheTree(filePath: string): void {\n let resolved: string;\n\n try {\n resolved = require.resolve(filePath);\n } catch {\n return;\n }\n\n const mod = require.cache[resolved];\n\n if (!mod) return;\n\n const dir = resolved.substring(0, resolved.lastIndexOf('/'));\n\n if (mod.children) {\n for (const child of mod.children) {\n if (child.id.startsWith(dir) && !child.id.includes('node_modules')) {\n clearRequireCacheTree(child.id);\n }\n }\n }\n\n delete require.cache[resolved];\n}\n\n// Shared CSSWriter cache keyed by resolved output path.\n// Persists across per-file Babel invocations (Turbopack model) so that\n// CSS from all files accumulates instead of being overwritten.\ninterface WriterCacheEntry {\n writer: CSSWriter;\n configKey: string;\n registry: StaticStyleRegistry;\n config: TastyZeroConfig;\n}\nconst writerCache = new Map<string, WriterCacheEntry>();\n\n/** Clear the shared CSSWriter cache. Exposed for testing. */\nexport function clearWriterCache(): void {\n writerCache.clear();\n}\n\n// @ts-expect-error PluginState vs PluginPass type mismatch in @babel/helper-plugin-utils\nexport default declare<TastyZeroBabelOptions>((api, options) => {\n api.assertVersion(7);\n\n const outputPath = options.output || 'tasty.css';\n const resolvedOutputPath = path.resolve(outputPath);\n const injectImport = options.injectImport ?? true;\n\n if (injectImport) {\n const dir = path.dirname(resolvedOutputPath);\n\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n if (!fs.existsSync(resolvedOutputPath)) {\n fs.writeFileSync(\n resolvedOutputPath,\n '/* Generated by @tenphi/tasty/zero - DO NOT EDIT */\\n',\n );\n }\n }\n\n const configDeps = [\n ...(options.configFile ? [options.configFile] : []),\n ...(options.configDeps || []),\n ];\n\n // Fingerprint for config deps — used to detect config changes\n // and invalidate the shared CSSWriter cache.\n const configKey =\n configDeps.length > 0 ? configDeps.map(mtime).join(',') : '';\n\n // Register external dependencies for babel-loader cache invalidation.\n // When any configDeps file changes, babel-loader discards the cached\n // transform result and re-runs the plugin, picking up fresh config.\n if (configDeps.length > 0) {\n api.cache.using(() => configKey);\n\n for (const dep of configDeps) {\n try {\n (\n api as unknown as { addExternalDependency(path: string): void }\n ).addExternalDependency(dep);\n } catch {\n // addExternalDependency may not be available in all environments\n }\n }\n } else {\n api.cache.forever();\n }\n\n // When configDeps are set, clear the require cache so we get fresh values.\n if (configDeps.length > 0) {\n for (const dep of configDeps) {\n clearRequireCacheTree(dep);\n }\n }\n\n // Look up or create the shared CSSWriter for this output path.\n // When config deps change (different configKey), discard the old writer\n // and reset pipeline state so configure() can run again.\n const cached = writerCache.get(resolvedOutputPath);\n const configChanged = !cached || cached.configKey !== configKey;\n\n if (configChanged) {\n const configOption = options.config;\n let resolvedConfig: TastyZeroConfig;\n\n if (configOption) {\n resolvedConfig =\n typeof configOption === 'function' ? configOption() : configOption;\n } else if (options.configFile) {\n const jiti = createJiti(path.dirname(options.configFile), {\n moduleCache: false,\n });\n\n resolvedConfig = jiti(options.configFile) as TastyZeroConfig;\n } else {\n resolvedConfig = {};\n }\n\n const devMode = resolvedConfig.devMode ?? false;\n\n if (cached) {\n resetConfig();\n }\n\n configure(resolvedConfig);\n\n const newWriter = new CSSWriter(outputPath, { devMode });\n\n // Emit configured tokens as :root CSS custom properties\n const tokenStyles = getGlobalConfigTokens();\n if (tokenStyles && Object.keys(tokenStyles).length > 0) {\n const result = extractStylesForSelector(':root', tokenStyles);\n if (result.css) {\n newWriter.add(':root:tokens', result.css);\n }\n }\n\n writerCache.set(resolvedOutputPath, {\n writer: newWriter,\n configKey,\n registry: {},\n config: resolvedConfig,\n });\n }\n\n const entry = writerCache.get(resolvedOutputPath)!;\n const cssWriter = entry.writer;\n const globalRegistry = entry.registry;\n const config = entry.config;\n const devMode = config.devMode ?? false;\n\n return {\n name: 'tasty-zero',\n\n pre(this: PluginState) {\n // Initialize per-file registry\n this.staticStyleRegistry = {};\n this._fileAddedCSS = false;\n // Extract source filename for devMode comments\n if (devMode && this.filename) {\n // Get relative path or just filename\n this.sourceFile = this.filename.split('/').pop() || this.filename;\n }\n },\n\n visitor: {\n ImportDeclaration(\n nodePath: NodePath<t.ImportDeclaration>,\n state: PluginState,\n ) {\n const source = nodePath.node.source.value;\n\n if (\n source === '@tenphi/tasty/static' ||\n source.endsWith('/tasty/static')\n ) {\n if (injectImport) {\n let importPath = resolvedOutputPath;\n\n if (state.filename) {\n const sourceDir = path.dirname(state.filename);\n importPath = path.relative(sourceDir, resolvedOutputPath);\n\n if (!importPath.startsWith('.')) {\n importPath = './' + importPath;\n }\n }\n\n nodePath.replaceWith(\n t.importDeclaration([], t.stringLiteral(importPath)),\n );\n } else {\n nodePath.remove();\n }\n }\n },\n\n // Transform tastyStatic() calls\n CallExpression(path: NodePath<t.CallExpression>, state: PluginState) {\n const callee = path.node.callee;\n\n // Match tastyStatic(...) calls\n if (!t.isIdentifier(callee, { name: 'tastyStatic' })) {\n return;\n }\n\n state._fileAddedCSS = true;\n\n const args = path.node.arguments;\n\n if (args.length === 0) {\n throw path.buildCodeFrameError(\n 'tastyStatic() requires at least one argument',\n );\n }\n\n const firstArg = args[0];\n\n if (t.isStringLiteral(firstArg)) {\n // Selector mode: tastyStatic(selector, styles)\n handleSelectorMode(\n path,\n args,\n cssWriter,\n state.sourceFile,\n config.keyframes,\n config.autoPropertyTypes,\n config.fontFace,\n config.counterStyle,\n );\n } else if (t.isObjectExpression(firstArg)) {\n // Styles mode: tastyStatic(styles)\n handleStylesMode(\n path,\n args,\n cssWriter,\n state,\n globalRegistry,\n config.keyframes,\n config.autoPropertyTypes,\n config.fontFace,\n config.counterStyle,\n );\n } else if (t.isIdentifier(firstArg)) {\n // Extension mode: tastyStatic(base, styles)\n handleExtensionMode(\n path,\n args,\n cssWriter,\n state,\n globalRegistry,\n config.keyframes,\n config.autoPropertyTypes,\n config.fontFace,\n config.counterStyle,\n );\n } else {\n throw path.buildCodeFrameError(\n 'tastyStatic() first argument must be an object (styles), ' +\n 'identifier (base StaticStyle), or string (selector)',\n );\n }\n },\n\n // Track variable declarations to register StaticStyle objects\n VariableDeclarator(\n path: NodePath<t.VariableDeclarator>,\n state: PluginState,\n ) {\n const init = path.node.init;\n const id = path.node.id;\n\n // Check if this is a StaticStyle object (has className and styles properties)\n if (\n t.isIdentifier(id) &&\n t.isObjectExpression(init) &&\n isStaticStyleObject(init)\n ) {\n const variableName = id.name;\n const styles = extractStylesFromStaticStyleObject(init, path);\n const className = extractClassNameFromStaticStyleObject(init);\n\n if (styles && className) {\n state.staticStyleRegistry[variableName] = { styles, className };\n globalRegistry[variableName] = { styles, className };\n }\n }\n },\n },\n\n post(this: PluginState) {\n // Only write when this file contributed CSS (had tastyStatic calls).\n // In Turbopack, separate workers each have their own CSSWriter with\n // only token CSS. Letting those workers write would overwrite the\n // complete CSS produced by the worker that processed tastyStatic files.\n if (this._fileAddedCSS && cssWriter.size > 0) {\n cssWriter.write();\n }\n },\n };\n});\n\n/**\n * Check if an object expression looks like a StaticStyle object\n */\nfunction isStaticStyleObject(node: t.ObjectExpression): boolean {\n const hasClassName = node.properties.some(\n (p) =>\n t.isObjectProperty(p) && t.isIdentifier(p.key, { name: 'className' }),\n );\n const hasStyles = node.properties.some(\n (p) => t.isObjectProperty(p) && t.isIdentifier(p.key, { name: 'styles' }),\n );\n return hasClassName && hasStyles;\n}\n\n/**\n * Extract styles object from a StaticStyle object expression\n */\nfunction extractStylesFromStaticStyleObject(\n node: t.ObjectExpression,\n path: NodePath,\n): Styles | null {\n for (const prop of node.properties) {\n if (\n t.isObjectProperty(prop) &&\n t.isIdentifier(prop.key, { name: 'styles' }) &&\n t.isObjectExpression(prop.value)\n ) {\n return evaluateObjectExpression(prop.value, path) as Styles;\n }\n }\n return null;\n}\n\n/**\n * Extract className from a StaticStyle object expression\n */\nfunction extractClassNameFromStaticStyleObject(\n node: t.ObjectExpression,\n): string | null {\n for (const prop of node.properties) {\n if (\n t.isObjectProperty(prop) &&\n t.isIdentifier(prop.key, { name: 'className' }) &&\n t.isStringLiteral(prop.value)\n ) {\n return prop.value.value;\n }\n }\n return null;\n}\n\n/**\n * Handle tastyStatic(styles) - returns StaticStyle object\n */\nfunction handleStylesMode(\n path: NodePath<t.CallExpression>,\n args: t.CallExpression['arguments'],\n cssWriter: CSSWriter,\n state: PluginState,\n globalRegistry: StaticStyleRegistry,\n globalKeyframes?: Record<string, KeyframesSteps>,\n autoPropertyTypes?: boolean,\n globalFontFace?: Record<string, FontFaceInput>,\n globalCounterStyle?: Record<string, CounterStyleDescriptors>,\n): void {\n const stylesArg = args[0];\n\n if (!t.isObjectExpression(stylesArg)) {\n throw path.buildCodeFrameError(\n 'tastyStatic(styles) argument must be a static object literal',\n );\n }\n\n // Evaluate styles object at build time\n const rawStyles = evaluateObjectExpression(stylesArg, path) as Styles;\n\n // Resolve recipes before extraction\n const styles = resolveRecipes(rawStyles);\n\n // Extract keyframes (deduplicated by content)\n const { keyframes, nameMap } = extractKeyframesFromStyles(\n styles,\n globalKeyframes,\n );\n\n // Add keyframes CSS\n for (const kf of keyframes) {\n cssWriter.add(kf.css, kf.css, state.sourceFile);\n }\n\n // Extract and add auto-inferred @property rules\n const properties = extractPropertiesFromStyles(styles, { autoPropertyTypes });\n for (const prop of properties) {\n cssWriter.add(prop.css, prop.css, state.sourceFile);\n }\n\n // Extract and add @font-face rules\n for (const ff of extractFontFaceFromStyles(styles, globalFontFace)) {\n cssWriter.add(ff.css, ff.css, state.sourceFile);\n }\n\n // Extract and add @counter-style rules\n for (const cs of extractCounterStyleFromStyles(styles, globalCounterStyle)) {\n cssWriter.add(cs.css, cs.css, state.sourceFile);\n }\n\n // Extract styles with chunking\n const chunks = extractStylesWithChunks(styles);\n\n // Add CSS to writer, replacing animation names if needed\n for (const chunk of chunks) {\n const css =\n nameMap.size > 0\n ? replaceAnimationNamesInCSS(chunk.css, nameMap)\n : chunk.css;\n cssWriter.add(chunk.className, css, state.sourceFile);\n }\n\n // Generate className\n const className =\n chunks.length > 0 ? chunks.map((c) => c.className).join(' ') : '';\n\n // Replace call with StaticStyle object\n const staticStyleObject = createStaticStyleAST(className, styles);\n path.replaceWith(staticStyleObject);\n\n // Register if this is being assigned to a variable\n registerIfVariableDeclaration(path, className, styles, state, globalRegistry);\n}\n\n/**\n * Handle tastyStatic(base, styles) - extends base with additional styles\n */\nfunction handleExtensionMode(\n path: NodePath<t.CallExpression>,\n args: t.CallExpression['arguments'],\n cssWriter: CSSWriter,\n state: PluginState,\n globalRegistry: StaticStyleRegistry,\n globalKeyframes?: Record<string, KeyframesSteps>,\n autoPropertyTypes?: boolean,\n globalFontFace?: Record<string, FontFaceInput>,\n globalCounterStyle?: Record<string, CounterStyleDescriptors>,\n): void {\n if (args.length < 2) {\n throw path.buildCodeFrameError(\n 'tastyStatic(base, styles) requires two arguments',\n );\n }\n\n const baseArg = args[0];\n const stylesArg = args[1];\n\n if (!t.isIdentifier(baseArg)) {\n throw path.buildCodeFrameError(\n 'tastyStatic(base, styles) first argument must be an identifier',\n );\n }\n\n if (!t.isObjectExpression(stylesArg)) {\n throw path.buildCodeFrameError(\n 'tastyStatic(base, styles) second argument must be a static object literal',\n );\n }\n\n const baseName = baseArg.name;\n\n // Look up base styles in registry\n const baseEntry =\n state.staticStyleRegistry[baseName] || globalRegistry[baseName];\n\n if (!baseEntry) {\n throw path.buildCodeFrameError(\n `Cannot find base StaticStyle '${baseName}'. ` +\n 'Make sure it is defined before being extended.',\n );\n }\n\n // Evaluate override styles\n const overrideStyles = evaluateObjectExpression(stylesArg, path) as Styles;\n\n // Merge styles using mergeStyles, then resolve recipes\n const mergedStyles = resolveRecipes(\n mergeStyles(baseEntry.styles, overrideStyles),\n );\n\n // Extract keyframes (deduplicated by content)\n const { keyframes, nameMap } = extractKeyframesFromStyles(\n mergedStyles,\n globalKeyframes,\n );\n\n // Add keyframes CSS\n for (const kf of keyframes) {\n cssWriter.add(kf.css, kf.css, state.sourceFile);\n }\n\n // Extract and add auto-inferred @property rules\n const properties = extractPropertiesFromStyles(mergedStyles, {\n autoPropertyTypes,\n });\n for (const prop of properties) {\n cssWriter.add(prop.css, prop.css, state.sourceFile);\n }\n\n // Extract and add @font-face rules\n for (const ff of extractFontFaceFromStyles(mergedStyles, globalFontFace)) {\n cssWriter.add(ff.css, ff.css, state.sourceFile);\n }\n\n // Extract and add @counter-style rules\n for (const cs of extractCounterStyleFromStyles(\n mergedStyles,\n globalCounterStyle,\n )) {\n cssWriter.add(cs.css, cs.css, state.sourceFile);\n }\n\n // Extract styles with chunking\n const chunks = extractStylesWithChunks(mergedStyles);\n\n // Add CSS to writer, replacing animation names if needed\n for (const chunk of chunks) {\n const css =\n nameMap.size > 0\n ? replaceAnimationNamesInCSS(chunk.css, nameMap)\n : chunk.css;\n cssWriter.add(chunk.className, css, state.sourceFile);\n }\n\n // Generate className\n const className =\n chunks.length > 0 ? chunks.map((c) => c.className).join(' ') : '';\n\n // Replace call with StaticStyle object\n const staticStyleObject = createStaticStyleAST(className, mergedStyles);\n path.replaceWith(staticStyleObject);\n\n // Register if this is being assigned to a variable\n registerIfVariableDeclaration(\n path,\n className,\n mergedStyles,\n state,\n globalRegistry,\n );\n}\n\n/**\n * Handle tastyStatic(selector, styles) - removes the call entirely\n */\nfunction handleSelectorMode(\n path: NodePath<t.CallExpression>,\n args: t.CallExpression['arguments'],\n cssWriter: CSSWriter,\n sourceFile?: string,\n globalKeyframes?: Record<string, KeyframesSteps>,\n autoPropertyTypes?: boolean,\n globalFontFace?: Record<string, FontFaceInput>,\n globalCounterStyle?: Record<string, CounterStyleDescriptors>,\n): void {\n if (args.length < 2) {\n throw path.buildCodeFrameError(\n 'tastyStatic(selector, styles) requires two arguments',\n );\n }\n\n const selectorArg = args[0];\n const stylesArg = args[1];\n\n if (!t.isStringLiteral(selectorArg)) {\n throw path.buildCodeFrameError(\n 'tastyStatic(selector, styles) first argument must be a string literal',\n );\n }\n\n if (!t.isObjectExpression(stylesArg)) {\n throw path.buildCodeFrameError(\n 'tastyStatic(selector, styles) second argument must be a static object literal',\n );\n }\n\n const selector = selectorArg.value;\n const rawStyles = evaluateObjectExpression(stylesArg, path) as Styles;\n\n // Resolve recipes before extraction\n const styles = resolveRecipes(rawStyles);\n\n // Extract keyframes (deduplicated by content)\n const { keyframes, nameMap } = extractKeyframesFromStyles(\n styles,\n globalKeyframes,\n );\n\n // Add keyframes CSS\n for (const kf of keyframes) {\n cssWriter.add(kf.css, kf.css, sourceFile);\n }\n\n // Extract and add auto-inferred @property rules\n const properties = extractPropertiesFromStyles(styles, { autoPropertyTypes });\n for (const prop of properties) {\n cssWriter.add(prop.css, prop.css, sourceFile);\n }\n\n // Extract and add @font-face rules\n for (const ff of extractFontFaceFromStyles(styles, globalFontFace)) {\n cssWriter.add(ff.css, ff.css, sourceFile);\n }\n\n // Extract and add @counter-style rules\n for (const cs of extractCounterStyleFromStyles(styles, globalCounterStyle)) {\n cssWriter.add(cs.css, cs.css, sourceFile);\n }\n\n // Extract styles for selector\n const result = extractStylesForSelector(selector, styles);\n\n // Replace animation names if needed and add CSS\n const css =\n nameMap.size > 0\n ? replaceAnimationNamesInCSS(result.css, nameMap)\n : result.css;\n cssWriter.add(selector, css, sourceFile);\n\n // Remove the entire statement\n const parent = path.parentPath;\n if (parent && t.isExpressionStatement(parent.node)) {\n parent.remove();\n } else {\n // If used in an expression context (which would be incorrect usage),\n // replace with undefined\n path.replaceWith(t.identifier('undefined'));\n }\n}\n\n/**\n * Create a StaticStyle object AST node\n */\nfunction createStaticStyleAST(\n className: string,\n styles: Styles,\n): t.ObjectExpression {\n return t.objectExpression([\n t.objectProperty(t.identifier('className'), t.stringLiteral(className)),\n t.objectProperty(t.identifier('styles'), valueToAST(styles)),\n t.objectMethod(\n 'method',\n t.identifier('toString'),\n [],\n t.blockStatement([\n t.returnStatement(\n t.memberExpression(t.thisExpression(), t.identifier('className')),\n ),\n ]),\n ),\n ]);\n}\n\n/**\n * Register a StaticStyle in the registry if it's being assigned to a variable\n */\nfunction registerIfVariableDeclaration(\n path: NodePath,\n className: string,\n styles: Styles,\n state: PluginState,\n globalRegistry: StaticStyleRegistry,\n): void {\n const parent = path.parentPath;\n if (parent && t.isVariableDeclarator(parent.node)) {\n const id = parent.node.id;\n if (t.isIdentifier(id)) {\n const variableName = id.name;\n state.staticStyleRegistry[variableName] = { styles, className };\n globalRegistry[variableName] = { styles, className };\n }\n }\n}\n\n/**\n * Convert a JavaScript value to an AST node\n */\nfunction valueToAST(value: unknown): t.Expression {\n if (value === null) {\n return t.nullLiteral();\n }\n if (value === undefined) {\n return t.identifier('undefined');\n }\n if (typeof value === 'string') {\n return t.stringLiteral(value);\n }\n if (typeof value === 'number') {\n return t.numericLiteral(value);\n }\n if (typeof value === 'boolean') {\n return t.booleanLiteral(value);\n }\n if (Array.isArray(value)) {\n return t.arrayExpression(value.map(valueToAST));\n }\n if (typeof value === 'object') {\n const properties = Object.entries(value).map(([key, val]) =>\n t.objectProperty(\n /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key)\n ? t.identifier(key)\n : t.stringLiteral(key),\n valueToAST(val),\n ),\n );\n return t.objectExpression(properties);\n }\n // Fallback for unsupported types\n return t.identifier('undefined');\n}\n\n/**\n * Evaluate an ObjectExpression to a plain JavaScript object.\n * Only supports static values that can be determined at build time.\n */\nfunction evaluateObjectExpression(\n node: t.ObjectExpression,\n path: NodePath,\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n\n for (const prop of node.properties) {\n if (t.isSpreadElement(prop)) {\n throw path.buildCodeFrameError(\n 'Spread elements are not supported in tastyStatic() - styles must be fully static',\n );\n }\n\n if (!t.isObjectProperty(prop)) {\n throw path.buildCodeFrameError(\n 'Only object properties are supported in tastyStatic()',\n );\n }\n\n // Get key\n let key: string;\n if (t.isIdentifier(prop.key)) {\n key = prop.key.name;\n } else if (t.isStringLiteral(prop.key)) {\n key = prop.key.value;\n } else {\n throw path.buildCodeFrameError(\n 'Dynamic property keys are not supported in tastyStatic()',\n );\n }\n\n // Get value\n const value = evaluateExpression(prop.value, path);\n result[key] = value;\n }\n\n return result;\n}\n\n/**\n * Evaluate an expression to a JavaScript value.\n */\nfunction evaluateExpression(node: t.Node, path: NodePath): unknown {\n if (t.isStringLiteral(node)) {\n return node.value;\n }\n\n if (t.isNumericLiteral(node)) {\n return node.value;\n }\n\n if (t.isBooleanLiteral(node)) {\n return node.value;\n }\n\n if (t.isNullLiteral(node)) {\n return null;\n }\n\n if (t.isIdentifier(node, { name: 'undefined' })) {\n return undefined;\n }\n\n if (t.isArrayExpression(node)) {\n return node.elements.map((el) => {\n if (el === null) return null;\n if (t.isSpreadElement(el)) {\n throw path.buildCodeFrameError(\n 'Spread elements are not supported in tastyStatic()',\n );\n }\n return evaluateExpression(el, path);\n });\n }\n\n if (t.isObjectExpression(node)) {\n return evaluateObjectExpression(node, path);\n }\n\n if (t.isTemplateLiteral(node)) {\n // Only support template literals without expressions\n if (node.expressions.length > 0) {\n throw path.buildCodeFrameError(\n 'Template literals with expressions are not supported in tastyStatic()',\n );\n }\n return node.quasis.map((q) => q.value.cooked).join('');\n }\n\n if (t.isUnaryExpression(node, { operator: '-' })) {\n const arg = evaluateExpression(node.argument, path);\n if (typeof arg === 'number') {\n return -arg;\n }\n }\n\n throw path.buildCodeFrameError(\n `Dynamic expressions are not supported in tastyStatic() - got ${node.type}. ` +\n 'All values must be static literals.',\n );\n}\n\n/**\n * Replace animation names in CSS string.\n * Wraps the keyframes replaceAnimationNames to work on full CSS blocks.\n */\nfunction replaceAnimationNamesInCSS(\n css: string,\n nameMap: Map<string, string>,\n): string {\n if (nameMap.size === 0) return css;\n\n // The CSS contains full rules like \".class { animation: name 1s; }\"\n // We need to replace animation names within declaration blocks\n return css.replace(\n /(animation(?:-name)?)\\s*:\\s*([^;}]+)/gi,\n (match, prop, value) => {\n let newValue = value;\n for (const [original, replacement] of nameMap) {\n // Word boundary replacement\n const pattern = new RegExp(`\\\\b${escapeRegex(original)}\\\\b`, 'g');\n newValue = newValue.replace(pattern, replacement);\n }\n return `${prop}: ${newValue}`;\n },\n );\n}\n\n/**\n * Escape special regex characters.\n */\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n"],"mappings":"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"}
|
package/dist/zero/extractor.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { extractLocalProperties, hasLocalProperties } from "../properties/index.js";
|
|
2
2
|
import { PropertyTypeResolver } from "../properties/property-type-resolver.js";
|
|
3
|
+
import { extractLocalFontFace, formatFontFaceRule, hasLocalFontFace } from "../font-face/index.js";
|
|
4
|
+
import { extractLocalCounterStyle, formatCounterStyleRule, hasLocalCounterStyle } from "../counter-style/index.js";
|
|
3
5
|
import { renderStyles } from "../pipeline/index.js";
|
|
4
6
|
import { categorizeStyleKeys } from "../chunks/definitions.js";
|
|
5
7
|
import { generateChunkCacheKey } from "../chunks/cacheKey.js";
|
|
@@ -209,7 +211,57 @@ function scanKeyframeSteps(steps, resolver, isPropertyDefined, registerProperty)
|
|
|
209
211
|
resolver.scanDeclarations(declarations, isPropertyDefined, registerProperty);
|
|
210
212
|
}
|
|
211
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* Extract @font-face rules from styles, merging with global config.
|
|
216
|
+
* Deduplicates by content hash.
|
|
217
|
+
*/
|
|
218
|
+
function extractFontFaceFromStyles(styles, globalFontFace) {
|
|
219
|
+
const results = [];
|
|
220
|
+
const seenHashes = /* @__PURE__ */ new Set();
|
|
221
|
+
function addFontFace(family, input) {
|
|
222
|
+
const descriptors = Array.isArray(input) ? input : [input];
|
|
223
|
+
for (const desc of descriptors) {
|
|
224
|
+
const hash = createHash("md5").update(JSON.stringify({
|
|
225
|
+
family,
|
|
226
|
+
...desc
|
|
227
|
+
})).digest("hex").slice(0, 8);
|
|
228
|
+
if (!seenHashes.has(hash)) {
|
|
229
|
+
seenHashes.add(hash);
|
|
230
|
+
results.push({ css: formatFontFaceRule(family, desc) });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (globalFontFace) for (const [family, input] of Object.entries(globalFontFace)) addFontFace(family, input);
|
|
235
|
+
if (hasLocalFontFace(styles)) {
|
|
236
|
+
const local = extractLocalFontFace(styles);
|
|
237
|
+
if (local) for (const [family, input] of Object.entries(local)) addFontFace(family, input);
|
|
238
|
+
}
|
|
239
|
+
return results;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Extract @counter-style rules from styles, merging with global config.
|
|
243
|
+
* Deduplicates by name (first definition wins).
|
|
244
|
+
*/
|
|
245
|
+
function extractCounterStyleFromStyles(styles, globalCounterStyle) {
|
|
246
|
+
const results = [];
|
|
247
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
248
|
+
function addCounterStyle(name, descriptors) {
|
|
249
|
+
if (!seenNames.has(name)) {
|
|
250
|
+
seenNames.add(name);
|
|
251
|
+
results.push({
|
|
252
|
+
name,
|
|
253
|
+
css: formatCounterStyleRule(name, descriptors)
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (globalCounterStyle) for (const [name, descriptors] of Object.entries(globalCounterStyle)) addCounterStyle(name, descriptors);
|
|
258
|
+
if (hasLocalCounterStyle(styles)) {
|
|
259
|
+
const local = extractLocalCounterStyle(styles);
|
|
260
|
+
if (local) for (const [name, descriptors] of Object.entries(local)) addCounterStyle(name, descriptors);
|
|
261
|
+
}
|
|
262
|
+
return results;
|
|
263
|
+
}
|
|
212
264
|
|
|
213
265
|
//#endregion
|
|
214
|
-
export { extractKeyframesFromStyles, extractPropertiesFromStyles, extractStylesForSelector, extractStylesWithChunks };
|
|
266
|
+
export { extractCounterStyleFromStyles, extractFontFaceFromStyles, extractKeyframesFromStyles, extractPropertiesFromStyles, extractStylesForSelector, extractStylesWithChunks };
|
|
215
267
|
//# sourceMappingURL=extractor.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extractor.js","names":[],"sources":["../../src/zero/extractor.ts"],"sourcesContent":["import { createHash } from 'crypto';\n\nimport {\n categorizeStyleKeys,\n generateChunkCacheKey,\n renderStylesForChunk,\n} from '../chunks';\nimport type { KeyframesSteps } from '../injector/types';\nimport {\n extractAnimationNamesFromStyles,\n extractLocalKeyframes,\n filterUsedKeyframes,\n hasLocalKeyframes,\n mergeKeyframes,\n} from '../keyframes';\nimport type { StyleResult } from '../pipeline';\nimport { renderStyles } from '../pipeline';\nimport { extractLocalProperties, hasLocalProperties } from '../properties';\nimport { PropertyTypeResolver } from '../properties/property-type-resolver';\nimport type { Styles } from '../styles/types';\n\nexport interface ExtractedChunk {\n className: string;\n css: string;\n}\n\nexport interface ExtractedSelector {\n selector: string;\n css: string;\n}\n\nexport interface ExtractedKeyframes {\n name: string;\n css: string;\n}\n\nexport interface KeyframesExtractionResult {\n /** Keyframes to inject (deduplicated by content) */\n keyframes: ExtractedKeyframes[];\n /** Map from original animation name to canonical name (for replacement) */\n nameMap: Map<string, string>;\n}\n\n/**\n * Generate a deterministic className from a cache key using content hash.\n * This ensures the same styles always produce the same className,\n * regardless of build order or incremental compilation.\n */\nfunction generateClassName(cacheKey: string): string {\n const hash = createHash('md5').update(cacheKey).digest('hex').slice(0, 6);\n return `ts${hash}`; // 'ts' prefix for \"tasty-static\" to distinguish from runtime 't' classes\n}\n\n/**\n * Extract styles using chunking (for className mode).\n * Returns multiple classes, one per chunk.\n */\nexport function extractStylesWithChunks(styles: Styles): ExtractedChunk[] {\n const chunks: ExtractedChunk[] = [];\n\n // Categorize style keys into chunks\n const chunkMap = categorizeStyleKeys(styles as Record<string, unknown>);\n\n for (const [chunkName, chunkStyleKeys] of chunkMap) {\n if (chunkStyleKeys.length === 0) continue;\n\n // Generate cache key for this chunk (used for className hash)\n const cacheKey = generateChunkCacheKey(styles, chunkName, chunkStyleKeys);\n\n // Render styles for this chunk\n const renderResult = renderStylesForChunk(\n styles,\n chunkName,\n chunkStyleKeys,\n );\n\n if (renderResult.rules.length === 0) continue;\n\n // Generate deterministic className from content hash\n const className = generateClassName(cacheKey);\n const selector = `.${className}.${className}`;\n\n // Format CSS\n const css = formatRulesToCSS(renderResult.rules, selector);\n\n chunks.push({ className, css });\n }\n\n return chunks;\n}\n\n/**\n * Extract styles for a specific selector (for global/selector mode).\n * Returns a single CSS block.\n */\nexport function extractStylesForSelector(\n selector: string,\n styles: Styles,\n): ExtractedSelector {\n // renderStyles with selector returns StyleResult[] with selectors already applied\n const rules = renderStyles(styles, selector);\n // Format without re-prefixing - rules already have the full selector\n const css = formatRulesDirectly(rules);\n\n return { selector, css };\n}\n\n/**\n * Format StyleResult[] to CSS string.\n * Prefixes each rule's selector with the base selector.\n * Used for chunked styles where rules have relative selectors.\n */\nfunction formatRulesToCSS(rules: StyleResult[], baseSelector: string): string {\n return rules\n .map((rule) => {\n // Handle selector as array (OR conditions) or string\n // Note: renderStyles without className joins array selectors with '|||' placeholder\n const selectorParts = Array.isArray(rule.selector)\n ? rule.selector\n : rule.selector\n ? rule.selector.split('|||')\n : [''];\n\n // Prefix each selector part with the base selector\n const fullSelector = selectorParts\n .map((part) => {\n // Build selector: [rootPrefix] baseSelector[part]\n let selector: string;\n\n // If part is empty, just use base selector\n if (!part) {\n selector = baseSelector;\n } else if (part.startsWith(':') || part.startsWith('[')) {\n // If part starts with a pseudo-class or pseudo-element, append to base\n selector = `${baseSelector}${part}`;\n } else if (\n part.startsWith('>') ||\n part.startsWith('+') ||\n part.startsWith('~')\n ) {\n // If part starts with >, +, ~ combinator, append with space\n selector = `${baseSelector}${part}`;\n } else {\n // Otherwise, combine base with part\n selector = `${baseSelector}${part}`;\n }\n\n // Prepend rootPrefix if present (for @root() states)\n if (rule.rootPrefix) {\n selector = `${rule.rootPrefix} ${selector}`;\n }\n\n return selector;\n })\n .join(', ');\n\n let css = `${fullSelector} { ${rule.declarations} }`;\n\n // Wrap in at-rules (in reverse order for proper nesting)\n if (rule.atRules && rule.atRules.length > 0) {\n for (const atRule of [...rule.atRules].reverse()) {\n css = `${atRule} {\\n ${css}\\n}`;\n }\n }\n\n return css;\n })\n .join('\\n\\n');\n}\n\n/**\n * Format StyleResult[] to CSS string directly without prefixing.\n * Used for global styles where rules already have the full selector.\n */\nfunction formatRulesDirectly(rules: StyleResult[]): string {\n return rules\n .map((rule) => {\n // Prepend rootPrefix if present (for @root() states)\n const selector = rule.rootPrefix\n ? `${rule.rootPrefix} ${rule.selector}`\n : rule.selector;\n\n let css = `${selector} { ${rule.declarations} }`;\n\n // Wrap in at-rules (in reverse order for proper nesting)\n if (rule.atRules && rule.atRules.length > 0) {\n for (const atRule of [...rule.atRules].reverse()) {\n css = `${atRule} {\\n ${css}\\n}`;\n }\n }\n\n return css;\n })\n .join('\\n\\n');\n}\n\n// Note: With hash-based className generation, counter management functions\n// are no longer needed. ClassNames are deterministic based on content.\n\n/**\n * Generate a deterministic keyframes name from content hash.\n * This ensures the same keyframes content always produces the same name,\n * enabling automatic deduplication across elements and files.\n */\nfunction generateKeyframesName(steps: KeyframesSteps): string {\n const content = JSON.stringify(steps);\n const hash = createHash('md5').update(content).digest('hex').slice(0, 6);\n return `kf${hash}`; // 'kf' prefix for \"keyframes\"\n}\n\n/**\n * Extract keyframes that are used in styles.\n * Merges local @keyframes with global keyframes, filters to only used ones.\n * Generates hash-based names from content for automatic deduplication.\n *\n * @param styles - The styles object (may contain @keyframes and animation properties)\n * @param globalKeyframes - Optional global keyframes from config\n * @returns Keyframes to inject and name mapping for replacement\n */\nexport function extractKeyframesFromStyles(\n styles: Styles,\n globalKeyframes?: Record<string, KeyframesSteps> | null,\n): KeyframesExtractionResult {\n const emptyResult: KeyframesExtractionResult = {\n keyframes: [],\n nameMap: new Map(),\n };\n\n // Extract animation names from styles\n const usedNames = extractAnimationNamesFromStyles(styles);\n if (usedNames.size === 0) return emptyResult;\n\n // Merge local and global keyframes\n const local = hasLocalKeyframes(styles)\n ? extractLocalKeyframes(styles)\n : null;\n const allKeyframes = mergeKeyframes(local, globalKeyframes ?? null);\n\n // Filter to only used keyframes\n const usedKeyframes = filterUsedKeyframes(allKeyframes, usedNames);\n if (!usedKeyframes) return emptyResult;\n\n // Generate hash-based names and collect unique keyframes\n const seenHashes = new Set<string>();\n const nameMap = new Map<string, string>();\n const keyframesToEmit: ExtractedKeyframes[] = [];\n\n for (const [originalName, steps] of Object.entries(usedKeyframes)) {\n const hashedName = generateKeyframesName(steps);\n\n // Always map original name to hashed name (for CSS replacement)\n nameMap.set(originalName, hashedName);\n\n // Only emit each unique keyframe once\n if (!seenHashes.has(hashedName)) {\n seenHashes.add(hashedName);\n const css = keyframesToCSS(hashedName, steps);\n keyframesToEmit.push({ name: hashedName, css });\n }\n }\n\n return { keyframes: keyframesToEmit, nameMap };\n}\n\n/**\n * Convert keyframes steps to CSS string.\n */\nfunction keyframesToCSS(name: string, steps: KeyframesSteps): string {\n const stepRules: string[] = [];\n\n for (const [key, value] of Object.entries(steps)) {\n if (typeof value === 'string') {\n // Raw CSS string\n stepRules.push(`${key} { ${value.trim()} }`);\n } else if (value && typeof value === 'object') {\n // Style map - convert to CSS declarations\n const declarations = Object.entries(value)\n .map(([prop, val]) => {\n const cssProperty = camelToKebab(prop);\n return `${cssProperty}: ${val}`;\n })\n .join('; ');\n stepRules.push(`${key} { ${declarations} }`);\n }\n }\n\n return `@keyframes ${name} { ${stepRules.join(' ')} }`;\n}\n\n/**\n * Convert camelCase to kebab-case.\n */\nfunction camelToKebab(str: string): string {\n return str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);\n}\n\n// ============================================================================\n// Property Extraction (auto-infer @property types for zero-runtime)\n// ============================================================================\n\nexport interface ExtractedProperty {\n name: string;\n css: string;\n}\n\n/**\n * Extract auto-inferred @property declarations from styles.\n * Scans rendered style declarations and keyframe declarations for custom properties\n * whose types can be inferred from their values.\n *\n * @param styles - The styles object\n * @param options - Options including autoPropertyTypes flag\n * @returns Array of @property CSS rules to inject\n */\nexport function extractPropertiesFromStyles(\n styles: Styles,\n options?: { autoPropertyTypes?: boolean },\n): ExtractedProperty[] {\n if (options?.autoPropertyTypes === false) return [];\n\n const registered = new Set<string>();\n const results: ExtractedProperty[] = [];\n\n // Collect explicitly declared properties (they take precedence)\n if (hasLocalProperties(styles)) {\n const localProps = extractLocalProperties(styles);\n if (localProps) {\n for (const token of Object.keys(localProps)) {\n // Normalize token to CSS name\n let cssName: string;\n if (token.startsWith('#')) {\n cssName = `--${token.slice(1)}-color`;\n } else if (token.startsWith('$')) {\n cssName = `--${token.slice(1)}`;\n } else if (token.startsWith('--')) {\n cssName = token;\n } else {\n cssName = `--${token}`;\n }\n registered.add(cssName);\n }\n }\n }\n\n const resolver = new PropertyTypeResolver();\n\n const registerProperty = (\n name: string,\n syntax: string,\n initialValue: string,\n ) => {\n if (registered.has(name)) return;\n registered.add(name);\n\n const parts: string[] = [];\n parts.push(`syntax: \"${syntax}\";`);\n parts.push(`inherits: true;`);\n parts.push(`initial-value: ${initialValue};`);\n\n const css = `@property ${name} { ${parts.join(' ')} }`;\n results.push({ name, css });\n };\n\n const isPropertyDefined = (name: string) => registered.has(name);\n\n // Scan rendered style declarations\n const chunkMap = categorizeStyleKeys(styles as Record<string, unknown>);\n for (const [chunkName, chunkStyleKeys] of chunkMap) {\n if (chunkStyleKeys.length === 0) continue;\n const renderResult = renderStylesForChunk(\n styles,\n chunkName,\n chunkStyleKeys,\n );\n for (const rule of renderResult.rules) {\n if (!rule.declarations) continue;\n resolver.scanDeclarations(\n rule.declarations,\n isPropertyDefined,\n registerProperty,\n );\n }\n }\n\n // Scan keyframe declarations\n if (hasLocalKeyframes(styles)) {\n const localKf = extractLocalKeyframes(styles);\n if (localKf) {\n for (const steps of Object.values(localKf)) {\n scanKeyframeSteps(steps, resolver, isPropertyDefined, registerProperty);\n }\n }\n }\n\n return results;\n}\n\nfunction scanKeyframeSteps(\n steps: KeyframesSteps,\n resolver: PropertyTypeResolver,\n isPropertyDefined: (name: string) => boolean,\n registerProperty: (\n name: string,\n syntax: string,\n initialValue: string,\n ) => void,\n): void {\n for (const value of Object.values(steps)) {\n if (typeof value === 'string') {\n resolver.scanDeclarations(value, isPropertyDefined, registerProperty);\n } else if (value && typeof value === 'object') {\n const declarations = Object.entries(value)\n .map(([prop, val]) => {\n const cssProperty = camelToKebab(prop);\n return `${cssProperty}: ${val}`;\n })\n .join('; ');\n resolver.scanDeclarations(\n declarations,\n isPropertyDefined,\n registerProperty,\n );\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"extractor.js","names":[],"sources":["../../src/zero/extractor.ts"],"sourcesContent":["import { createHash } from 'crypto';\n\nimport {\n categorizeStyleKeys,\n generateChunkCacheKey,\n renderStylesForChunk,\n} from '../chunks';\nimport type {\n CounterStyleDescriptors,\n FontFaceDescriptors,\n FontFaceInput,\n KeyframesSteps,\n} from '../injector/types';\nimport {\n extractLocalCounterStyle,\n formatCounterStyleRule,\n hasLocalCounterStyle,\n} from '../counter-style';\nimport {\n extractLocalFontFace,\n formatFontFaceRule,\n hasLocalFontFace,\n} from '../font-face';\nimport {\n extractAnimationNamesFromStyles,\n extractLocalKeyframes,\n filterUsedKeyframes,\n hasLocalKeyframes,\n mergeKeyframes,\n} from '../keyframes';\nimport type { StyleResult } from '../pipeline';\nimport { renderStyles } from '../pipeline';\nimport { extractLocalProperties, hasLocalProperties } from '../properties';\nimport { PropertyTypeResolver } from '../properties/property-type-resolver';\nimport type { Styles } from '../styles/types';\n\nexport interface ExtractedChunk {\n className: string;\n css: string;\n}\n\nexport interface ExtractedSelector {\n selector: string;\n css: string;\n}\n\nexport interface ExtractedKeyframes {\n name: string;\n css: string;\n}\n\nexport interface KeyframesExtractionResult {\n /** Keyframes to inject (deduplicated by content) */\n keyframes: ExtractedKeyframes[];\n /** Map from original animation name to canonical name (for replacement) */\n nameMap: Map<string, string>;\n}\n\n/**\n * Generate a deterministic className from a cache key using content hash.\n * This ensures the same styles always produce the same className,\n * regardless of build order or incremental compilation.\n */\nfunction generateClassName(cacheKey: string): string {\n const hash = createHash('md5').update(cacheKey).digest('hex').slice(0, 6);\n return `ts${hash}`; // 'ts' prefix for \"tasty-static\" to distinguish from runtime 't' classes\n}\n\n/**\n * Extract styles using chunking (for className mode).\n * Returns multiple classes, one per chunk.\n */\nexport function extractStylesWithChunks(styles: Styles): ExtractedChunk[] {\n const chunks: ExtractedChunk[] = [];\n\n // Categorize style keys into chunks\n const chunkMap = categorizeStyleKeys(styles as Record<string, unknown>);\n\n for (const [chunkName, chunkStyleKeys] of chunkMap) {\n if (chunkStyleKeys.length === 0) continue;\n\n // Generate cache key for this chunk (used for className hash)\n const cacheKey = generateChunkCacheKey(styles, chunkName, chunkStyleKeys);\n\n // Render styles for this chunk\n const renderResult = renderStylesForChunk(\n styles,\n chunkName,\n chunkStyleKeys,\n );\n\n if (renderResult.rules.length === 0) continue;\n\n // Generate deterministic className from content hash\n const className = generateClassName(cacheKey);\n const selector = `.${className}.${className}`;\n\n // Format CSS\n const css = formatRulesToCSS(renderResult.rules, selector);\n\n chunks.push({ className, css });\n }\n\n return chunks;\n}\n\n/**\n * Extract styles for a specific selector (for global/selector mode).\n * Returns a single CSS block.\n */\nexport function extractStylesForSelector(\n selector: string,\n styles: Styles,\n): ExtractedSelector {\n // renderStyles with selector returns StyleResult[] with selectors already applied\n const rules = renderStyles(styles, selector);\n // Format without re-prefixing - rules already have the full selector\n const css = formatRulesDirectly(rules);\n\n return { selector, css };\n}\n\n/**\n * Format StyleResult[] to CSS string.\n * Prefixes each rule's selector with the base selector.\n * Used for chunked styles where rules have relative selectors.\n */\nfunction formatRulesToCSS(rules: StyleResult[], baseSelector: string): string {\n return rules\n .map((rule) => {\n // Handle selector as array (OR conditions) or string\n // Note: renderStyles without className joins array selectors with '|||' placeholder\n const selectorParts = Array.isArray(rule.selector)\n ? rule.selector\n : rule.selector\n ? rule.selector.split('|||')\n : [''];\n\n // Prefix each selector part with the base selector\n const fullSelector = selectorParts\n .map((part) => {\n // Build selector: [rootPrefix] baseSelector[part]\n let selector: string;\n\n // If part is empty, just use base selector\n if (!part) {\n selector = baseSelector;\n } else if (part.startsWith(':') || part.startsWith('[')) {\n // If part starts with a pseudo-class or pseudo-element, append to base\n selector = `${baseSelector}${part}`;\n } else if (\n part.startsWith('>') ||\n part.startsWith('+') ||\n part.startsWith('~')\n ) {\n // If part starts with >, +, ~ combinator, append with space\n selector = `${baseSelector}${part}`;\n } else {\n // Otherwise, combine base with part\n selector = `${baseSelector}${part}`;\n }\n\n // Prepend rootPrefix if present (for @root() states)\n if (rule.rootPrefix) {\n selector = `${rule.rootPrefix} ${selector}`;\n }\n\n return selector;\n })\n .join(', ');\n\n let css = `${fullSelector} { ${rule.declarations} }`;\n\n // Wrap in at-rules (in reverse order for proper nesting)\n if (rule.atRules && rule.atRules.length > 0) {\n for (const atRule of [...rule.atRules].reverse()) {\n css = `${atRule} {\\n ${css}\\n}`;\n }\n }\n\n return css;\n })\n .join('\\n\\n');\n}\n\n/**\n * Format StyleResult[] to CSS string directly without prefixing.\n * Used for global styles where rules already have the full selector.\n */\nfunction formatRulesDirectly(rules: StyleResult[]): string {\n return rules\n .map((rule) => {\n // Prepend rootPrefix if present (for @root() states)\n const selector = rule.rootPrefix\n ? `${rule.rootPrefix} ${rule.selector}`\n : rule.selector;\n\n let css = `${selector} { ${rule.declarations} }`;\n\n // Wrap in at-rules (in reverse order for proper nesting)\n if (rule.atRules && rule.atRules.length > 0) {\n for (const atRule of [...rule.atRules].reverse()) {\n css = `${atRule} {\\n ${css}\\n}`;\n }\n }\n\n return css;\n })\n .join('\\n\\n');\n}\n\n// Note: With hash-based className generation, counter management functions\n// are no longer needed. ClassNames are deterministic based on content.\n\n/**\n * Generate a deterministic keyframes name from content hash.\n * This ensures the same keyframes content always produces the same name,\n * enabling automatic deduplication across elements and files.\n */\nfunction generateKeyframesName(steps: KeyframesSteps): string {\n const content = JSON.stringify(steps);\n const hash = createHash('md5').update(content).digest('hex').slice(0, 6);\n return `kf${hash}`; // 'kf' prefix for \"keyframes\"\n}\n\n/**\n * Extract keyframes that are used in styles.\n * Merges local @keyframes with global keyframes, filters to only used ones.\n * Generates hash-based names from content for automatic deduplication.\n *\n * @param styles - The styles object (may contain @keyframes and animation properties)\n * @param globalKeyframes - Optional global keyframes from config\n * @returns Keyframes to inject and name mapping for replacement\n */\nexport function extractKeyframesFromStyles(\n styles: Styles,\n globalKeyframes?: Record<string, KeyframesSteps> | null,\n): KeyframesExtractionResult {\n const emptyResult: KeyframesExtractionResult = {\n keyframes: [],\n nameMap: new Map(),\n };\n\n // Extract animation names from styles\n const usedNames = extractAnimationNamesFromStyles(styles);\n if (usedNames.size === 0) return emptyResult;\n\n // Merge local and global keyframes\n const local = hasLocalKeyframes(styles)\n ? extractLocalKeyframes(styles)\n : null;\n const allKeyframes = mergeKeyframes(local, globalKeyframes ?? null);\n\n // Filter to only used keyframes\n const usedKeyframes = filterUsedKeyframes(allKeyframes, usedNames);\n if (!usedKeyframes) return emptyResult;\n\n // Generate hash-based names and collect unique keyframes\n const seenHashes = new Set<string>();\n const nameMap = new Map<string, string>();\n const keyframesToEmit: ExtractedKeyframes[] = [];\n\n for (const [originalName, steps] of Object.entries(usedKeyframes)) {\n const hashedName = generateKeyframesName(steps);\n\n // Always map original name to hashed name (for CSS replacement)\n nameMap.set(originalName, hashedName);\n\n // Only emit each unique keyframe once\n if (!seenHashes.has(hashedName)) {\n seenHashes.add(hashedName);\n const css = keyframesToCSS(hashedName, steps);\n keyframesToEmit.push({ name: hashedName, css });\n }\n }\n\n return { keyframes: keyframesToEmit, nameMap };\n}\n\n/**\n * Convert keyframes steps to CSS string.\n */\nfunction keyframesToCSS(name: string, steps: KeyframesSteps): string {\n const stepRules: string[] = [];\n\n for (const [key, value] of Object.entries(steps)) {\n if (typeof value === 'string') {\n // Raw CSS string\n stepRules.push(`${key} { ${value.trim()} }`);\n } else if (value && typeof value === 'object') {\n // Style map - convert to CSS declarations\n const declarations = Object.entries(value)\n .map(([prop, val]) => {\n const cssProperty = camelToKebab(prop);\n return `${cssProperty}: ${val}`;\n })\n .join('; ');\n stepRules.push(`${key} { ${declarations} }`);\n }\n }\n\n return `@keyframes ${name} { ${stepRules.join(' ')} }`;\n}\n\n/**\n * Convert camelCase to kebab-case.\n */\nfunction camelToKebab(str: string): string {\n return str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);\n}\n\n// ============================================================================\n// Property Extraction (auto-infer @property types for zero-runtime)\n// ============================================================================\n\nexport interface ExtractedProperty {\n name: string;\n css: string;\n}\n\n/**\n * Extract auto-inferred @property declarations from styles.\n * Scans rendered style declarations and keyframe declarations for custom properties\n * whose types can be inferred from their values.\n *\n * @param styles - The styles object\n * @param options - Options including autoPropertyTypes flag\n * @returns Array of @property CSS rules to inject\n */\nexport function extractPropertiesFromStyles(\n styles: Styles,\n options?: { autoPropertyTypes?: boolean },\n): ExtractedProperty[] {\n if (options?.autoPropertyTypes === false) return [];\n\n const registered = new Set<string>();\n const results: ExtractedProperty[] = [];\n\n // Collect explicitly declared properties (they take precedence)\n if (hasLocalProperties(styles)) {\n const localProps = extractLocalProperties(styles);\n if (localProps) {\n for (const token of Object.keys(localProps)) {\n // Normalize token to CSS name\n let cssName: string;\n if (token.startsWith('#')) {\n cssName = `--${token.slice(1)}-color`;\n } else if (token.startsWith('$')) {\n cssName = `--${token.slice(1)}`;\n } else if (token.startsWith('--')) {\n cssName = token;\n } else {\n cssName = `--${token}`;\n }\n registered.add(cssName);\n }\n }\n }\n\n const resolver = new PropertyTypeResolver();\n\n const registerProperty = (\n name: string,\n syntax: string,\n initialValue: string,\n ) => {\n if (registered.has(name)) return;\n registered.add(name);\n\n const parts: string[] = [];\n parts.push(`syntax: \"${syntax}\";`);\n parts.push(`inherits: true;`);\n parts.push(`initial-value: ${initialValue};`);\n\n const css = `@property ${name} { ${parts.join(' ')} }`;\n results.push({ name, css });\n };\n\n const isPropertyDefined = (name: string) => registered.has(name);\n\n // Scan rendered style declarations\n const chunkMap = categorizeStyleKeys(styles as Record<string, unknown>);\n for (const [chunkName, chunkStyleKeys] of chunkMap) {\n if (chunkStyleKeys.length === 0) continue;\n const renderResult = renderStylesForChunk(\n styles,\n chunkName,\n chunkStyleKeys,\n );\n for (const rule of renderResult.rules) {\n if (!rule.declarations) continue;\n resolver.scanDeclarations(\n rule.declarations,\n isPropertyDefined,\n registerProperty,\n );\n }\n }\n\n // Scan keyframe declarations\n if (hasLocalKeyframes(styles)) {\n const localKf = extractLocalKeyframes(styles);\n if (localKf) {\n for (const steps of Object.values(localKf)) {\n scanKeyframeSteps(steps, resolver, isPropertyDefined, registerProperty);\n }\n }\n }\n\n return results;\n}\n\nfunction scanKeyframeSteps(\n steps: KeyframesSteps,\n resolver: PropertyTypeResolver,\n isPropertyDefined: (name: string) => boolean,\n registerProperty: (\n name: string,\n syntax: string,\n initialValue: string,\n ) => void,\n): void {\n for (const value of Object.values(steps)) {\n if (typeof value === 'string') {\n resolver.scanDeclarations(value, isPropertyDefined, registerProperty);\n } else if (value && typeof value === 'object') {\n const declarations = Object.entries(value)\n .map(([prop, val]) => {\n const cssProperty = camelToKebab(prop);\n return `${cssProperty}: ${val}`;\n })\n .join('; ');\n resolver.scanDeclarations(\n declarations,\n isPropertyDefined,\n registerProperty,\n );\n }\n }\n}\n\n// ============================================================================\n// Font Face Extraction (zero-runtime)\n// ============================================================================\n\nexport interface ExtractedFontFace {\n css: string;\n}\n\n/**\n * Extract @font-face rules from styles, merging with global config.\n * Deduplicates by content hash.\n */\nexport function extractFontFaceFromStyles(\n styles: Styles,\n globalFontFace?: Record<string, FontFaceInput> | null,\n): ExtractedFontFace[] {\n const results: ExtractedFontFace[] = [];\n const seenHashes = new Set<string>();\n\n function addFontFace(family: string, input: FontFaceInput) {\n const descriptors: FontFaceDescriptors[] = Array.isArray(input)\n ? input\n : [input];\n for (const desc of descriptors) {\n const hash = createHash('md5')\n .update(JSON.stringify({ family, ...desc }))\n .digest('hex')\n .slice(0, 8);\n if (!seenHashes.has(hash)) {\n seenHashes.add(hash);\n results.push({ css: formatFontFaceRule(family, desc) });\n }\n }\n }\n\n // Global font faces first\n if (globalFontFace) {\n for (const [family, input] of Object.entries(globalFontFace)) {\n addFontFace(family, input);\n }\n }\n\n // Local font faces (override globals with same hash)\n if (hasLocalFontFace(styles)) {\n const local = extractLocalFontFace(styles);\n if (local) {\n for (const [family, input] of Object.entries(local)) {\n addFontFace(family, input);\n }\n }\n }\n\n return results;\n}\n\n// ============================================================================\n// Counter Style Extraction (zero-runtime)\n// ============================================================================\n\nexport interface ExtractedCounterStyle {\n name: string;\n css: string;\n}\n\n/**\n * Extract @counter-style rules from styles, merging with global config.\n * Deduplicates by name (first definition wins).\n */\nexport function extractCounterStyleFromStyles(\n styles: Styles,\n globalCounterStyle?: Record<string, CounterStyleDescriptors> | null,\n): ExtractedCounterStyle[] {\n const results: ExtractedCounterStyle[] = [];\n const seenNames = new Set<string>();\n\n function addCounterStyle(name: string, descriptors: CounterStyleDescriptors) {\n if (!seenNames.has(name)) {\n seenNames.add(name);\n results.push({ name, css: formatCounterStyleRule(name, descriptors) });\n }\n }\n\n // Global counter styles first\n if (globalCounterStyle) {\n for (const [name, descriptors] of Object.entries(globalCounterStyle)) {\n addCounterStyle(name, descriptors);\n }\n }\n\n // Local counter styles (override globals with same name)\n if (hasLocalCounterStyle(styles)) {\n const local = extractLocalCounterStyle(styles);\n if (local) {\n for (const [name, descriptors] of Object.entries(local)) {\n addCounterStyle(name, descriptors);\n }\n }\n }\n\n return results;\n}\n"],"mappings":"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"}
|