English

hypersig_style クレートの hypersig::style

Rust で書く、型付きの atomic CSS

標準の CSS プロパティはすべてメソッドで、キーワードはすべてバリアントです。プロパティの打ち間違いや型の違う値はコンパイルエラーになり、宣言 1 つが 1 つのクラスになります。

MDN のデータから生成

プロパティ、そのキーワード、擬似クラス、名前付きの色は MDN の CSS データから作るので、メソッドは標準に沿います。

宣言 1 つにクラス 1 つ

Panda CSS や Tailwind CSS と同じく、宣言 1 つが 1 つのクラスになり、ページは要素が使うルールだけを書き出します。

シグナルに合わせて変わる

css_when と css_match はページのシグナルに合わせてスタイルを切り替えます。差分には足す要素のルールも含まれます。

シグナルから決まるトークン

色見本は 1 つのトークンを読み、bind_token がラジオグループのシグナルからそのトークンを設定します。詳しくは トークンとテーマ で説明します。

実行結果

accent.rs
use hypersig::html::div;
use hypersig::style::pattern::{stack, wrap};
use hypersig::style::value::{Color, px, rem};
use hypersig::style::{Style, Token};
use hypersig::{Page, Signals};
use hypersig_ui::{ButtonSize, ButtonVariant, button, radio_group, token};
use serde::{Deserialize, Serialize};

#[derive(Signals, Serialize, Deserialize)]
pub struct Palette {
    accent: String,
}

pub fn page() -> Page<Palette> {
    let accent = Token::new("accent-demo", Color::hex(0x0025_63eb));
    let swatch = Style::new()
        .width(rem(3))
        .height(rem(3))
        .border_radius(px(8))
        .background_color(accent.var());
    Page::new(
        "パレット",
        Palette {
            accent: "#2563eb".into(),
        },
    )
    .child(
        div()
            .css(stack(rem(1)))
            .bind_token(&accent, Palette::accent())
            .child(radio_group(
                "アクセント",
                Palette::accent(),
                [
                    (
                        "#2563eb".to_owned(),
                        "青",
                    ),
                    (
                        "#16a34a".to_owned(),
                        "緑",
                    ),
                    (
                        "#e11d48".to_owned(),
                        "ローズ",
                    ),
                ],
            ))
            .child(
                div()
                    .css(wrap(rem(0.75)))
                    .child(div().css(swatch.clone()))
                    .child(div().css(swatch.border_radius(px(999))))
                    .child(
                        button(ButtonVariant::Default, ButtonSize::Default)
                            // The button reads `primary`, which takes the accent here.
                            .css(Style::new().token(&token::primary(), accent.var()))
                            .text("アクセントのボタン"),
                    ),
            ),
    )
}

ベンチマーク

hypersig のフレームワークのベンチマーク にある 2000 件の TodoMVC のページを、6 通りのやり方でスタイルし、同じマークアップで hypersig に描かせたものです。違うのはクラス名と CSS だけです。Apple M4 Max の Chrome 155 で、CPU を 4 倍に減速して測りました。ブラウザの値は 5 回、サーバーで描く時間は 50 回の中央値です。

hypersigTailwind CSSPanda CSSStyleXvanilla-extract手書きの CSS
CSS(gzip)1.0 KB1.6 KB1.0 KB1.0 KB0.6 KB0.8 KB
CSS を除いた HTML(gzip)30.2 KB29.1 KB26.9 KB26.8 KB27.6 KB27.3 KB
送る合計(gzip)31.5 KB30.7 KB27.8 KB27.8 KB28.2 KB28.1 KB
CSS のビルドなし268 ms574 ms182 ms74 ms3.1 ms
サーバーで描く時間1.7 ms1.5 ms1.4 ms1.4 ms0.8 ms0.8 ms
First Contentful Paint136 ms140 ms136 ms144 ms140 ms140 ms
読み込み時のスタイル計算とレイアウト76 ms74 ms73 ms81 ms67 ms68 ms
2000 件を完了にする時間27 ms37 ms26 ms27 ms24 ms24 ms
hypersig が不利な点理由
HTML の大きさクラス名を宣言だけから作るので長くなります。名前は条件、プロパティの正式な名前、値をつないだ font-size_24px のような形で、空白など英数字と .、#、%、- 以外の文字を含む値は 16 桁のハッシュになります。サーバーのパッチはページにあるクラスを知らずに要素を描くので、同じ宣言はいつ描いても同じ名前になる必要があります
サーバーで描く時間Page::render がその長い名前を項目ごとに書き出します。gzip 前の HTML は hypersig が 1.21 MB、Panda CSS が 0.88 MB で、描く時間も同じ順に長くなります

ページと計測の仕組みは bench/style にあります。

hypersig の一部

このクレートは hypersig::style から使い、El は css で Style を受け取ります。hypersig_ui のコンポーネントもこれでスタイルを付けています。