CSS-in-JSのトレードオフ

Artem Baliによる写真

最近、私はCSS-in-JSのより高いレベルの概要を書きました。主にこのアプローチが解決しようとしている問題について話しています。ライブラリの作成者は、ソリューションのトレードオフを説明するために時間を費やすことはめったにありません。偏見が強すぎるためである場合もあれば、ユーザーがツールをどのように適用するかわからない場合もあります。これは、これまで見てきたトレードオフを説明する試みです。私がJSSの著者であることに言及することは重要だと思うので、偏見があると考えるべきです。

社会的影響

Webプラットフォームで作業し、JavaScriptをまったく知らない人々の層があります。それらの人々は、HTMLとCSSを書くための報酬を得ています。 CSS-in-JSは、開発者のワークフローに大きな影響を与えました。一部の人々が取り残されていない限り、真に変革的な変化を行うことはできません。 CSS-in-JSが唯一の方法である必要があるかどうかはわかりませんが、大量採用は、現代のアプリケーションでCSSを使用する際の問題の明らかな兆候です。

問題の大部分は、CSS-in-JSが優れているユースケースと、タスクで適切に使用する方法を正確に伝えることができないことです。多くのCSS-in-JS愛好家はこの技術の宣伝に成功していますが、ツールを安く振ることなく、建設的な方法でトレードオフについて語った批評家はあまりいませんでした。その結果、多くのトレードオフを隠し、説明と回避策を提供するための強い努力をしませんでした。

CSS-in-JSは、複雑なユースケースの処理を容易にするための試みであるため、不要な場所にプッシュしないでください。

ランタイムコスト

実行時にJavaScriptからブラウザーでCSSが生成される場合、固有のオーバーヘッドがあります。ランタイムのオーバーヘッドはライブラリごとに異なります。これは優れた汎用ベンチマークですが、必ず独自のテストを作成してください。テンプレート文字列の完全なCSS解析、最適化の量、動的スタイルの実装の詳細、ハッシュアルゴリズム、フレームワーク統合コストの必要性に応じて、実行時の大きな違いが現れます。*

一部のCSS-in-JSライブラリは複数の戦略をサポートし、それらを適用するのはユーザー次第であるため、潜在的なランタイムオーバーヘッドに加えて、4つの異なるバンドル戦略を検討する必要があります。 *

戦略1:ランタイム生成のみ

ランタイムCSS生成は、JavaScriptでCSS文字列を生成し、スタイルタグを使用してその文字列をドキュメントに挿入する手法です。この手法は、インラインスタイルではなくスタイルシートを生成します。

ランタイム生成のトレードオフは、ドキュメントの読み込みが開始されるため、早い段階でスタイル付きコンテンツを提供できないことです。このアプローチは通常、すぐに役立つコンテンツのないアプリケーションに適しています。通常、このようなアプリケーションは、ユーザーにとって本当に役立つ前にユーザーとの対話を必要とします。多くの場合、このようなアプリケーションは非常に動的なコンテンツを処理するため、ロードするとすぐに古くなるため、たとえばTwitterなどの早い段階で更新パイプラインを確立する必要があります。さらに、ユーザーがログインしている場合、SEOにHTMLを提供する必要はありません。

インタラクションにJavaScriptが必要な場合、アプリの準備ができる前にバンドルをロードする必要があります。たとえば、Slackをドキュメントに読み込むときにデフォルトチャネルのコンテンツを表示できますが、ユーザーはその後すぐにチャネルを変更する可能性があります。したがって、最初のコンテンツをロードしてすぐに破棄する場合。

そのようなアプリケーションの知覚パフォーマンスは、プレースホルダーやその他のトリックを使用して、アプリケーションが実際よりも瞬時に感じるように改善できます。このようなアプリケーションは通常、とにかくデータ量が多いため、記事ほど速くは役に立ちません。

戦略2:クリティカルCSSを使用したランタイム生成

クリティカルCSSは、初期状態のページをスタイルするために必要な最小限のCSSです。ドキュメントのヘッドでスタイルタグを使用してレンダリングされます。この手法は、CSS-in-JSの有無にかかわらず広く使用されています。どちらの場合も、クリティカルCSSの一部として、またJavaScriptまたはCSSバンドルの一部として、CSSルールをダブルロードする可能性があります。クリティカルCSSのサイズは、コンテンツの量に応じて非常に大きくなる可能性があります。通常、ドキュメントはキャッシュされません。

