@tenphi/tasty 0.0.0-snapshot.d656d18 → 0.0.0-snapshot.d7129ca
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/{babel-_RVkw0iE.d.ts → babel-DD7kdEFT.d.ts} +2 -2
- package/dist/chunks/async-storage-CG5HnUT5.js +20 -0
- package/dist/chunks/{async-storage-DKK-wTD4.js.map → async-storage-CG5HnUT5.js.map} +1 -1
- package/dist/chunks/build-config-CUQVwdWF.js +42 -0
- package/dist/chunks/build-config-CUQVwdWF.js.map +1 -0
- package/dist/chunks/{collector-DBGcmUEq.js → collector-B0LTHw6Z.js} +6 -124
- package/dist/chunks/collector-B0LTHw6Z.js.map +1 -0
- package/dist/chunks/config-engine-C3pF9EK9.js +267 -0
- package/dist/chunks/config-engine-C3pF9EK9.js.map +1 -0
- package/dist/chunks/css-definitions-D_it4XZH.js +875 -0
- package/dist/chunks/css-definitions-D_it4XZH.js.map +1 -0
- package/dist/chunks/{css-resources-Cyl_axbI.js → css-resources-BN8apgxt.js} +1 -8
- package/dist/chunks/{css-resources-Cyl_axbI.js.map → css-resources-BN8apgxt.js.map} +1 -1
- package/dist/chunks/{debug-BUwqo6vC.js → debug-CiiHDgmR.js} +69 -150
- package/dist/chunks/debug-CiiHDgmR.js.map +1 -0
- package/dist/chunks/{dsl-CXvoaLnj.js → dsl-C5yuAn41.js} +62 -786
- package/dist/chunks/dsl-C5yuAn41.js.map +1 -0
- package/dist/chunks/hydration-TRcz5vnk.js +41 -0
- package/dist/chunks/hydration-TRcz5vnk.js.map +1 -0
- package/dist/chunks/react-runtime-sH6r6Ajr.js +557 -0
- package/dist/chunks/react-runtime-sH6r6Ajr.js.map +1 -0
- package/dist/chunks/runtime-engine-BrRcOZwD.js +2438 -0
- package/dist/chunks/runtime-engine-BrRcOZwD.js.map +1 -0
- package/dist/chunks/runtime-state-BTDqSYBk.js +127 -0
- package/dist/chunks/runtime-state-BTDqSYBk.js.map +1 -0
- package/dist/chunks/{shared-utils-OIg0N_dp.js → shared-utils-DWA4LjIa.js} +24 -62
- package/dist/chunks/shared-utils-DWA4LjIa.js.map +1 -0
- package/dist/chunks/{style-engine-CqSmIlud.js → style-engine-C1ECBZxR.js} +671 -2736
- package/dist/chunks/style-engine-C1ECBZxR.js.map +1 -0
- package/dist/chunks/{css-writer-PFzrSPeu.js → zero-engine-B6EnYcXY.js} +3 -120
- package/dist/chunks/zero-engine-B6EnYcXY.js.map +1 -0
- package/dist/{collector-CVdVIy7s.d.ts → collector-YpsMjV7T.d.ts} +6 -4
- package/dist/{config-BI0y21JV.d.ts → config-BnZqyYG4.d.ts} +16 -25
- package/dist/core/index.d.ts +5 -5
- package/dist/core/index.js +9 -7
- package/dist/{index-CrT0bsiI.d.ts → index-BLJLo9cl.d.ts} +17 -50
- package/dist/index.d.ts +5 -5
- package/dist/index.js +10 -7
- package/dist/{merge-styles-BFktNP8J.d.ts → merge-styles-CTDLIrO5.d.ts} +2 -2
- package/dist/ssr/astro-client.js +1 -14
- package/dist/ssr/astro-client.js.map +1 -1
- package/dist/ssr/astro-middleware-static.js +0 -11
- package/dist/ssr/astro-middleware-static.js.map +1 -1
- package/dist/ssr/astro-middleware.js +0 -12
- package/dist/ssr/astro-middleware.js.map +1 -1
- package/dist/ssr/astro.js +5 -85
- package/dist/ssr/astro.js.map +1 -1
- package/dist/ssr/index.d.ts +6 -6
- package/dist/ssr/index.js +4 -4
- package/dist/ssr/next-config.d.ts +1 -1
- package/dist/ssr/next-config.js +29 -20
- package/dist/ssr/next-config.js.map +1 -1
- package/dist/ssr/next.d.ts +1 -1
- package/dist/ssr/next.js +5 -36
- package/dist/ssr/next.js.map +1 -1
- package/dist/static/index.d.ts +2 -2
- package/dist/static/index.js +1 -19
- package/dist/static/index.js.map +1 -1
- package/dist/{index-BzAC3p06.d.ts → types-vfRQhzRd.d.ts} +811 -811
- package/dist/zero/babel.d.ts +1 -1
- package/dist/zero/babel.js +25 -95
- package/dist/zero/babel.js.map +1 -1
- package/dist/zero/index.d.ts +1 -1
- package/dist/zero/index.js +1 -1
- package/dist/zero/next.d.ts +1 -1
- package/dist/zero/next.js +0 -31
- package/dist/zero/next.js.map +1 -1
- package/docs/ai-agents.md +5 -1
- package/docs/dsl.md +2 -2
- package/docs/getting-started.md +12 -11
- package/docs/methodology.md +2 -2
- package/docs/pipeline.md +3 -0
- package/docs/plugins.md +1 -1
- package/docs/runtime-benchmarks.md +105 -68
- package/docs/ssr.md +1 -1
- package/docs/styles.md +5 -5
- package/docs/tasty-static.md +1 -1
- package/package.json +29 -10
- package/dist/chunks/async-storage-DKK-wTD4.js +0 -44
- package/dist/chunks/collector-DBGcmUEq.js.map +0 -1
- package/dist/chunks/css-writer-PFzrSPeu.js.map +0 -1
- package/dist/chunks/debug-BUwqo6vC.js.map +0 -1
- package/dist/chunks/dsl-CXvoaLnj.js.map +0 -1
- package/dist/chunks/hydrate-6YoHZP6A.js +0 -37
- package/dist/chunks/hydrate-6YoHZP6A.js.map +0 -1
- package/dist/chunks/react-runtime-B3CmvDEQ.js +0 -1525
- package/dist/chunks/react-runtime-B3CmvDEQ.js.map +0 -1
- package/dist/chunks/runtime-engine-Y00VWjrS.js +0 -4719
- package/dist/chunks/runtime-engine-Y00VWjrS.js.map +0 -1
- package/dist/chunks/shared-utils-OIg0N_dp.js.map +0 -1
- package/dist/chunks/style-engine-CqSmIlud.js.map +0 -1
package/dist/zero/next.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"next.js","names":[],"sources":["../../src/zero/next.ts"],"sourcesContent":["/**\n * Next.js configuration wrapper for tasty-zero.\n *\n * Supports both webpack and Turbopack bundlers:\n * - **webpack**: Injects a babel-loader rule with the tasty-zero Babel plugin\n * via `webpack()` config hook. Config is passed as a jiti factory function.\n * - **Turbopack**: Adds a `turbopack.rules` entry with babel-loader and\n * JSON-serializable options (`configFile` path instead of a function).\n * The Babel plugin loads the config internally via jiti.\n *\n * The generated CSS is injected automatically — `@tenphi/tasty/static`\n * imports are replaced with an import of the output CSS file at build time.\n * No manual CSS import in layout files is needed.\n *\n * @example\n * ```javascript\n * // next.config.js\n * const { withTastyZero } = require('@tenphi/tasty/zero/next');\n *\n * module.exports = withTastyZero({\n * output: 'public/tasty.css',\n * configFile: './app/tasty-zero.config.ts',\n * })({\n * // your Next.js config\n * });\n * ```\n */\n\nimport { createRequire } from 'module';\nimport * as path from 'path';\nimport { fileURLToPath } from 'url';\n\nimport { createJiti } from 'jiti';\n\nimport type { TastyZeroBabelOptions, TastyZeroConfig } from './babel';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n// Next.js types (inline to avoid requiring next as a dependency)\ninterface WebpackConfigContext {\n isServer: boolean;\n dev: boolean;\n buildId: string;\n dir: string;\n}\n\n/* eslint-disable @typescript-eslint/no-explicit-any -- webpack/Next.js config types are complex */\ninterface TurbopackLoaderItem {\n loader: string;\n options?: Record<string, unknown>;\n}\n\ninterface TurbopackRuleConfig {\n loaders: (string | TurbopackLoaderItem)[];\n as?: string;\n condition?: unknown;\n}\n\ninterface TurbopackConfig {\n rules?: Record<string, TurbopackRuleConfig | TurbopackRuleConfig[]>;\n [key: string]: unknown;\n}\n\ninterface NextConfig {\n webpack?: (config: any, context: WebpackConfigContext) => any;\n turbopack?: TurbopackConfig;\n [key: string]: unknown;\n}\n\nexport interface TastyZeroNextOptions {\n /**\n * Output path for CSS relative to project root.\n * @default 'public/tasty.css'\n */\n output?: string;\n\n /**\n * Whether to enable the plugin.\n * @default true\n */\n enabled?: boolean;\n\n /**\n * Tasty configuration for build-time processing.\n * For static configs that don't change during dev.\n *\n * For configs that depend on theme files, use `configFile` instead.\n */\n config?: TastyZeroConfig;\n\n /**\n * Path to a TypeScript/JavaScript module that exports the tasty zero config\n * as its default export. The module is re-evaluated on each\n * compilation, enabling hot reload when the file (or its imports) change.\n *\n * @example './app/tasty-zero.config.ts'\n */\n configFile?: string;\n\n /**\n * Extra file paths (relative to project root) that the config depends on.\n * When any of these files change, the Babel cache is invalidated and\n * the config is re-evaluated.\n *\n * The `configFile` itself is always tracked automatically.\n * Use this for transitive dependencies that aren't directly imported\n * by the config file, or when using `config` instead of `configFile`.\n *\n * @example ['./app/theme.ts']\n */\n configDeps?: string[];\n\n /**\n * Output mode for extracted CSS.\n *\n * - `'file'` (default): CSS is written to a single output file.\n * - `'inject'`: CSS is embedded inline in JS and injected at runtime.\n * No CSS file is written. Best for reusable components and extensions.\n *\n * When `mode` is `'inject'`, `output` is ignored.\n *\n * @default 'file'\n */\n mode?: 'file' | 'inject';\n}\n\n/**\n * Next.js configuration wrapper for tasty-zero.\n * Configures both webpack and Turbopack bundlers automatically.\n */\nexport function withTastyZero(options: TastyZeroNextOptions = {}) {\n const {\n output = 'public/tasty.css',\n enabled = true,\n config: tastyConfig,\n configFile,\n configDeps = [],\n mode,\n } = options;\n\n return (nextConfig: NextConfig = {}): NextConfig => {\n if (!enabled) {\n return nextConfig;\n }\n\n const projectDir = process.cwd();\n const absoluteOutput = path.resolve(projectDir, output);\n const babelPluginPath = path.resolve(__dirname, 'babel.js');\n\n const absoluteConfigFile = configFile\n ? path.resolve(projectDir, configFile)\n : undefined;\n\n const allDeps = [\n ...(absoluteConfigFile ? [absoluteConfigFile] : []),\n ...configDeps.map((dep) => path.resolve(projectDir, dep)),\n ];\n\n // --- Turbopack configuration ---\n // Turbopack loader options must be JSON-serializable (no functions).\n // The Babel plugin loads config internally via `configFile` path + jiti.\n const turbopackBabelOptions: Record<string, unknown> = {\n babelrc: false,\n configFile: false,\n parserOpts: {\n plugins: ['typescript', 'jsx', 'decorators-legacy'],\n },\n plugins: [\n [\n babelPluginPath,\n {\n output: absoluteOutput,\n injectImport: true,\n ...(mode ? { mode } : {}),\n ...(absoluteConfigFile\n ? { configFile: absoluteConfigFile }\n : tastyConfig\n ? { config: tastyConfig }\n : {}),\n ...(allDeps.length > 0 ? { configDeps: allDeps } : {}),\n },\n ],\n ],\n };\n\n const existingTurbopack = nextConfig.turbopack || {};\n const existingRules = existingTurbopack.rules || {};\n\n const existingExperimental =\n (nextConfig.experimental as Record<string, unknown>) || {};\n\n return {\n ...nextConfig,\n\n experimental: {\n ...existingExperimental,\n turbopackUseBuiltinBabel: true,\n },\n\n turbopack: {\n ...existingTurbopack,\n rules: {\n ...existingRules,\n '*.{ts,tsx,js,jsx}': {\n condition: { not: 'foreign' },\n loaders: [\n {\n loader: 'babel-loader',\n options: turbopackBabelOptions,\n },\n ],\n },\n },\n },\n\n webpack(config: any, context: WebpackConfigContext) {\n const { dir } = context;\n\n const wpProjectDir = dir || projectDir;\n const wpAbsoluteOutput = path.resolve(wpProjectDir, output);\n const projectRequire = createRequire(\n path.resolve(wpProjectDir, 'package.json'),\n );\n\n const wpAbsoluteConfigFile = configFile\n ? path.resolve(wpProjectDir, configFile)\n : undefined;\n\n const wpAllDeps = [\n ...(wpAbsoluteConfigFile ? [wpAbsoluteConfigFile] : []),\n ...configDeps.map((dep) => path.resolve(wpProjectDir, dep)),\n ];\n\n const babelPluginOptions: TastyZeroBabelOptions = {\n output: wpAbsoluteOutput,\n injectImport: true,\n ...(mode ? { mode } : {}),\n };\n\n if (wpAbsoluteConfigFile) {\n const jiti = createJiti(wpProjectDir, {\n moduleCache: false,\n });\n\n babelPluginOptions.config = () => {\n return jiti(wpAbsoluteConfigFile) as TastyZeroConfig;\n };\n } else if (tastyConfig) {\n babelPluginOptions.config = tastyConfig;\n }\n\n if (wpAllDeps.length > 0) {\n babelPluginOptions.configDeps = wpAllDeps;\n }\n\n const babelPluginConfig = [babelPluginPath, babelPluginOptions];\n\n const existingRule = config.module?.rules?.find(\n (rule: any) =>\n rule.use?.loader === 'babel-loader' ||\n rule.use?.some?.((u: any) => u.loader === 'babel-loader'),\n );\n\n if (existingRule) {\n const babelUse = Array.isArray(existingRule.use)\n ? existingRule.use.find((u: any) => u.loader === 'babel-loader')\n : existingRule.use;\n\n if (babelUse?.options) {\n babelUse.options.plugins = babelUse.options.plugins || [];\n babelUse.options.plugins.push(babelPluginConfig);\n }\n } else {\n config.module = config.module || {};\n config.module.rules = config.module.rules || [];\n config.module.rules.push({\n test: /\\.(tsx?|jsx?)$/,\n exclude: /node_modules/,\n use: [\n {\n loader: projectRequire.resolve('babel-loader'),\n options: {\n babelrc: false,\n configFile: false,\n parserOpts: {\n plugins: ['typescript', 'jsx', 'decorators-legacy'],\n },\n plugins: [babelPluginConfig],\n },\n },\n ],\n });\n }\n\n if (typeof nextConfig.webpack === 'function') {\n return nextConfig.webpack(config, context);\n }\n\n return config;\n },\n };\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"next.js","names":[],"sources":["../../src/zero/next.ts"],"sourcesContent":["/**\n * Next.js configuration wrapper for tasty-zero.\n *\n * Supports both webpack and Turbopack bundlers:\n * - **webpack**: Injects a babel-loader rule with the tasty-zero Babel plugin\n * via `webpack()` config hook. Config is passed as a jiti factory function.\n * - **Turbopack**: Adds a `turbopack.rules` entry with babel-loader and\n * JSON-serializable options (`configFile` path instead of a function).\n * The Babel plugin loads the config internally via jiti.\n *\n * The generated CSS is injected automatically — `@tenphi/tasty/static`\n * imports are replaced with an import of the output CSS file at build time.\n * No manual CSS import in layout files is needed.\n *\n * @example\n * ```javascript\n * // next.config.js\n * const { withTastyZero } = require('@tenphi/tasty/zero/next');\n *\n * module.exports = withTastyZero({\n * output: 'public/tasty.css',\n * configFile: './app/tasty-zero.config.ts',\n * })({\n * // your Next.js config\n * });\n * ```\n */\n\nimport { createRequire } from 'module';\nimport * as path from 'path';\nimport { fileURLToPath } from 'url';\n\nimport { createJiti } from 'jiti';\n\nimport type { TastyZeroBabelOptions, TastyZeroConfig } from './babel';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n// Next.js types (inline to avoid requiring next as a dependency)\ninterface WebpackConfigContext {\n isServer: boolean;\n dev: boolean;\n buildId: string;\n dir: string;\n}\n\n/* eslint-disable @typescript-eslint/no-explicit-any -- webpack/Next.js config types are complex */\ninterface TurbopackLoaderItem {\n loader: string;\n options?: Record<string, unknown>;\n}\n\ninterface TurbopackRuleConfig {\n loaders: (string | TurbopackLoaderItem)[];\n as?: string;\n condition?: unknown;\n}\n\ninterface TurbopackConfig {\n rules?: Record<string, TurbopackRuleConfig | TurbopackRuleConfig[]>;\n [key: string]: unknown;\n}\n\ninterface NextConfig {\n webpack?: (config: any, context: WebpackConfigContext) => any;\n turbopack?: TurbopackConfig;\n [key: string]: unknown;\n}\n\nexport interface TastyZeroNextOptions {\n /**\n * Output path for CSS relative to project root.\n * @default 'public/tasty.css'\n */\n output?: string;\n\n /**\n * Whether to enable the plugin.\n * @default true\n */\n enabled?: boolean;\n\n /**\n * Tasty configuration for build-time processing.\n * For static configs that don't change during dev.\n *\n * For configs that depend on theme files, use `configFile` instead.\n */\n config?: TastyZeroConfig;\n\n /**\n * Path to a TypeScript/JavaScript module that exports the tasty zero config\n * as its default export. The module is re-evaluated on each\n * compilation, enabling hot reload when the file (or its imports) change.\n *\n * @example './app/tasty-zero.config.ts'\n */\n configFile?: string;\n\n /**\n * Extra file paths (relative to project root) that the config depends on.\n * When any of these files change, the Babel cache is invalidated and\n * the config is re-evaluated.\n *\n * The `configFile` itself is always tracked automatically.\n * Use this for transitive dependencies that aren't directly imported\n * by the config file, or when using `config` instead of `configFile`.\n *\n * @example ['./app/theme.ts']\n */\n configDeps?: string[];\n\n /**\n * Output mode for extracted CSS.\n *\n * - `'file'` (default): CSS is written to a single output file.\n * - `'inject'`: CSS is embedded inline in JS and injected at runtime.\n * No CSS file is written. Best for reusable components and extensions.\n *\n * When `mode` is `'inject'`, `output` is ignored.\n *\n * @default 'file'\n */\n mode?: 'file' | 'inject';\n}\n\n/**\n * Next.js configuration wrapper for tasty-zero.\n * Configures both webpack and Turbopack bundlers automatically.\n */\nexport function withTastyZero(options: TastyZeroNextOptions = {}) {\n const {\n output = 'public/tasty.css',\n enabled = true,\n config: tastyConfig,\n configFile,\n configDeps = [],\n mode,\n } = options;\n\n return (nextConfig: NextConfig = {}): NextConfig => {\n if (!enabled) {\n return nextConfig;\n }\n\n const projectDir = process.cwd();\n const absoluteOutput = path.resolve(projectDir, output);\n const babelPluginPath = path.resolve(__dirname, 'babel.js');\n\n const absoluteConfigFile = configFile\n ? path.resolve(projectDir, configFile)\n : undefined;\n\n const allDeps = [\n ...(absoluteConfigFile ? [absoluteConfigFile] : []),\n ...configDeps.map((dep) => path.resolve(projectDir, dep)),\n ];\n\n // --- Turbopack configuration ---\n // Turbopack loader options must be JSON-serializable (no functions).\n // The Babel plugin loads config internally via `configFile` path + jiti.\n const turbopackBabelOptions: Record<string, unknown> = {\n babelrc: false,\n configFile: false,\n parserOpts: {\n plugins: ['typescript', 'jsx', 'decorators-legacy'],\n },\n plugins: [\n [\n babelPluginPath,\n {\n output: absoluteOutput,\n injectImport: true,\n ...(mode ? { mode } : {}),\n ...(absoluteConfigFile\n ? { configFile: absoluteConfigFile }\n : tastyConfig\n ? { config: tastyConfig }\n : {}),\n ...(allDeps.length > 0 ? { configDeps: allDeps } : {}),\n },\n ],\n ],\n };\n\n const existingTurbopack = nextConfig.turbopack || {};\n const existingRules = existingTurbopack.rules || {};\n\n const existingExperimental =\n (nextConfig.experimental as Record<string, unknown>) || {};\n\n return {\n ...nextConfig,\n\n experimental: {\n ...existingExperimental,\n turbopackUseBuiltinBabel: true,\n },\n\n turbopack: {\n ...existingTurbopack,\n rules: {\n ...existingRules,\n '*.{ts,tsx,js,jsx}': {\n condition: { not: 'foreign' },\n loaders: [\n {\n loader: 'babel-loader',\n options: turbopackBabelOptions,\n },\n ],\n },\n },\n },\n\n webpack(config: any, context: WebpackConfigContext) {\n const { dir } = context;\n\n const wpProjectDir = dir || projectDir;\n const wpAbsoluteOutput = path.resolve(wpProjectDir, output);\n const projectRequire = createRequire(\n path.resolve(wpProjectDir, 'package.json'),\n );\n\n const wpAbsoluteConfigFile = configFile\n ? path.resolve(wpProjectDir, configFile)\n : undefined;\n\n const wpAllDeps = [\n ...(wpAbsoluteConfigFile ? [wpAbsoluteConfigFile] : []),\n ...configDeps.map((dep) => path.resolve(wpProjectDir, dep)),\n ];\n\n const babelPluginOptions: TastyZeroBabelOptions = {\n output: wpAbsoluteOutput,\n injectImport: true,\n ...(mode ? { mode } : {}),\n };\n\n if (wpAbsoluteConfigFile) {\n const jiti = createJiti(wpProjectDir, {\n moduleCache: false,\n });\n\n babelPluginOptions.config = () => {\n return jiti(wpAbsoluteConfigFile) as TastyZeroConfig;\n };\n } else if (tastyConfig) {\n babelPluginOptions.config = tastyConfig;\n }\n\n if (wpAllDeps.length > 0) {\n babelPluginOptions.configDeps = wpAllDeps;\n }\n\n const babelPluginConfig = [babelPluginPath, babelPluginOptions];\n\n const existingRule = config.module?.rules?.find(\n (rule: any) =>\n rule.use?.loader === 'babel-loader' ||\n rule.use?.some?.((u: any) => u.loader === 'babel-loader'),\n );\n\n if (existingRule) {\n const babelUse = Array.isArray(existingRule.use)\n ? existingRule.use.find((u: any) => u.loader === 'babel-loader')\n : existingRule.use;\n\n if (babelUse?.options) {\n babelUse.options.plugins = babelUse.options.plugins || [];\n babelUse.options.plugins.push(babelPluginConfig);\n }\n } else {\n config.module = config.module || {};\n config.module.rules = config.module.rules || [];\n config.module.rules.push({\n test: /\\.(tsx?|jsx?)$/,\n exclude: /node_modules/,\n use: [\n {\n loader: projectRequire.resolve('babel-loader'),\n options: {\n babelrc: false,\n configFile: false,\n parserOpts: {\n plugins: ['typescript', 'jsx', 'decorators-legacy'],\n },\n plugins: [babelPluginConfig],\n },\n },\n ],\n });\n }\n\n if (typeof nextConfig.webpack === 'function') {\n return nextConfig.webpack(config, context);\n }\n\n return config;\n },\n };\n };\n}\n"],"mappings":"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"}
|
package/docs/ai-agents.md
CHANGED
|
@@ -128,7 +128,11 @@ Value-only properties reject both colors and modifiers: `gap`, `columnGap`, `row
|
|
|
128
128
|
|
|
129
129
|
Use semantic names, not CSS property names: `fade` `fill` `color` `theme` `border` `radius` `shadow` `outline` `preset` `text` `gap` `opacity` `translate` `rotate` `scale` `filter` `image` `background` `width` `height` `zIndex` `inset` `flow` `dimension`.
|
|
130
130
|
|
|
131
|
-
❌ `transition: 'background-color 0.2s'` → ✅ `transition: 'fill
|
|
131
|
+
❌ `transition: 'background-color 0.2s'` → ✅ `transition: 'fill'`
|
|
132
|
+
|
|
133
|
+
Omit the duration to use `$fill-transition` with `$transition` as fallback, or
|
|
134
|
+
use a duration token explicitly (`transition: 'fill $transition'`). Avoid raw
|
|
135
|
+
time values in duration slots. An explicit zero can be useful to disable motion.
|
|
132
136
|
|
|
133
137
|
## 4. State maps
|
|
134
138
|
|
package/docs/dsl.md
CHANGED
|
@@ -701,7 +701,7 @@ const FadeIn = tasty({
|
|
|
701
701
|
styles: {
|
|
702
702
|
opacity: { '': '1', '@starting': '0' },
|
|
703
703
|
transform: { '': 'scale(1)', '@starting': 'scale(0.95)' },
|
|
704
|
-
transition: 'opacity
|
|
704
|
+
transition: 'opacity, translate',
|
|
705
705
|
},
|
|
706
706
|
});
|
|
707
707
|
```
|
|
@@ -833,7 +833,7 @@ const AnimatedGradient = tasty({
|
|
|
833
833
|
'$gradient-angle': '0deg',
|
|
834
834
|
'#theme': 'okhst(280 80% 50%)',
|
|
835
835
|
image: 'linear-gradient($gradient-angle, #theme, #clear)',
|
|
836
|
-
transition: '$$gradient-angle
|
|
836
|
+
transition: '$$gradient-angle, ##theme',
|
|
837
837
|
},
|
|
838
838
|
});
|
|
839
839
|
```
|
package/docs/getting-started.md
CHANGED
|
@@ -169,20 +169,21 @@ export default [
|
|
|
169
169
|
|
|
170
170
|
### What `recommended` catches
|
|
171
171
|
|
|
172
|
-
The `recommended` config
|
|
173
|
-
|
|
174
|
-
| Category | Rules | Examples
|
|
175
|
-
| ------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
176
|
-
| Property validation | `known-property`, `valid-boolean-property`, `valid-sub-element` | Flags typos like `pading` or invalid boolean usage
|
|
177
|
-
| Value validation | `valid-value`, `valid-color-token`, `valid-custom-unit` | Catches `#nonexistent` tokens, bad unit syntax
|
|
178
|
-
| State validation | `valid-state-key`, `no-nested-state-map`, `require-default-state` | Validates state key syntax, ensures `''` default exists
|
|
179
|
-
| Structure | `valid-styles-structure`, `no-important`, `no-nested-selector` | Prevents `!important`, invalid nesting
|
|
180
|
-
| Static mode | `static-no-dynamic-values`, `static-valid-selector` | Enforces build-time constraints in `tastyStatic()`
|
|
181
|
-
| Style properties | `valid-preset`, `valid-recipe`, `valid-transition`, `valid-directional-modifier`, `valid-radius-shape` | Validates preset names, recipe references, transition syntax
|
|
172
|
+
The `recommended` config covers common correctness and style issues:
|
|
173
|
+
|
|
174
|
+
| Category | Rules | Examples |
|
|
175
|
+
| ------------------- | ------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------- |
|
|
176
|
+
| Property validation | `known-property`, `valid-boolean-property`, `valid-sub-element` | Flags typos like `pading` or invalid boolean usage |
|
|
177
|
+
| Value validation | `valid-value`, `valid-color-token`, `valid-custom-unit` | Catches `#nonexistent` tokens, bad unit syntax |
|
|
178
|
+
| State validation | `valid-state-key`, `no-nested-state-map`, `require-default-state` | Validates state key syntax, ensures `''` default exists |
|
|
179
|
+
| Structure | `valid-styles-structure`, `no-important`, `no-nested-selector` | Prevents `!important`, invalid nesting |
|
|
180
|
+
| Static mode | `static-no-dynamic-values`, `static-valid-selector` | Enforces build-time constraints in `tastyStatic()` |
|
|
181
|
+
| Style properties | `valid-preset`, `valid-recipe`, `valid-transition`, `valid-directional-modifier`, `valid-radius-shape` | Validates preset names, recipe references, transition syntax |
|
|
182
|
+
| Motion guidance | `no-raw-transition-duration` | Suggests timing tokens or the default timing for transition durations |
|
|
182
183
|
|
|
183
184
|
### Strict config
|
|
184
185
|
|
|
185
|
-
For stricter governance, use `tasty.configs.strict`. It adds rules that
|
|
186
|
+
For stricter governance, use `tasty.configs.strict`. It adds rules that flag direct `styles` prop usage and other patterns:
|
|
186
187
|
|
|
187
188
|
```js
|
|
188
189
|
export default [tasty.configs.strict];
|
package/docs/methodology.md
CHANGED
|
@@ -236,7 +236,7 @@ const ProgressBar = tasty({
|
|
|
236
236
|
width: '$progress',
|
|
237
237
|
height: '100%',
|
|
238
238
|
fill: '#primary',
|
|
239
|
-
transition: 'width
|
|
239
|
+
transition: 'width',
|
|
240
240
|
},
|
|
241
241
|
},
|
|
242
242
|
elements: { Bar: 'div' },
|
|
@@ -534,7 +534,7 @@ See [Configuration](configuration.md) for the full `configure()` API.
|
|
|
534
534
|
|
|
535
535
|
- **Create styled wrappers** instead of passing `styles` directly — faster, composable, inspectable
|
|
536
536
|
- **Use design tokens and custom units** (`#text`, `2x`, `1r`) instead of raw CSS values
|
|
537
|
-
- **Use semantic transition names** (`transition: 'theme
|
|
537
|
+
- **Use semantic transition names** (`transition: 'theme'`) instead of listing CSS properties; omit the duration to use the motion tokens
|
|
538
538
|
- **Use `elements` prop** to declare typed sub-components for compound components
|
|
539
539
|
- **Use `styleProps`** to define what product engineers can customize
|
|
540
540
|
- **Use `modProps`** to expose known modifier states as clean component props
|
package/docs/pipeline.md
CHANGED
|
@@ -395,6 +395,8 @@ See the comment block in `exclusive.ts:500-523`: a default value whose higher-pr
|
|
|
395
395
|
|
|
396
396
|
Computes the Cartesian product of all style entries for a handler, creating snapshots of which value each style has for each possible state combination.
|
|
397
397
|
|
|
398
|
+
When every input is a direct value rather than a state map, the pipeline skips stages 0–4 and passes one unconditional snapshot directly to the handler. This avoids building exclusive entries and a Cartesian product for ordinary state-free styles.
|
|
399
|
+
|
|
398
400
|
### How It Works
|
|
399
401
|
|
|
400
402
|
1. Collect exclusive entries for each style the handler uses
|
|
@@ -454,6 +456,7 @@ Style handlers encapsulate the logic for translating design tokens (like `'2x'`)
|
|
|
454
456
|
### What It Does
|
|
455
457
|
|
|
456
458
|
Combines rules that have identical CSS output into a single rule with an OR condition.
|
|
459
|
+
Zero- and one-rule inputs return unchanged because no merge is possible.
|
|
457
460
|
|
|
458
461
|
### How It Works
|
|
459
462
|
|
package/docs/plugins.md
CHANGED
|
@@ -40,27 +40,44 @@ of elements, rules or interactions. Machine load, browser versions, and CPU
|
|
|
40
40
|
power will move the results — on a loaded machine the absolute columns drift
|
|
41
41
|
several percent while the raw/Tasty delta holds, so read the delta.
|
|
42
42
|
|
|
43
|
+
**On a laptop, check the power state first.** Low Power Mode caps CPU frequency,
|
|
44
|
+
and it is the single largest source of error here — larger than background load.
|
|
45
|
+
Measured on the machine below, running on battery with Low Power Mode enabled
|
|
46
|
+
made every one of the 53 Node cases slower, by a median of 26%, and halving the
|
|
47
|
+
background load recovered only 3 points of that. A frequency cap scales every
|
|
48
|
+
case by roughly the same factor, so it does not look like noise; it looks like a
|
|
49
|
+
uniformly slower library. Verify before trusting a run:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pmset -g ps | head -2 # want 'AC Power'
|
|
53
|
+
pmset -g | grep -i lowpowermode # want 0
|
|
54
|
+
```
|
|
55
|
+
|
|
43
56
|
## Core Style Pipeline
|
|
44
57
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
| Operation |
|
|
49
|
-
| ----------------------------------------------------------- |
|
|
50
|
-
| `renderStyles` — 5 flat properties (cold) |
|
|
51
|
-
| `renderStyles` — state map with media/hover/modifier (cold) |
|
|
52
|
-
| `renderStyles` —
|
|
53
|
-
| `
|
|
54
|
-
| `parseStateKey` —
|
|
55
|
-
| `parseStateKey` —
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
58
|
-
| `parseStyle` —
|
|
58
|
+
Single-call throughput, three consecutive runs on an Apple M3 Pro with Node
|
|
59
|
+
24.18.0 on AC power:
|
|
60
|
+
|
|
61
|
+
| Operation | ops/sec | Latency (mean) |
|
|
62
|
+
| ----------------------------------------------------------- | --------------------: | -------------: |
|
|
63
|
+
| `renderStyles` — 5 flat properties (cold) | 143,000–158,000 | 6.3–7.0 us |
|
|
64
|
+
| `renderStyles` — state map with media/hover/modifier (cold) | 31,000–36,000 | 28–32 us |
|
|
65
|
+
| `renderStyles` — OR state map across contexts (cold) | 13,000–14,000 | 71–76 us |
|
|
66
|
+
| `renderStyles` — same styles (cached) | 9,830,000–10,420,000 | ~0.10 us |
|
|
67
|
+
| `parseStateKey` — simple key like `:hover` (cold) | 1,910,000–1,980,000 | ~0.50 us |
|
|
68
|
+
| `parseStateKey` — value modifier key (cold) | 1,120,000–1,160,000 | ~0.90 us |
|
|
69
|
+
| `parseStateKey` — complex OR/AND/NOT key (cold) | 341,000–357,000 | 2.8–2.9 us |
|
|
70
|
+
| `parseStateKey` — any key (cached) | 4,900,000–16,530,000 | 0.10–0.20 us |
|
|
71
|
+
| `parseStyle` — value tokens like `2x 4x` (cold) | 575,000–605,000 | ~1.7 us |
|
|
72
|
+
| `parseStyle` — color tokens (cold) | 1,160,000–1,240,000 | 0.80–0.90 us |
|
|
73
|
+
| `parseStyle` — layered functions (cold) | 175,000–184,000 | 5.4–5.7 us |
|
|
74
|
+
| `parseStyle` — any value (cached) | 29,500,000–30,750,000 | ~0.03 us |
|
|
59
75
|
|
|
60
76
|
“Cold” cases use unique inputs to bypass the relevant caches. Cached cases
|
|
61
|
-
reuse one input and measure the LRU hot path.
|
|
62
|
-
|
|
63
|
-
|
|
77
|
+
reuse one input and measure the LRU hot path. Run-to-run spread on an idle
|
|
78
|
+
machine was 1–7% for most cases, rising to 10–16% for the two `renderStyles`
|
|
79
|
+
cold cases. These benchmarks do not include React, DOM work, stylesheet
|
|
80
|
+
injection, style resolution, layout, or paint.
|
|
64
81
|
|
|
65
82
|
The benchmark sources are colocated with the code they exercise:
|
|
66
83
|
[`pipeline.bench.ts`](../src/pipeline/pipeline.bench.ts),
|
|
@@ -86,19 +103,19 @@ excludes layout, paint, and stylesheet matching. Every commit is wrapped in
|
|
|
86
103
|
`flushSync`, keeping its synchronous reconciliation and commit inside the
|
|
87
104
|
sample. This does not estimate React's concurrent scheduling latency.
|
|
88
105
|
|
|
89
|
-
On an Apple M3 Pro with React 19.2.
|
|
106
|
+
On an Apple M3 Pro with React 19.2.8 and Chromium 151, three consecutive runs
|
|
90
107
|
produced these ranges:
|
|
91
108
|
|
|
92
|
-
| Work on 10,000 siblings | Raw elements |
|
|
93
|
-
| ----------------------------------- | -----------: |
|
|
94
|
-
| Mount + remove | 4.
|
|
95
|
-
| Rerender, same host props | 1.
|
|
96
|
-
| Rerender, change one host attribute | 2.
|
|
109
|
+
| Work on 10,000 siblings | Raw elements | `tasty({})` | Extra per wrapped element |
|
|
110
|
+
| ----------------------------------- | -----------: | ----------: | ------------------------: |
|
|
111
|
+
| Mount + remove | 4.2–4.3 ms | 9.2–9.7 ms | 0.50–0.54 us |
|
|
112
|
+
| Rerender, same host props | 1.2–1.3 ms | 6.3–6.5 ms | 0.51–0.52 us |
|
|
113
|
+
| Rerender, change one host attribute | 2.5–2.7 ms | 11.2–11.6 ms | 0.86–0.91 us |
|
|
97
114
|
|
|
98
115
|
The useful result is the raw/Tasty time difference divided by 10,000, not the
|
|
99
116
|
ratio between the two times. The ratio becomes large because the raw baseline
|
|
100
|
-
is tiny. In this synthetic workload, an empty wrapper adds roughly
|
|
101
|
-
participating element, or
|
|
117
|
+
is tiny. In this synthetic workload, an empty wrapper adds roughly 0.5 us per
|
|
118
|
+
participating element, or ~0.9 us when React also changes a DOM attribute.
|
|
102
119
|
|
|
103
120
|
This is the floor Tasty consumes when it has no styling job. It is not a
|
|
104
121
|
page-level score. Real trees include application components, effects, layout,
|
|
@@ -143,17 +160,23 @@ ranges:
|
|
|
143
160
|
|
|
144
161
|
| Workload | CSS already present | Tasty runtime | Incremental Tasty cost |
|
|
145
162
|
| ---------------------------------------------------- | ------------------: | -------------: | ---------------------: |
|
|
146
|
-
| One new rule + immediate resolution, per transaction | 2.
|
|
147
|
-
| 1,000 new rules + one resolution | 1.
|
|
148
|
-
| 1,000-rule workload, incremental cost per rule | — | — | 7
|
|
163
|
+
| One new rule + immediate resolution, per transaction | 2.6–3.0 us | 102.8–107.0 us | 100.3–104.3 us |
|
|
164
|
+
| 1,000 new rules + one resolution | 1.69–1.79 ms | 7.42–7.67 ms | 5.73–5.94 ms |
|
|
165
|
+
| 1,000-rule workload, incremental cost per rule | — | — | 5.7–5.9 us |
|
|
149
166
|
|
|
150
167
|
Directly compared, injecting 1,000 rules before one resolution boundary cost
|
|
151
|
-
about
|
|
152
|
-
1,000 times as much. Its average incremental cost per rule was about
|
|
168
|
+
about 55–59 times as much in total as injecting one rule and resolving it—not
|
|
169
|
+
1,000 times as much. Its average incremental cost per rule was about 17–18
|
|
153
170
|
times lower. This is the same Tasty generation and injection path in both cases;
|
|
154
171
|
the group amortizes fixed transaction work and lets the browser resolve all the
|
|
155
172
|
stylesheet writes together.
|
|
156
173
|
|
|
174
|
+
The per-transaction single-rule figure is the one number here that engine work
|
|
175
|
+
does not move: it is dominated by the browser's injection-to-resolution
|
|
176
|
+
boundary, which is crossed once per rule regardless of how fast generation is.
|
|
177
|
+
The 1,000-rule column, where Tasty's own generation dominates, is where engine
|
|
178
|
+
changes show up.
|
|
179
|
+
|
|
157
180
|
The subtraction is the meaningful result. It includes Tasty's cold style
|
|
158
181
|
generation, cache and injector bookkeeping, rule insertion, and any additional
|
|
159
182
|
style invalidation exposed by that workload's resolution boundary. It does not
|
|
@@ -207,25 +230,25 @@ if the hovered rule is not already there. That a style map's states all ship in
|
|
|
207
230
|
one chunk on first render is the premise of this case; if the hovered rule
|
|
208
231
|
arrived lazily, the first sample would be timing injection.
|
|
209
232
|
|
|
210
|
-
On an Apple
|
|
233
|
+
On an Apple M3 Pro with React 19.2.8 and Chromium 151, three consecutive runs:
|
|
211
234
|
|
|
212
|
-
| Workload | Raw elements |
|
|
213
|
-
| ---------------------------------------------------- | -----------: |
|
|
214
|
-
| 300 single-element mod toggles in a 100-element tree |
|
|
215
|
-
| 20 mount + unmount cycles of a 200-element subtree |
|
|
235
|
+
| Workload | Raw elements | Tasty mods | Extra per unit |
|
|
236
|
+
| ---------------------------------------------------- | -----------: | ---------: | -----------------------: |
|
|
237
|
+
| 300 single-element mod toggles in a 100-element tree | 1.7–1.8 ms | 2.0–2.1 ms | 1.2–1.3 us / interaction |
|
|
238
|
+
| 20 mount + unmount cycles of a 200-element subtree | 5.2–5.4 ms | 8.3–8.6 ms | 0.77–0.81 us / element |
|
|
216
239
|
|
|
217
240
|
The absolute columns move several percent with machine load; the delta between
|
|
218
241
|
the arms is the stable quantity, so read that rather than either column.
|
|
219
242
|
|
|
220
243
|
Two things are worth reading out of this.
|
|
221
244
|
|
|
222
|
-
A mod flip on an already-mounted element costs about 2 us. The CSS for both
|
|
245
|
+
A mod flip on an already-mounted element costs about 1.2 us. The CSS for both
|
|
223
246
|
states already exists — Tasty emits every state of a style map in one chunk on
|
|
224
247
|
first render — so both arms perform the same commit, and what is left is
|
|
225
|
-
Tasty's props and mod handling. That is the same order as the ~
|
|
248
|
+
Tasty's props and mod handling. That is the same order as the ~0.5 us empty
|
|
226
249
|
wrapper measured above, which is most of where it comes from.
|
|
227
250
|
|
|
228
|
-
Subtree churn is not about styling at all. Its ~
|
|
251
|
+
Subtree churn is not about styling at all. Its ~0.8 us per element sits right
|
|
229
252
|
on the empty-wrapper mount cost, because the styles are already cached:
|
|
230
253
|
reopening a menu re-pays the React wrapper, not the style pipeline.
|
|
231
254
|
|
|
@@ -257,7 +280,7 @@ Two things about the payload decide whether this measures a deployment or a
|
|
|
257
280
|
straw man, so both are enforced rather than assumed:
|
|
258
281
|
|
|
259
282
|
- **Assets are served brotli-compressed**, the way a static host serves them.
|
|
260
|
-
The bundle is
|
|
283
|
+
The bundle is 51.5 KB on the wire and 180 KB decoded; putting the decoded
|
|
261
284
|
bytes on a 1.6 Mbps link would add ~700 ms and charge it to Tasty. The run
|
|
262
285
|
reads `encodedBodySize` back out of resource timing and fails if what
|
|
263
286
|
crossed the wire is not the compressed size the table reports.
|
|
@@ -266,35 +289,48 @@ straw man, so both are enforced rather than assumed:
|
|
|
266
289
|
would be. Re-exporting the whole library adds ~4 KB brotli of code no page
|
|
267
290
|
here calls.
|
|
268
291
|
|
|
269
|
-
On an Apple
|
|
292
|
+
On an Apple M3 Pro with React 19.2.8 and Chromium 151, first contentful paint,
|
|
293
|
+
median of three full runs of the matrix:
|
|
294
|
+
|
|
295
|
+
| Link / CPU | baseline | runtime | prewarm | Tasty's cost | noise |
|
|
296
|
+
| --------------------- | -------: | ------: | ------: | -----------: | ----: |
|
|
297
|
+
| No throttling, 1x | 36 ms | 40 ms | 40 ms | +4 ms | 0 ms |
|
|
298
|
+
| Fast 4G, 1x | 628 ms | 672 ms | 688 ms | +44 ms | 20 ms |
|
|
299
|
+
| Slow 4G, 1x | 2044 ms | 2308 ms | 2308 ms | +264 ms | 24 ms |
|
|
300
|
+
| No throttling, 4x CPU | 112 ms | 144 ms | 144 ms | +32 ms | 16 ms |
|
|
301
|
+
| Fast 4G, 4x CPU | 656 ms | 728 ms | 736 ms | +72 ms | 4 ms |
|
|
302
|
+
| Slow 4G, 4x CPU | 2064 ms | 2360 ms | 2360 ms | +296 ms | 8 ms |
|
|
303
|
+
|
|
304
|
+
The `noise` column is not an estimate. The `baseline` page contains no Tasty at
|
|
305
|
+
all, so its three samples should be identical; the spread they actually show is
|
|
306
|
+
this cell's measurement error, and it applies to the other columns too.
|
|
270
307
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
| No throttling, 4x CPU | 148 ms | 196 ms | 196 ms | +48 ms |
|
|
277
|
-
| Fast 4G, 4x CPU | 684 ms | 784 ms | 788 ms | +100 ms |
|
|
278
|
-
| Slow 4G, 4x CPU | 2096 ms | 2416 ms | 2408 ms | +320 ms |
|
|
308
|
+
Read that column before any other. Only the Slow 4G rows carry a cost an order
|
|
309
|
+
of magnitude above their own noise. The unthrottled 1x row (+4 ms) means "too
|
|
310
|
+
small to measure this way", not "4 ms". And a single run is genuinely not
|
|
311
|
+
enough here: taken alone, the first of these three runs put Fast 4G 1x at
|
|
312
|
+
+72 ms, which the median over three corrects to +44 ms.
|
|
279
313
|
|
|
280
|
-
|
|
314
|
+
For the same reason, `prewarm` landing above `runtime` in the Fast 4G 1x row is
|
|
315
|
+
noise, not a cost — the two modes differ only in when the engine compiles, and
|
|
316
|
+
that difference is measured in the phase table below, not in FCP.
|
|
281
317
|
|
|
282
318
|
**The cost is the bundle, not the work.** On Slow 4G the extra transfer alone
|
|
283
|
-
accounts for
|
|
284
|
-
Tasty then *does* is small by comparison:
|
|
319
|
+
accounts for 263 ms of the 264 ms FCP delta — effectively all of it. Everything
|
|
320
|
+
Tasty then *does* is small by comparison (median of three runs):
|
|
285
321
|
|
|
286
322
|
| Phase (Slow 4G, 1x) | baseline | runtime | prewarm |
|
|
287
323
|
| ----------------------- | -------: | ------: | ------: |
|
|
288
|
-
| js+css transfer |
|
|
289
|
-
| module compile (shared) |
|
|
290
|
-
| tasty top-level execute | — | 1.2 ms | 0.
|
|
291
|
-
| `configure()` | — | 0.6 ms | 0.
|
|
292
|
-
| prewarm | — | — |
|
|
293
|
-
| render 1st component |
|
|
294
|
-
| render 49 more | 1.
|
|
324
|
+
| js+css transfer | 1423 ms | 1686 ms | 1690 ms |
|
|
325
|
+
| module compile (shared) | 3.5 ms | 3.3 ms | 3.0 ms |
|
|
326
|
+
| tasty top-level execute | — | 1.2 ms | 0.8 ms |
|
|
327
|
+
| `configure()` | — | 0.6 ms | 0.4 ms |
|
|
328
|
+
| prewarm | — | — | 3.3 ms |
|
|
329
|
+
| render 1st component | 3.0 ms | 7.3 ms | 1.9 ms |
|
|
330
|
+
| render 49 more | 1.3 ms | 5.2 ms | 3.8 ms |
|
|
295
331
|
|
|
296
332
|
Importing Tasty costs about 1 ms of top-level execution; `configure()` costs
|
|
297
|
-
half of one. The rest of the CPU delta — about
|
|
333
|
+
half of one. The rest of the CPU delta — about 10 ms for 50 components — is
|
|
298
334
|
generation and injection, which is the cost the injection benchmark isolates.
|
|
299
335
|
|
|
300
336
|
One asymmetry is worth naming: the control links a render-blocking stylesheet
|
|
@@ -304,20 +340,21 @@ delivery models, not a thumb on the scale, but it means the FCP delta is not
|
|
|
304
340
|
purely "what Tasty costs to execute".
|
|
305
341
|
|
|
306
342
|
**Prewarming moves the wake-up, it does not remove it.** The first styled render
|
|
307
|
-
is ~5 ms more expensive than the ones after it, because that is when the
|
|
343
|
+
is ~5.4 ms more expensive than the ones after it, because that is when the
|
|
308
344
|
engine's deferred payload is actually compiled. A throwaway `computeStyles()`
|
|
309
|
-
against a detached root pays it early: `render 1st` drops from
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
latency-critical path and the page
|
|
345
|
+
against a detached root pays it early: `render 1st` drops from 7.3 ms to
|
|
346
|
+
1.9 ms. The prewarm itself costs 3.3 ms, so the work is moved rather than
|
|
347
|
+
removed and FCP does not move. It is worth doing only when something else can
|
|
348
|
+
overlap it, or when the first render is on a latency-critical path and the page
|
|
349
|
+
has idle time before it.
|
|
313
350
|
|
|
314
351
|
**Retained heap.** After a forced collection, the runtime page holds about
|
|
315
|
-
|
|
352
|
+
985 KB more than the control (2,605 KB vs 1,619 KB) for 50 components — the
|
|
316
353
|
parser caches, the chunk cache, the injector's registry and the generated CSS.
|
|
317
354
|
The control is not zero either; most of its 1.6 MB is React and the DOM.
|
|
318
355
|
|
|
319
356
|
CPU throttling changes which line moves. At 4x, module compilation of the
|
|
320
|
-
larger graph becomes visible (
|
|
357
|
+
larger graph becomes visible (2.6 ms → 19 ms) where at 1x it is free: V8
|
|
321
358
|
pre-parses at import and compiles lazily, so a slower CPU pays for code the
|
|
322
359
|
faster one never fully compiled. Transfer numbers from the unthrottled cells
|
|
323
360
|
are not worth reading — with no emulated link, resource timings are scheduling
|
|
@@ -341,8 +378,8 @@ blindly. They describe different paths:
|
|
|
341
378
|
|
|
342
379
|
The cold-start measurement is the one that puts the rest in proportion. On a
|
|
343
380
|
slow connection, nearly all of Tasty's page-load cost is transferring the
|
|
344
|
-
library —
|
|
345
|
-
microbenchmarks obsess over is ~
|
|
381
|
+
library — 263 ms of a 264 ms delta — while the generation and injection the
|
|
382
|
+
microbenchmarks obsess over is ~10 ms for 50 components. Bundle size is
|
|
346
383
|
therefore the lever with the largest effect on first paint, and the runtime
|
|
347
384
|
levers matter for what happens after it.
|
|
348
385
|
|
package/docs/ssr.md
CHANGED
|
@@ -506,7 +506,7 @@ const stream = await runWithCollector(collector, () =>
|
|
|
506
506
|
| `@tenphi/tasty/ssr/next-config` | Next.js config wrapper: shared, content-hashed global stylesheet |
|
|
507
507
|
| `@tenphi/tasty/ssr/astro` | Astro: `tastyIntegration`, `tastyMiddleware` |
|
|
508
508
|
| `@tenphi/tasty/ssr/astro-client` | Astro: client-side cache hydration (auto-injected by integration, or import manually) |
|
|
509
|
-
| `@tenphi/tasty/ssr/astro-middleware`<br
|
|
509
|
+
| `@tenphi/tasty/ssr/astro-middleware`<br />`@tenphi/tasty/ssr/astro-middleware-static`<br />`@tenphi/tasty/ssr/astro-middleware-extract`<br />`@tenphi/tasty/ssr/astro-middleware-extract-static` | Astro: the middleware entrypoints `tastyIntegration()` registers via `addMiddleware()`. Exported so Astro can resolve them by specifier; you should not import them. For manual setups use `tastyMiddleware()`. |
|
|
510
510
|
|
|
511
511
|
### `ServerStyleCollector`
|
|
512
512
|
|
package/docs/styles.md
CHANGED
|
@@ -566,15 +566,15 @@ When easing is provided without a duration, the default timing variable is used
|
|
|
566
566
|
| `zIndex` | `z-index` |
|
|
567
567
|
| `inset` | `inset`, `top`, `right`, `bottom`, `left` |
|
|
568
568
|
|
|
569
|
-
Default timing: `$transition` (or `${name}-transition` with `$transition` fallback per semantic name).
|
|
569
|
+
Default timing: `$transition` (or `${name}-transition` with `$transition` fallback per semantic name). Prefer omitting the duration or using a duration token instead of repeating raw time values across components. Explicit zero can disable motion.
|
|
570
570
|
|
|
571
571
|
```jsx
|
|
572
|
-
transition="theme
|
|
573
|
-
transition="fill
|
|
574
|
-
transition="fade
|
|
572
|
+
transition="theme"
|
|
573
|
+
transition="fill, radius"
|
|
574
|
+
transition="fade $transition ease-in"
|
|
575
575
|
transition="fill ease-in" // easing without duration (uses default timing)
|
|
576
576
|
transition="radius ease-in-out" // easing keyword only
|
|
577
|
-
transition="$$custom-prop
|
|
577
|
+
transition="$$custom-prop" // custom property transition
|
|
578
578
|
```
|
|
579
579
|
|
|
580
580
|
**Recognized easing keywords:** `ease`, `ease-in`, `ease-out`, `ease-in-out`, `linear`, `step-start`, `step-end`. CSS easing functions (`cubic-bezier(...)`, `steps(...)`, `linear(...)`) are also recognized.
|
package/docs/tasty-static.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tenphi/tasty",
|
|
3
|
-
"version": "0.0.0-snapshot.
|
|
3
|
+
"version": "0.0.0-snapshot.d7129ca",
|
|
4
4
|
"description": "A design-system-integrated styling system and DSL for concise, state-aware UI styling",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -133,6 +133,7 @@
|
|
|
133
133
|
"release": "changeset publish",
|
|
134
134
|
"knip": "knip",
|
|
135
135
|
"check:build": "node scripts/check-built-entries.mjs",
|
|
136
|
+
"check:react-compat": "node scripts/check-react-compat.mjs",
|
|
136
137
|
"check:test-only": "node scripts/check-test-only-code.mjs",
|
|
137
138
|
"hygiene": "pnpm lint && pnpm format:check && pnpm typecheck",
|
|
138
139
|
"hygiene:fix": "pnpm lint:fix && pnpm format && pnpm typecheck"
|
|
@@ -231,43 +232,43 @@
|
|
|
231
232
|
"name": "main (import *)",
|
|
232
233
|
"path": "dist/index.js",
|
|
233
234
|
"import": "*",
|
|
234
|
-
"limit": "
|
|
235
|
+
"limit": "57.5 kB"
|
|
235
236
|
},
|
|
236
237
|
{
|
|
237
238
|
"name": "main (tasty)",
|
|
238
239
|
"path": "dist/index.js",
|
|
239
240
|
"import": "{ tasty }",
|
|
240
|
-
"limit": "
|
|
241
|
+
"limit": "50.61 kB"
|
|
241
242
|
},
|
|
242
243
|
{
|
|
243
244
|
"name": "main (StyleParser)",
|
|
244
245
|
"path": "dist/index.js",
|
|
245
246
|
"import": "{ StyleParser }",
|
|
246
|
-
"limit": "
|
|
247
|
+
"limit": "6.94 kB"
|
|
247
248
|
},
|
|
248
249
|
{
|
|
249
250
|
"name": "main (parseColor)",
|
|
250
251
|
"path": "dist/index.js",
|
|
251
252
|
"import": "{ parseColor }",
|
|
252
|
-
"limit": "
|
|
253
|
+
"limit": "7.59 kB"
|
|
253
254
|
},
|
|
254
255
|
{
|
|
255
256
|
"name": "main (useRawCSS)",
|
|
256
257
|
"path": "dist/index.js",
|
|
257
258
|
"import": "{ useRawCSS }",
|
|
258
|
-
"limit": "
|
|
259
|
+
"limit": "31.2 kB"
|
|
259
260
|
},
|
|
260
261
|
{
|
|
261
262
|
"name": "core (import *)",
|
|
262
263
|
"path": "dist/core/index.js",
|
|
263
264
|
"import": "*",
|
|
264
|
-
"limit": "
|
|
265
|
+
"limit": "54.3 kB"
|
|
265
266
|
},
|
|
266
267
|
{
|
|
267
268
|
"name": "static",
|
|
268
269
|
"path": "dist/static/index.js",
|
|
269
270
|
"import": "*",
|
|
270
|
-
"limit": "
|
|
271
|
+
"limit": "1.08 kB"
|
|
271
272
|
},
|
|
272
273
|
{
|
|
273
274
|
"name": "zero",
|
|
@@ -278,7 +279,7 @@
|
|
|
278
279
|
"path",
|
|
279
280
|
"crypto"
|
|
280
281
|
],
|
|
281
|
-
"limit": "
|
|
282
|
+
"limit": "32.29 kB"
|
|
282
283
|
},
|
|
283
284
|
{
|
|
284
285
|
"name": "babel-plugin",
|
|
@@ -289,7 +290,25 @@
|
|
|
289
290
|
"path",
|
|
290
291
|
"crypto"
|
|
291
292
|
],
|
|
292
|
-
"limit": "
|
|
293
|
+
"limit": "41.1 kB"
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
"name": "astro client",
|
|
297
|
+
"path": "dist/ssr/astro-client.js",
|
|
298
|
+
"import": "*",
|
|
299
|
+
"limit": "0.5 kB"
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
"name": "Next.js config",
|
|
303
|
+
"path": "dist/ssr/next-config.js",
|
|
304
|
+
"import": "{ withTastyNext }",
|
|
305
|
+
"ignore": [
|
|
306
|
+
"node:crypto",
|
|
307
|
+
"node:fs",
|
|
308
|
+
"node:module",
|
|
309
|
+
"node:path"
|
|
310
|
+
],
|
|
311
|
+
"limit": "37.2 kB"
|
|
293
312
|
}
|
|
294
313
|
]
|
|
295
314
|
}
|