首頁 > web前端 > css教學 > 主體

重新思考 JS 中的 CSS

王林
發布: 2024-09-12 16:18:55
原創
1002 人瀏覽過

0. Introduction

In the world of web development, CSS is a key element in making user interfaces beautiful and functional.

However, as the complexity of web applications has increased, CSS management has become an increasingly challenging task. Style conflicts, performance degradation, and maintenance difficulties are concerns for many developers.

Are these issues hindering the progress of your projects? (image source)

Rethinking CSS in JS

This article delves deeply into new approaches to solving these problems, particularly CSS in JS.
Starting with the historical background of CSS, it covers a wide range of topics from modern styling methods to future design systems.

The structure of the article is as follows:

  1. Definition and background of CSS in JS
    • 1. What is CSS in JS?
    • 2. The background of CSS in JS
  2. Historical context of CSS and design
    • 3. The background of CSS
    • 4. The background of Design
    • 5. The background of Design System
  3. Analysis of style management methods and new proposals
    • 6. How were styles being managed?
    • 7. How should styles be managed?
  4. Specific implementation plans for CSS in JS
    • 8. Why CSS in JS?
    • 9. Introduce project mincho
    • 10. CSS-friendly CSS in JS
    • 11. Scalable CSS in JS
  5. Integration with design systems
    • 12. CSS in JS for Design Systems

In particular, this article introduces new concepts called SCALE CSS methodology and StyleStack, and proposes a mincho project based on these. It aims to implement CSS in JS that is CSS-friendly and scalable.

The ultimate purpose of this article is to present the possibility of better styling solutions to developers, designers, and other web project stakeholders.

Now, let's delve deeper into the world of CSS in JS in the main text. It will be a long journey, but I hope it provides you with new inspiration and opportunities for challenge.

1. What is CSS in JS?

CSS in JS is a technique that allows you to write CSS styles directly within your JavaScript(or TypeScript) code.
Instead of creating separate CSS files, you can define styles alongside components in your JavaScript files.

/** @jsxImportSource @emotion/react */
import { css } from "@emotion/react";

const buttonStyles = (primary) => css({
  backgroundColor: primary ? "blue" : "white",
  color: primary ? "white" : "black",
  fontSize: "1em",
  padding: "0.25em 1em",
  border: "2px solid blue",
  borderRadius: "3px",
  cursor: "pointer",
});

function Button({ primary, children }) {
  return (
    <button css={buttonStyles(primary)}>
      {children}
    </button>
  );
}

function App() {
  return (
    <div>
      <Button>Normal Button</Button>
      <Button primary>Primary Button</Button>
    </div>
  );
}
登入後複製

Being able to integrate it into JavaScript certainly seems convenient ?

2. The background of CSS in JS

CSS in JS was introduced from a presentation titled 'React: CSS in JS – NationJS' by Vjeux, a Facebook developer.

The problems that CSS-in-JS aimed to solve were as follows:
Rethinking CSS in JS

What is the problem more specifically?
And how does CSS in JS solve it?

I've organized them in the following table:

Problem Solution
Global namespace Need unique class names that are not duplicated as all styles are declared globally Use Local values as default
- Creating unique class names
- Dynamic styling
Implicit Dependencies The difficulty of managing dependencies between CSS and JS
- Side effect: CSS is applied globally, so it still works if another file is already using that CSS
- Difficulty in call automation: It's not easy to statically analyze and automate CSS file calls, so developers have to manage them directly
Using the module system of JS
Dead Code Elimination Difficulty in removing unnecessary CSS during the process of adding, changing, or deleting features Utilize the optimization features of the bundler
Minification Dependencies should be identified and reduced As dependencies are identified, it becomes easier
to reduce them.
Sharing Constants Unable to share JS code and state values Use JS values as they are, or utilize CSS Variables
Non-deterministic Resolution Style priority varies depending on the CSS loading order - Specificity is automatically calculated and applied
- Compose and use the final value
Breaking Isolation Difficulty in managing external modifications to CSS (encapsulation) - Encapsulation based on components
- Styling based on state
- Prevent styles that break encapsulation, such as .selector > *

But it's not a silverbullet, and it has its drawbacks.

  1. CSS-in-JS adds runtime overhead.
  2. CSS-in-JS increases your bundle size.
  3. CSS-in-JS clutters the React DevTools.
  4. Frequently inserting CSS rules forces the browser to do a lot of extra work.
  5. With CSS-in-JS, there's a lot more that can go wrong, especially when using SSR and/or component libraries.

