前提知識
- CSS in JS: CSSをそれに対応するコンポーネントと同居させて(一般的にはJavaScriptファイル内に)書く技術の総称。コンポーネントからスタイル定義への依存関係が細かく管理でき、UIコードの凝集度も上がるメリットがある。
- CSS Extraction: CSS in JSで記述されたCSSを1つの静的なCSSファイルとして取り出すこと。
なぜLinaria / Compiledなのか
この2つはCSS Extractionに対応した現代的なCSS in JSライブラリであり、かつstyled-componentsやemotionからの移行を意識したAPIになっているからです。
CSS in JSライブラリ全般の比較については以下の記事が参考になります。
Linaria / Compiled で共通の仕組み
Linaria / Compiled ともに、JavaScriptコードのビルド時に静的解析を行ってCSSを抽出します。Reactと組み合わせて使うときの代表的なインターフェースは styled
関数といい、以下のように利用します。
import { styled } from "...";
const Button = styled.button`
background-color: red;
`;
このコードを静的解析し、各スタイル定義に一意なクラス名を割り当てます。そして以下のようなスタイル定義が生成されます。
.62i3d5CS {
background-color: red;
}
利用側ではコンポーネントにこのクラス名が設定されます。
<Button>Hello!</Button>
<button className="62i3d5CS">Hello!</button>
もしスタイルを動的に構築したければ、以下のようにタグ付きテンプレートに置換を含めることができます。
import { styled } from "...";
const Button = styled.button<{ $size: number }>`
background-color: red;
font-size: ${p => p.$size}px;
`;
ここで宣言された引数は、コンポーネント呼び出し時の属性として与えられます。
<Button $size={10}>Hello!</Button>
このようなケースに対応するために、 Linaria / Compiled ではCSS Variables (Custom Properties) を使います。置換部分のトークン (またはプロパティ値全体?) に一意なカスタムプロパティ名を割り当てます。そして以下のようなスタイル定義が生成されます。
.FS4wccCM {
background-color: red;
font-size: var(--qdu3rvWc);
}
CSS Variablesに置き換えられた部分の実際の値は、style属性経由で供給されます。置換部分の評価自体は普通にJavaScript側で行われます。
<Button $size={10}>Hello!</Button>
<button className="FS4wccCM" style={{ "--qdu3rvWc": "10px" }}>Hello!</button>
Linaria
Linariaのコア部分は別ライブラリとして切り出され、現在はwyw-in-jsと呼ばれています。これは一種のマクロ機構で、特定パッケージのインポートに対して静的解析と変換を行うメタ処理を書けるようになっているようです。 (babel-plugin-macros をBabel非依存にしたようなものと理解しておけばよさそうです)
wyw-in-jsのBabelプラグインが実行されると、Linariaのプロセッサが読み込まれ、コードはJS側とCSS側に分離されます。このCSS側のデータがどのような経路を辿るのかはパッと調べた感じでは不明ですが、Babel CLIでトランスパイルしたときに欠落するので、Babelのstateかどこかにオンメモリで保存されているのではないかと思います。
Linariaを使う場合はバンドラにもwyw-in-jsの設定を行っておきます。wyw-in-jsのプラグインはBabelプラグインから結果を受け取り、CSSの統合と出力を行います。
デザイントークン (JSで定数として定義され、CSSに埋め込まれるデータ) のインライン化にも対応していますが、どの段階で行っているかは未調査です。
特徴
- BabelとBundlerの双方の設定が必須
- ライブラリをビルドする場合
- ライブラリをビルドする場合でも、Bundlerを設定する必要がある
- 生成されたCSSを利用側でimportする必要がある (設定方法による)
Compiled
CompiledではBundler PluginまたはBabel Pluginのいずれかを使って前処理を行います。
いずれの場合も、まずソースコードには第一の解析が行われます。このフェーズでは以下が行われます。
- デザイントークン (JSで定義され、CSSに埋め込まれる定数) のインライン化。必要に応じて別ファイルも読みに行く。
- クラス名の付与。
この段階で生成されるコードは、JSに埋め込まれた静的なCSS断片をstyleタグとして埋め込むよう指示するものです。SSR時は漸進的に埋め込まれ、クライアントレンダリング時はhead内のstyleに埋め込まれます。この時点では成果物はJSファイルのみです。
次に、バンドラでCSS extractionの指示があった場合には第二の解析が行われます。ここでバンドル対象のモジュールから、上記のパターンに沿った静的なCSSが収集されます。JSコードからはCSS断片の埋め込み指令が除去され、かわりに統合されたCSSが静的なファイルとして出力されます。
特徴
- Babelプラグインなのに別のファイルを読みに行く
- キャッシュを考えると怪しい挙動になったりしないのか心配
- BabelとBundlerの片方だけの設定が有効になるように注意が必要
- ライブラリをビルドする場合
- CSS Extractionをしてもいいが、公式にはBabelで変換しただけの状態にすることが推奨されている。
- 利用側でnode_modules内も見てCSS Extractionをすることが想定されている。
所感
一長一短で悩ましいです。