From df48ee21f9c6c2aae95079daa447a00e01723c07 Mon Sep 17 00:00:00 2001 From: touyou <465697+touyou@users.noreply.github.com> Date: Fri, 10 Jul 2026 17:20:37 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20build-sparkle-design-theme.mjs=20?= =?UTF-8?q?=E3=81=8C=20@theme=20inline=20=E3=81=AE=E8=87=AA=E5=B7=B1?= =?UTF-8?q?=E5=8F=82=E7=85=A7=E3=81=A7=E5=A3=8A=E3=82=8C=E3=82=8B=E5=95=8F?= =?UTF-8?q?=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sparkle-design-cli 2.4.1+ は @theme inline の各セマンティック宣言を同名変数 への自己参照(例: --color-primary-500: var(--color-primary-500))にする設計 に変更した(#289 のレビュー指摘で発覚)。build-sparkle-design-theme.mjs は この @theme inline ブロックを解析して registry の cssVars にマージしていた ため、自己参照の場合 color-primary-500 の値が解決不能な循環参照 ("var(--color-primary-500)")になってしまっていた。 sparkle-design.css には実行時に参照できる実値を持つセマンティックトークン 専用の :root ブロックが別途あるため(@theme inline は Tailwind の compiled utility 生成専用)、@theme inline ではなくこちらを読むように変更。 extractBraceBlock(最初の1ブロックのみ取得)を extractAllBraceBlocks に 置き換え、プリミティブ :root とセマンティック :root の両方をマージする。 現状 Vercel 本番ビルド(scripts/build-registry-vercel.sh)は sparkle-design-cli@2.2.2 に固定されているため影響しないが、ローカルの `make registry` 実行、および将来のバージョン固定引き上げ時に確実に顕在化する 問題だったため修正した。 Co-Authored-By: Claude Sonnet 5 --- scripts/build-sparkle-design-theme.mjs | 80 +++++++++++++++++--------- 1 file changed, 54 insertions(+), 26 deletions(-) diff --git a/scripts/build-sparkle-design-theme.mjs b/scripts/build-sparkle-design-theme.mjs index 8aa5a3e9..9253a591 100644 --- a/scripts/build-sparkle-design-theme.mjs +++ b/scripts/build-sparkle-design-theme.mjs @@ -21,26 +21,39 @@ function sortObjectKeys(obj) { } /** - * ブロック抽出ヘルパー: 指定トークン直後の {} をパース - * en: Extracts the brace block following a token (e.g. :root, @theme inline) + * `token` に一致する {} ブロックをすべて抽出する。sparkle-design.css には + * `:root { ... }` が2つある(プリミティブブロックと、実行時に参照できる + * セマンティックトークンブロック)ため、両方を集める必要がある。 + * en: Extract every brace block matching `token`. sparkle-design.css + * declares two `:root { ... }` blocks (a primitive block and a + * semantic-token block meant to be usable at runtime), both of which need + * to be collected here. */ -function extractBraceBlock(source, token) { - const start = source.indexOf(token); - if (start === -1) return null; - const open = source.indexOf("{", start); - if (open === -1) return null; - let depth = 0; - for (let i = open; i < source.length; i++) { - if (source[i] === "{") depth++; - else if (source[i] === "}") { - depth--; - if (depth === 0) { - // return inner content (without braces) - return source.slice(open + 1, i); +function extractAllBraceBlocks(source, token) { + const blocks = []; + let searchFrom = 0; + while (true) { + const start = source.indexOf(token, searchFrom); + if (start === -1) break; + const open = source.indexOf("{", start); + if (open === -1) break; + let depth = 0; + let end = -1; + for (let i = open; i < source.length; i++) { + if (source[i] === "{") depth++; + else if (source[i] === "}") { + depth--; + if (depth === 0) { + end = i; + break; + } } } + if (end === -1) break; // unbalanced + blocks.push(source.slice(open + 1, end)); + searchFrom = end + 1; } - return null; + return blocks; } /** @@ -139,15 +152,31 @@ function buildTheme() { const importRules = parseImports(noComments); // 2) :root variables - const rootBlock = extractBraceBlock(noComments, ":root"); - const rootVars = rootBlock ? parseDeclarations(rootBlock) : {}; - - // 3) @theme inline variables (same treatment as vars) - const themeInlineBlock = extractBraceBlock(noComments, "@theme inline"); - const themeVars = themeInlineBlock ? parseDeclarations(themeInlineBlock) : {}; - - // Merge vars (semantic overrides primitive) - const cssVarsTheme = { ...rootVars, ...themeVars }; + // sparkle-design.css には `:root { ... }` が2つある + // (プリミティブトークンブロックと、実行時に参照できるセマンティックトークン + // ブロック)。どちらも実際に解決可能な値(リテラル、または他の :root 変数への + // 参照)を持つため、両方をマージしてレジストリの cssVars に使う。 + // + // `@theme inline` は意図的にここでは使わない: sparkle-design-cli 2.4.1+ の + // `@theme inline` は各セマンティック宣言が同名変数へ自己参照する + // (例: `--color-primary-500: var(--color-primary-500)`)設計になっており、 + // これは Tailwind の compiled utility class 生成のためだけの仕組みで、 + // レジストリ消費者へそのまま渡すと解決不能な循環参照になってしまう + // (goodpatch/sparkle-design#289 のレビュー指摘)。 + // en: sparkle-design.css declares two `:root { ... }` blocks (primitive + // tokens, and semantic tokens meant to be usable at runtime). Both resolve + // to real values (literals or references to other :root vars), so merge + // both for the registry's cssVars. + // `@theme inline` is intentionally NOT used here: as of sparkle-design-cli + // 2.4.1+, every `@theme inline` declaration self-references its own + // semantic variable name (e.g. `--color-primary-500: var(--color-primary-500)`) + // — a Tailwind-compiler-only construct — which would become an unresolvable + // circular reference if handed to registry consumers as-is. + const rootBlocks = extractAllBraceBlocks(noComments, ":root"); + const cssVarsTheme = rootBlocks.reduce( + (acc, block) => ({ ...acc, ...parseDeclarations(block) }), + {} + ); // 4) @utility blocks -> css const utilities = parseUtilities(noComments); @@ -190,4 +219,3 @@ if (import.meta.url === `file://${process.argv[1]}`) { } export default buildTheme; -