Aside from the DevTools issue, it appears to be mostly a performance issue.
Of course, there are CSS in JS, which overcomes these issues by extracting the CSS and making it zero runtime, but there are some tradeoffs.
Here are two examples.

  1. Co‑location: To support co-location while removing as much runtime as possible, the module graph and AST should be analyzed and build times will increase. Alternatively, there is a method of abandoning co-location and isolating on a file-by-file basis, similar to Vanilla Extract.
  2. Dynamic styling restrictions: The combination of build issues and the use of CSS Variables forces us to support only some representations, like Styling based on props in Pigment CSS, or learn to do things differently, like Coming from Emotion or styled-components. Dynamicity is also one of the main metrics that can be used to distinguish between CSS in JS.

Therefore, pursuing zero(or near-zero) runtime in CSS-in-JS implementation methods creates a significant difference in terms of expressiveness and API.

3. The background of CSS

3.1 The Beginning of CSS

Where did CSS come from?
Early web pages were composed only of HTML, with very limited styling options.

<p><font color="red">This text is red.</font></p>
<p>This is <strong>emphasized</strong> text.</p>
<p>This is <em>italicized</em> text.</p>
<p>This is <u>underlined</u> text.</p>
<p>This is <strike>strikethrough</strike> text.</p>
<p>This is <big>big</big> text, and this is <small>small</small> text.</p>
<p>H<sub>2</sub>O is the chemical formula for water.</p>
<p>2<sup>3</sup> is 8.</p>
登入後複製

For example, the font tag could change color and size, but it couldn't adjust letter spacing, line height, margins, and so on.

You might think, "Why not just extend HTML tags?" However, it's difficult to create tags for all styling options, and when changing designs, you'd have to modify the HTML structure itself.
This deviates from HTML's original purpose as a document markup language and also means that it's hard to style dynamically.

If you want to change an underline to a strikethrough at runtime, you'd have to create a strike element, clone the inner elements, and then replace them.

const strikeElement = document.createElement("strike");
strikeElement.innerHTML = uElement.innerHTML;
uElement.parentNode.replaceChild(strikeElement, uElement);
登入後複製

When separated by style, you only need to change the attributes.

element.style.textDecoration = "line-through";
登入後複製

If you convert to inline style, it would be as follows:

<p style="color: red;">This text is red.</p>
<p>This is <span style="font-weight: bold;">bold</span> text.</p>
<p>This is <span style="font-style: italic;">italic</span> text.</p>
<p>This is <span style="text-decoration: underline;">underlined</span> text.</p>
<p>This is <span style="text-decoration: line-through;">strikethrough</span> text.</p>
<p>This is <span style="font-size: larger;">large</span> text, and this is <span style="font-size: smaller;">small</span> text.</p>
<p>H<span style="vertical-align: sub; font-size: smaller;">2</span>O is the chemical formula for water.</p>
<p>2<span style="vertical-align: super; font-size: smaller;">3</span> is 8.</p>
登入後複製

However, inline style must be written repeatedly every time.
That's why CSS, which styles using selectors and declarations, was introduced.

<p>This is the <strong>important part</strong> of this sentence.</p>
<p>Hello! I want to <strong>emphasize this in red</strong></p>
<p>In a new sentence, there is still an <strong>important part</strong>.</p>

<style>
strong { color: red; text-decoration: underline; }
</style>
登入後複製

Since CSS is a method that applies multiple styles collectively, rules are needed to determine which style should take precedence when the target and style of CSS Rulesets overlap.

CSS was created with a feature called Cascade to address this issue. Cascade is a method of layering styles, starting with the simple ones and moving on to the more specific ones later. The idea was that it would be good to create a system where basic styles are first applied to the whole, and then increasingly specific styles are applied, in order to reduce repetitive work.

Therefore, CSS was designed to apply priorities differently according to the inherent specificity of CSS Rules, rather than the order in which they were written.

/* The following four codes produce the same result even if their order is changed. */
#id { color: red; }
.class { color: green; }
h1 { color: blue; }
[href] { color: yellow; }

/* Even if the order is changed, the result is the same as the above code. */
h1 { color: blue; }
#id { color: red; }
[href] { color: yellow; }
.class { color: green; }
登入後複製

However, as CSS became more scalable, a problem arose..

3.2 Scalable CSS

Despite the advancements in CSS, issues related to scalability in CSS are still being discussed.
In addition to the issues raised by CSS in JS, several other obstacles exist in CSS.

  1. Code duplication: When writing media queries, pseudo-classes, and pseudo-elements, a lot of duplication occurs if logic is required.
  2. Specificity wars: As a workaround for name collisions and non-deterministic ordering, specificity keeps raising the specificity to override the style. You can have fun reading Specificity Battle!
  3. Lack of type-safety: CSS does not work type-safely with TypeScript or Flow.

