依存関係の更新と`@tailwindcss-upgrade`の信頼性改善
今回のPRは複数パッケージの依存関係をまとめて更新するとともに、@tailwindcss-upgradeのマイグレーションツールにおける実際のバグ修正を含んでいます。
背景
PR本文が示す通り、この変更は「全パッケージの依存関係を最新パッチリリースに追従させる」定期的な更新です。複数の独立したIssue(#19936、#19917、#19899等)をまとめてクローズする形で実施されています。
依存関係の更新に加えて、Diffを精査すると@tailwindcss-upgradeに対する実質的なコード修正が含まれていることがわかります。キャッシュの欠如による意図しない再コンパイルと、ディレクトリ探索における無限ループの可能性という2つの問題が解消されています。
技術的な変更
依存関係の更新
各パッケージのpackage.jsonとpnpm-workspace.yamlに渡って、複数の依存関係のバージョンが引き上げられました。主な変更は以下の通りです:
-
postcss:8.5.6 → 8.5.10—package.json、@tailwindcss-postcss、@tailwindcss-upgradeで更新 -
h3:1.15.5 → 1.15.11—@tailwindcss-browserで更新 -
enhanced-resolve:5.19.0 → 5.20.1—@tailwindcss-cli、@tailwindcss-node、@tailwindcss-standaloneで更新 -
globby:15.0.0 → 16.2.0(メジャーアップデート)—@tailwindcss-upgradeで更新 -
dedent:1.7.1 → 1.7.2—@tailwindcss-postcss、@tailwindcss-upgrade、tailwindcssで更新 -
turbo:2.7.6 → 2.9.6、typescript:5.5.4 → 5.9.3、vitest:4.0.18 → 4.1.5、@playwright/test:1.58.0 → 1.59.1— ルートpackage.jsonで更新 -
bun/@types/bun:1.3.9 → 1.3.13、listhen:1.9.0 → 1.9.1—@tailwindcss-standalone、@tailwindcss-browserで更新 -
@types/node:^20.19.0 → ^22.19.17、prettier:3.8.1 → 3.8.3、vite:^8.0.0 → ^8.0.10、webpack:^5 → ^5.106.2—pnpm-workspace.yamlのカタログで更新
Stylesheetクラスへのコンパイラキャッシュ導入
Stylesheetクラスのcompiler()とdesignSystem()メソッドに、プライベートフィールドによるメモ化が導入されました。
変更前:
async compiler(): Promise<...> {
return compileAst(postCssAstToCssAst(this.root), {
base: path.dirname(this.file),
onDependency() {},
})
}
async designSystem(): Promise<...> {
return __unstable__loadDesignSystem(this.root.toString(), {
base: path.dirname(this.file),
})
}
変更後:
#compiler?: Promise<Awaited<ReturnType<typeof compileAst>> | null>
#designSystem?: Promise<Awaited<ReturnType<typeof __unstable__loadDesignSystem>> | null>
async compiler(): Promise<...> {
return (this.#compiler ??= compileAst(postCssAstToCssAst(this.root), {
base: path.dirname(this.file),
onDependency() {},
}))
}
async designSystem(): Promise<...> {
return (this.#designSystem ??= __unstable__loadDesignSystem(this.root.toString(), {
base: path.dirname(this.file),
}))
}
??=演算子による遅延初期化パターンを採用することで、同一のStylesheetインスタンスに対してcompiler()やdesignSystem()が複数回呼ばれても、コンパイル処理は初回のみ実行されます。#compilerと#designSystemフィールドの型はどちらもPromise<...>であり、解決済みの値ではなくPromiseそのものをメモ化している点が重要です。これにより、同時に複数のawait compiler()が呼び出された場合でも、コンパイル処理が二重に実行されることなく同一のPromiseが共有されます。
マイグレーション前のキャッシュウォームアップ
index.tsの実行フローに、スタイルシートのマイグレーション処理前にキャッシュを意図的に温めるステップが追加されました。
// Prime the compiler/designSystem cache against the pre-migration AST
// so the later template-migration phase sees the same v4 state. After
// this point, `sheet.root` may be mutated by `migrateStylesheet`.
if (!version.isMajor(3) && sheet.isTailwindRoot) {
await Promise.all([sheet.compiler(), sheet.designSystem()])
}
コメントが明示する通り、migrateStylesheet()がsheet.rootを変更する前の状態でコンパイラとデザインシステムを初期化し、後続のテンプレートマイグレーション処理が同じv4の状態を参照できるようにすることが目的です。
無限ループの修正
ディレクトリをたどるwhileループに、ファイルシステムのルート到達を検出する終端条件が追加されました。
変更前:
parent = path.dirname(parent)
変更後:
let nextParent = path.dirname(parent)
if (nextParent === parent) break
parent = nextParent
path.dirname('/')はルートディレクトリ'/'自身を返すため、変更前はnextParent === parentが成立する状況でdo...whileの条件式だけでループを抜けることを期待した実装でした。明示的なbreakを追加することで、ファイルシステムのルートに到達した際に確実にループを終了できるようになっています。
verbatimModuleSyntaxの無効化
@tailwindcss-node、@tailwindcss-postcss、@tailwindcss-webpack、tailwindcssの各tsconfig.jsonに、verbatimModuleSyntax: falseが追加されました。
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"verbatimModuleSyntax": false,
},
}
これは今回の依存関係更新(globbyのメジャーアップデートやtypescriptのアップデートを含む)に伴い、型定義との互換性を維持するための調整です。
設計判断
Promiseをそのままキャッシュする設計が採用されています。コンパイル処理の結果(解決済みの値)ではなくPromiseオブジェクトをフィールドに保持することで、非同期処理の重複実行を防ぐ標準的なパターンが実現されています。仮に結果の値をキャッシュする設計を採用すると、最初のawaitが完了する前に2回目の呼び出しが行われた場合にコンパイルが二重に走るリスクがありますが、Promiseをキャッシュすることでこの競合を回避しています。
無限ループの修正とキャッシュの導入はどちらも、@tailwindcss-upgradeのマイグレーション実行時の信頼性に直結する変更です。前者は異常終了しうるエッジケースの閉塞であり、後者はマイグレーション前後でコンパイラの状態が一致しないことによる誤った変換の防止という、それぞれ独立した問題への対処です。
まとめ
今回の更新は定期的な依存関係の追従を行いながら、@tailwindcss-upgradeにおいてコンパイラキャッシュの導入・無限ループの修正・マイグレーション前のキャッシュウォームアップという3つの実質的な改善を同時に取り込んでいます。依存関係の更新とバグ修正を一つのPRにまとめることで、マイグレーションツールの正確性と堅牢性が一段階引き上げられた変更といえます。