Lightning CSS 1.32.0 へのバージョンアップと`detect-libc@2`対応パッチの更新
Lightning CSS を 1.31.1 から 1.32.0 へバージョンアップし、detect-libc@2 への移行に伴うネイティブバイナリ解決ロジックのパッチを更新しました。CSS 出力における var() のフォールバック値の空白正規化や、:autofill セレクタの分離など、生成 CSS に複数の変更が生じています。
背景
Lightning CSS 1.32.0 では node/index.js のネイティブバイナリ選択ロジックが変更され、detect-libc@2 を同梱するようになりました。Tailwind CSS はネイティブバイナリ解決時に process.env.PLATFORM_LIBC 環境変数を考慮する必要があり、また Bun のバンドラが require(…) 呼び出しを静的解析できるようにする制約もあるため、アップストリームの変更をそのまま使用できず、独自のパッチを維持しています。
@parcel/watcher@2.5.1 も同様に detect-libc@2 を同梱するようになり、こちらのパッチも合わせて更新が必要になりました。
技術的な変更
detect-libc@2 対応:familySync の無条件呼び出しへの変更
両パッチの最も重要な変更は、detect-libc の API 利用方法の統一です。旧パッチでは detect-libc@1 の family(非同期)と detect-libc@2 の familySync の両方に対応するため、typeof familySync === 'function' による条件分岐を設けていました。新パッチでは detect-libc@2 が確定的に使われる前提で、familySync を直接呼び出す形に簡略化されています。
変更前(旧パッチ):
let { MUSL, GLIBC, family, familySync } = require('detect-libc')
// Bun polyfills `detect-libc` in compiled binaries. We rely on
// detect-libc@1.0.3 but the polyfilled version is 2.x. In detect-libc@2x
// there is a `familySync` function that we can use instead.
if (typeof familySync === 'function') family = familySync()
変更後(新パッチ):
let { MUSL, GLIBC, familySync } = require('detect-libc')
let family = familySync()
同様の変更が @parcel/watcher のパッチにも適用されています。
loadPackage() 関数の導入とコメントの整理
両パッチで、プラットフォーム別のバイナリ選択ロジックが loadPackage() 関数に整理されています。各パッチのコード先頭には以下のコメントが統一的に追加されており、パッチが必要な理由が明示されています。
// We need to take the `process.env.PLATFORM_LIBC` into account, and we also
// need static analysis, so we can't make use of the `parts.push(…)` approach.
function loadPackage() {
if (process.platform === 'linux') {
if (process.env.PLATFORM_LIBC === 'musl') {
return require(`lightningcss-${process.platform}-${process.arch}-musl`)
} else if (process.env.PLATFORM_LIBC === 'glibc') {
return require(`lightningcss-${process.platform}-${process.arch}-gnu`)
} else {
let { MUSL, GLIBC, familySync } = require('detect-libc')
let family = familySync()
// ...
}
}
// ...
}
アップストリームが採用している parts.push(…) による動的な require 文字列の組み立ては、Bun の静的解析を通過できないため使用できません。require() の引数を文字列リテラルで固定する必要があり、プラットフォームごとに require を明示的に記述する構造が維持されています。
生成 CSS の変化:var() フォールバック値の空白正規化
Lightning CSS 1.32.0 は、var() のフォールバック値に含まれる余分な空白を除去するようになりました。transform プロパティに使われるカスタムプロパティのフォールバック値が影響を受け、テストスナップショットが大量に更新されています。
変更前:
transform: var(--tw-rotate-x, ) var(--tw-rotate-y, ) var(--tw-rotate-z, ) var(--tw-skew-x, ) var(--tw-skew-y, );
変更後:
transform: var(--tw-rotate-x, ) var(--tw-rotate-y, ) var(--tw-rotate-z, ) var(--tw-skew-x, ) var(--tw-skew-y, );
フォールバック値のスペースが2文字から1文字に正規化されています。この変更は rotate-x・rotate-y・rotate-z・skew-x・skew-y など多数のユーティリティに影響し、utilities.test.ts で 160 箇所以上のスナップショット更新が発生しています。
セレクタのグルーピング変化::autofill の分離
Lightning CSS 1.32.0 では、:autofill 疑似クラスを含むセレクタが他のセレクタと同一ルールにまとめられなくなりました。この変更により、:autofill を使用するルールは独立したブロックとして出力されます。
変更前:
.group-autofill\:flex:is(:where(.group):autofill *), .peer-autofill\:flex:is(:where(.peer):autofill ~ *), .autofill\:flex:autofill {
display: flex;
}
変更後:
.group-autofill\:flex:is(:where(.group):autofill *), .peer-autofill\:flex:is(:where(.peer):autofill ~ *) {
display: flex;
}
.autofill\:flex:autofill {
display: flex;
}
同様の分離は variant order テストの :autofill や not バリアントの :not(:autofill) でも発生しており、variants.test.ts のスナップショットが更新されています。
設計判断
アップストリームのコード構造に追従しつつ、独自パッチを最小限に維持する方針が一貫して取られています。
アップストリームが parts.push(…) というエレガントな動的文字列構築を採用したとしても、Bun の静的解析要件と PLATFORM_LIBC 環境変数のサポートという2つの制約から、Tailwind CSS 側では require() 引数をリテラルで記述するアプローチを維持せざるを得ません。この判断はパッチのコメントに明示されており、将来のメンテナンス性を考慮した透明性のある記述といえます。
また、detect-libc@2 が確定的に使用される状況になったことで、バージョン互換のための typeof familySync === 'function' 分岐が不要になり、パッチのコードが簡潔になっています。依存側のバージョン移行が完了したことで、互換レイヤーを削除できたという好例です。
まとめ
本 PR は Lightning CSS 1.32.0 へのバージョンアップに伴い、detect-libc@2 対応のパッチ更新と生成 CSS の出力変化をセットで取り込んだ変更です。パッチの維持が必要な理由(Bun の静的解析要件と PLATFORM_LIBC 対応)をコード内コメントで明示することで、今後の同様の変更に対してメンテナンス指針を残している点が注目されます。