These issues can be addressed as follows:

  1. コードの重複: CSS プリプロセッサなどでネストを使用します。
  2. 特異性戦争: アトミック CSS はプロパティごとに個別に定義されるため、読み込み順序と !重要を除いて同じ特異性を持ちます。
  3. タイプ セーフの欠如: タイプ セーフ サポートを備えた JS で CSS を使用するだけです。

レイアウトの表現は CSS のもう 1 つのハードルであり、さまざまなプロパティ間の相互作用によってさらに複雑になります。
Rethinking CSS in JS

CSS は一見シンプルに見えるかもしれませんが、習得するのは簡単ではありません。単純な中央揃えでも多くの人が苦労していることはよく知られています(1、2)。 CSS は一見シンプルに見えますが、その奥深さと微妙な違いにより、見た目よりも難しくなる場合があります。

たとえば、CSS での表示には、ブロック、インライン、テーブル、フレックス、グリッドなど、さまざまなレイアウト モデルがあります
次のプロパティを組み合わせて使用​​するときの複雑さを想像してみてください: ボックス モデル、レスポンシブ デザイン、フロート、位置決め、変換、書き込みモード、マスクなど。

プロジェクトの規模が大きくなるにつれて、DOM の位置決め、カスケード、特異性に関連する副作用により、作業はさらに困難になります。

レイアウトの問題は、適切に設計された CSS フレームワークを通じて対処する必要があり、前述したように、JS で CSS を使用してスタイルを分離すると、副作用を軽減できます。

ただし、このアプローチではすべての問題が完全に解決されるわけではありません。スタイルの分離は、各コンポーネントでのスタイル宣言の重複によるファイル サイズの増加や、アプリケーション全体での共通スタイルの一貫性維持の困難など、新たな副作用を引き起こす可能性があります。
これは、次に紹介する設計の組み合わせ爆発と一貫性の問題と直接衝突します。

今のところ、私たちはレイアウトに関する懸念を Bootstrap や Bulma などのフレームワークに委任し、管理面により重点を置くことができます

4. デザインの背景

CSS は本質的に、Web 開発でデザインを表現し実装するための強力なツールです。

UI/UX を作成する際には考慮すべき要素が数多くありますが、デザインで表現するには次の要素が重要です。
Rethinking CSS in JS

  1. ビジュアルデザイン
    • レイアウト: 画面の構造と要素の配置を決定します。要素間の間隔、配置、階層を考慮してください。
    • 色: ブランド アイデンティティとユーザー エクスペリエンスを考慮したカラー パレットを選択します。色彩理論の理解が必要です。
    • タイポグラフィ: 読みやすさとブランド イメージに合ったフォントとテキスト スタイルを選択します。
    • アイコンとグラフィック要素: 直感的で一貫性のあるアイコンとグラフィックをデザインします。
  2. インタラクションデザイン
    • ボタン、スライダー、スクロールバーなどの UI 要素の動作を設計します。
    • アニメーションとトランジション効果を通じて自然なユーザー エクスペリエンスを提供します。
    • さまざまな画面サイズに適応するレスポンシブデザインを検討してください。
  3. 情報アーキテクチャ
    • ユーザーが情報を簡単に見つけて理解できるように、コンテンツの構造と階層を設計します。
    • ユーザーが目的の場所に簡単に移動できるようにナビゲーション システムを設計します。
  4. アクセシビリティと使いやすさ
    • 多様なユーザーを考慮したインクルーシブデザインを追求します。 i18n も含めることができます。
    • 直感的で使いやすいインターフェイスを作成して、ユーザーの認知的負荷を軽減します。
  5. 一貫性とスタイルガイド
    • アプリケーション全体で一貫したデザイン言語を維持するためのスタイル ガイドを作成します。
    • 効率を高めるために再利用可能なコンポーネントとパターンを開発します。

さまざまな条件下でさまざまなデザイン要素を正確に表現することは、大きな課題となります。
デバイス (電話、タブレット、ラップトップ、モニター、テレビ)、入力デバイス (キーボード、マウス、タッチ、音声)、ランドスケープ/ポートレート モード、ダーク/ライト テーマ、ハイ コントラスト モード、国際化 (言語) を考慮する必要があることを考慮してください。 、LTR/RTL) など。
さらに、ユーザー設定に基づいて異なる UI を表示する必要がある場合があります。

したがって、組み合わせ爆発は避けられず、それらを 1 つずつ手動で実装することは不可能です。 (画像出典)