クリティカルCSSがないと、ランタイムCSS-in-JSを備えたコンテンツが重い単一ページの静的アプリケーションは、コンテンツの代わりにプレースホルダーを表示する必要があります。これは悪いことです。なぜなら、それはずっと前からユーザーにとって有用であり、ローエンドデバイスや低帯域幅接続でのアクセシビリティを改善していたからです。

重要なCSSを使用すると、初期段階でUIをブロックすることなく、後の段階でランタイムCSSを生成できます。ただし、約5年以上前のローエンドモバイルデバイスでは、JavaScriptからのCSS生成がパフォーマンスに悪影響を及ぼす可能性があります。生成されるCSSの量と使用されるライブラリに大きく依存するため、一般化することはできません。

この戦略のトレードオフは、クリティカルCSS抽出のコストとランタイムCSS生成のコストです。

戦略3:ビルド時の抽出のみ

この戦略は、CSS-in-JSを使用しないWeb上のデフォルト戦略です。一部のCSS-in-JSライブラリを使用すると、ビルド時に静的CSSを抽出できます。*この場合、ランタイムオーバーヘッドは発生せず、リンクタグを使用してページにCSSがレンダリングされます。 CSS生成のコストは、事前に1回支払われます。

ここには2つの大きなトレードオフがあります。

  1. 状態にアクセスできないため、CSS-in-JSが提供する動的APIの一部を実行時に使用できません。 CSSカスタムプロパティはすべてのブラウザーでサポートされておらず、ビルド時に本質的にポリフィルできないため、CSSカスタムプロパティを使用できないことがよくあります。この場合、動的テーマ設定と状態ベースのスタイル設定の回避策を実行する必要があります。*
  2. Critical CSSがなく、キャッシュが空の場合、CSSバンドルがロードされるまで最初のペイントをブロックします。ドキュメントの先頭にあるリンク要素は、HTMLのレンダリングをブロックします。
  3. 単一ページアプリケーションでのページベースのバンドル分割による非決定的な特異性。*

戦略4:クリティカルCSSを使用したビルド時の抽出

この戦略は、CSS-in-JSに固有のものでもありません。重要なCSSを使用した完全な静的抽出は、より静的なアプリケーションで作業する場合に最高のパフォーマンスを提供します。このアプローチには、ブロッキングリンクタグをドキュメントの下部に移動できることを除いて、静的CSSの前述のトレードオフがあります。

4つの主要なCSSレンダリング戦略があります。それらのうち、CSS-in-JSに固有のものは2つだけで、すべてのライブラリに適用されるものはありません。

アクセシビリティ

CSS-in-JSを誤った方法で使用すると、アクセシビリティが低下する可能性があります。これは、JavaScriptバンドルが読み込まれて評価される前にHTMLをペイントできないように、Critical CSS抽出なしで大部分が静的なコンテンツサイトが実装されるときに発生します。これは、ドキュメントの先頭でブロッキングリンクタグを使用して巨大なCSSファイルがレンダリングされる場合にも発生する可能性があります。

開発者はアクセシビリティに責任を持つ必要があります。不安定なインターネット接続は、経済的に弱い国々の問題であるという強い見当違いの考えがまだあります。地下の鉄道システムや大きな建物に入ると、毎日接続の問題があることを忘れがちです。安定したケーブルのないモバイル接続は神話です。たとえば、2.4 GHzのWI-FIネットワークは電子レンジから干渉を受ける可能性があるため、安定したWiFi接続を確立することは容易ではありません。

サーバー側レンダリングを使用したクリティカルCSSのコスト

CSS-in-JSのクリティカルCSS抽出を取得するには、SSRが必要です。 SSRは、サーバー上のアプリケーションの特定の状態に対して最終的なHTMLを生成するプロセスです。実際、非常に複雑で高価なプロセスになる可能性があります。 HTTPリクエストごとにサーバーで一定量のCPUサイクルが必要です。