Rethinking CSS in JS

代表的な例として、Firefox テーマのタブ バー レイアウトの定義とコンパイルを参照してください。
OS とユーザーのオプションのみを考慮しているにも関わらず、約 360 行のファイルのコンパイル結果は約 1400 行に達します。

結論としては、効果的な設計の実装には本質的にスケーラブルが必要であり、通常はプログラムまたは明確に定義されたルールセットを通じて管理される必要があります。
その結果、大規模な一貫した管理のための設計システムが誕生しました。

5. デザインシステムの背景

デザイン システムは、ビジュアル スタイルから UI パターン、コード実装に至るまで、デザインと開発のあらゆる側面をカバーする単一の信頼できる情報源として機能します。

Rethinking CSS in JS

Nielsen Norman Group によると、デザイン システムには次のものが含まれます。

  • スタイル ガイド: ブランド、コンテンツ、ビジュアル デザインなど、特定のスタイルのニーズに関するスタイル ガイダンスを提供するドキュメント。
  • コンポーネント ライブラリ: これらは、ボタンなどの再利用可能な個別の UI 要素を指定します。各 UI 要素について、カスタマイズ可能な属性 (サイズ、コピーなど)、さまざまな状態 (有効、ホバー、フォーカス、無効)、およびそれぞれの再利用可能なクリーンでタイトなコードなどの情報を含む、特定の設計と実装の詳細が提供されます。要素。
  • パターン ライブラリ: これらは、再利用可能なパターン、つまりコンポーネント ライブラリから取得された個々の UI 要素のグループを指定します。たとえば、タイトル、パンくずリスト、検索、主ボタンと副ボタンで構成されるページ ヘッダーのパターンが表示される場合があります。
  • 設計リソース: 設計者がコンポーネントとライブラリを実際に使用および設計するには、設計ファイルが必要です (通常は Figma 内)。通常、デザイナーや開発者が使用できるように、ロゴ、書体、フォント、アイコンなどのリソースも含まれています。

デザイン システムは、機能フォームアクセシビリティ、および カスタマイズをサポートし、デザイナーと開発者にとっての交差点として機能する必要があります。
しかし、デザイナーと開発者は考え方が異なり、異なる視点を持っています。

コンポーネントをレンズとして、設計者と開発者の視点の違いを認識しましょう!!

5.1 コンポーネントの構造

デザイナーは、チェックボックス コントロールにどのアイコンを使用するかを決定する必要もあります。
Rethinking CSS in JS

デザイナーは形状に注目する傾向があり、開発者は機能に注目する傾向があります。
デザイナーにとって、ボタンは押したくなるような見た目であればボタンですが、開発者にとっては、押せる限りボタンです。

コンポーネントがより複雑であれば、設計者と開発者の間の溝はさらに広がる可能性があります。

Rethinking CSS in JS

5.2 設計者の考慮事項

  • ビジュアルオプション: プライマリ、アクセント、アウトライン、テキストのみなどの設定されたオプションに従って外観が変わります。
    Rethinking CSS in JS

  • 状態オプション: 外観は状態とコンテキストに応じて変化します
    Rethinking CSS in JS

  • デザインの決定: コンポーネント構造、ビジュアル/状態オプション、ビジュアル属性 (色、タイポグラフィ、アイコンなど) などの値を決定します。

5.3 開発者の考慮事項

  • オプション: 設定可能な初期値。ビジュアルオプションも含まれています。例) アウトライン、サイズ
  • 状態: ユーザーの操作に基づいて変更されます。例) ホバー、押した、フォーカス、選択(チェック)
  • イベント: 状態の変化をトリガーするアクション。例) HoverEvent、PressEvent、FocusEvent、ClickEvent
  • コンテキスト: 動作に影響を与えるコードから挿入される条件。例) 読み取り専用、無効

最終的な形式は、オプション、状態、コンテキストの組み合わせであり、前述の組み合わせ爆発が生じます。

これらのうち、Option は設計者の視点と一致しますが、State と Context は一致しません。
並列状態、階層、ガードなどを考慮して状態圧縮を実行し、設計者の視点に戻ります。
Rethinking CSS in JS

  • 有効: 無効オフ、押されたオフ、ホバーされたオフ、フォーカスされたオフ
  • ホバー: オフが無効、オフが押された、ホバーがオン
  • フォーカス中: 無効オフ、押下オフ、フォーカスオン
  • 押すと: 無効 OFF、押すと ON
  • 無効: 無効オン

6. スタイルはどのように管理されていましたか?

もうお気づきかと思いますが、高品質の UI を作成して維持するのは大変な作業です。

さまざまな状態は状態管理ライブラリでカバーされていますが、スタイルはどのように管理されていたのでしょうか?
解決策がまだ確立されていないため、方法論、ライブラリ、フレームワークが次々と登場していますが、3 つの主要なパラダイムがあります。
Rethinking CSS in JS

  1. セマンティック CSS: 要素の目的または意味に基づいてクラスを割り当てます。
  2. Atomic CSS: スタイル (ビジュアル) 属性ごとに 1 つのクラスを作成します。
  3. JS の CSS: JavaScript で記述し、コンポーネント単位で CSS を分離します。

その中でも、JS の CSS は、スタイルの表現と管理に根本的に異なるアプローチを使用するパラダイムのように感じられます。
これは、JS の CSS が メカニズム に似ているのに対し、セマンティック CSS とアトミック CSS は ポリシー に似ているためです。
この違いのため、JS の CSS は他の 2 つのアプローチとは分けて説明する必要があります。 (画像出典)

Rethinking CSS in JS

JS メカニズムでの CSS について議論するとき、CSS のプリ/ポスト プロセッサーが思い浮かぶかもしれません。
同様に、ポリシーについて話すとき、「CSS 方法論」が思い浮かぶかもしれません。

そこで、次の順序でスタイル管理方法を紹介します: JS の CSS、プロセッサー、セマンティック CSS とアトミック CSS、その他のスタイル方法論。

6.1 JS の CSS

では、JSにおけるCSSの正体は何でしょうか?
答えは上記の定義にあります。

JavaScriptで記述し、コンポーネント単位でCSSを分離します。

  1. JavaScript で書かれた CSS
  2. コンポーネントレベルでの CSS 分離

これらの中で、CSS 分離は既存の CSS に十分に適用でき、グローバル名前空間と分離破壊の問題を解決できます。
これは CSS モジュールです。

Rethinking CSS in JS

上記のCSS in JS解析記事へのリンクをもとに、機能を以下のように分類してみました。
各機能にはトレードオフがあり、これらは JS で CSS を作成する際の重要な要素です

6.1.1 統合

特に注目すべきコンテンツは

SSR(サーバーサイドレンダリング) と RSC(React Server Component) です。 これらはフロントエンドを代表するReactやNEXTが目指す方向性であり、実装に大きな影響を与える重要なものです。

  • IDE: 構文の強調表示とコード補完
  • TypeScript: タイプセーフかどうか
  • フレームワーク
    • Agnostic: フレームワークに依存せず、StyledComponent のようなライブラリは React 専用に設計されています。
    • SSR: サーバー上でレンダリングするときにスタイルを文字列として抽出し、ハイドレーションをサポートします
    • RSC: RSC はサーバー上でのみ実行されるため、クライアント側 API は使用できません。
サーバーサイドレンダリングはサーバー上でHTMLを作成してクライアントに送信するため、

文字列として抽出する必要があり、ストリーミングへの応答が必要です。スタイル付きコンポーネントの例と同様に、追加の設定が必要な場合があります。 (画像出典)

Rethinking CSS in JS

  1. Server-side style extraction
    • Should be able to extract styles as strings when rendering on the server
    • Insert extracted styles inline into HTML or create separate stylesheets
  2. Unique class name generation
    • Need a mechanism to generate unique class names to prevent class name conflicts between server and client
  3. Hydration support
    • The client should be able to recognize and reuse styles generated on the server
  4. Asynchronous rendering support
    • Should be able to apply accurate styles even in asynchronous rendering situations due to data fetching, etc.

Server components have more limitations. [1, 2]
Server and client components are separated, and dynamic styling based on props, state, and context is not possible in server components.
It should be able to extract .css files as mentioned below.

Rethinking CSS in JS

  1. Static CSS generation
    • Should be able to generate static CSS at build time
    • Should be able to apply styles without executing JavaScript at runtime
  2. Server component compatibility
    • Should be able to define styles within server components
    • Should not depend on client-side APIs
  3. Style synchronization between client and server
    • Styles generated on the server must be accurately transmitted to the client

6.1.2 Style Writing

As these are widely known issues, I will not make any further mention of them.

  • Co-location: Styles within the same file as the component?
  • Theming: Design token feature supports
  • Definition: Plain CSS string vs Style Objects
  • Nesting
    • Contextual: Utilize parent selectors using &
    • Abitrary: Whether arbitrary deep nesting is possible

6.1.3 Style Output and Apply

The notable point in the CSS output is Atomic CSS.
Styles are split and output according to each CSS property.

Rethinking CSS in JS

  • Style Ouput
    • .css file: Extraction as CSS files