CSS-in-JSは通常、HTMLレンダリングパイプラインにフックされているという事実を活用します。重要なCSSは、サーバー上のHTMLレンダリングに追加のオーバーヘッドを追加します。これは、CSSも最終的なCSS文字列にコンパイルする必要があるためです。ただし、一部のシナリオでは、サーバーにキャッシュするのが難しいか不可能です。

ブラックボックスのレンダリング

使用しているCSS-in-JSライブラリがCSSをどのようにレンダリングしているかを認識する必要があります。たとえば、人々は多くの場合、Styled ComponentsとEmotionが動的スタイルを実装する方法を認識していません。動的スタイルは、スタイル宣言内でJavaScript関数を使用できる構文です。これらの関数は小道具を受け入れ、CSSブロックを返します。

ソース順序の特異性の一貫性を保つために、上記の名前付きライブラリは両方とも、動的宣言が含まれ、コンポーネントが新しいプロパティで更新される場合、新しいCSSルールを生成します。つまり、このサンドボックスを作成しました。 JSSでは、別のトレードオフを採用することにしました。これにより、新しいCSSルールを生成せずに動的プロパティを更新できます。*

急な学習曲線

CSSに精通しているがJavaScriptに慣れていない人にとっては、CSS-in-JSに慣れるための最初の作業量はかなり大きいかもしれません。

複雑なロジックが関与するまで、CSS-in-JSを記述するためにプロのJavaScript開発者である必要はありません。スタイリングの複雑さを一般化することはできません。これは、ユースケースに本当に依存するためです。 CSS-in-JSが複雑になる場合、バニラCSSを使用した実装はさらに複雑になる可能性があります。

基本的なCSS-in-JSスタイリングでは、変数の宣言方法、テンプレート文字列の使用方法、およびJavaScript値の補間方法を知る必要があります。オブジェクト表記を使用する場合は、JavaScriptオブジェクトとライブラリ固有のオブジェクトベースの構文を操作する方法を知る必要があります。動的なスタイル設定が関係する場合、JavaScript関数と条件を使用する方法を知る必要があります。

全体的に学習曲線がありますが、否定することはできません。ただし、この学習曲線は通常、Sassの学習よりもそれほど大きくありません。実際、これを実証するためにこのeggheadコースを作成しました。

相互運用性なし

ほとんどのCSS-in-JSライブラリは相互運用できません。つまり、1つのライブラリを使用して記述されたスタイルを別のライブラリを使用してレンダリングすることはできません。実際には、アプリケーション全体をある実装から別の実装に簡単に切り替えることはできません。また、CSSのビルド時の静的抽出がない限り、選択したCSS-in-JSライブラリを消費者のバンドルに入れずにNPMでUIを簡単に共有できないことも意味します。

この問題を解決することになっているISTF形式で作業を開始しましたが、残念ながら、本番環境に移行する時間はまだありません。*

再利用可能なフレームワークに依存しないUIコンポーネントをパブリックドメインで共有することは、依然として一般的に解決が難しい問題だと思います。

セキュリティリスク

CSS-in-JSでセキュリティリークが発生する可能性があります。すべてのクライアント側アプリケーションと同様に、常にレンダリングする前にユーザー入力をエスケープする必要があります。

この記事では、より多くの洞察と改ざんの例を示します。

読めないクラス名

一部の人々は、意味のある読みやすいクラス名をWeb上に保持することが重要だと考えています。現在、多くのCSS-in-JSライブラリは、開発モードでの宣言名またはコンポーネント名に基づいて意味のあるクラス名を提供します。それらの中には、クラス名ジェネレーター関数をカスタマイズできるものもあります。

しかし、本番モードでは、それらのほとんどは、より小さなペイロードに対してより短い名前を生成します。これは、ライブラリのユーザーが必要に応じてライブラリを作成およびカスタマイズする必要があるトレードオフです。

結論

トレードオフが存在し、おそらくそれらすべてについて言及しなかったでしょう。しかし、それらのほとんどはすべてのCSS-in-JSに普遍的に適用されるわけではありません。使用するライブラリとその使用方法によって異なります。

*この文を説明するには、専用の記事が必要です。 Twitter(@ oleg008)で、もっと詳しく知りたいものを教えてください。