Skip to content

ガイド ​

このセクションでは、EffCSS のインストール方法と使い方を説明します。

インストール ​

ターミナルで以下を入力してください。

sh
# npm
npm i effcss

# pnpm
pnpm add effcss

# yarn
yarn add effcss

CSSの型定義 ​

EffCSSにはCSSプロパティの自動補完機能が組み込まれているため、導入してすぐにスタイルルールを入力し始めることができます。

注意: 自動補完機能はプロパティ名を提案してくれますが、タイプミスを検知することはできません。スタイルルールには任意のキー(ネストされたセレクタやカスタムプロパティなど)を指定できるため、プロパティ名の綴りを間違えてもエラーにはなりません。これにより厳格な制約はなくなりますが、その分、入力時にはより注意を払う必要があります。

独自のプロパティを追加したり、組み込みのプロパティセットを拡張したりしたい場合(例えば、csstype の定義をすべて取り込むなど)、プロジェクト内でグローバル名前空間の拡張(global namespace augmentation)を行い、EffCSS.Properties を定義してください。

sh
npm i -D csstype

次にEffCSSと組み合わせる

ts
// env.d.ts
import type { Properties as CSSProperties } from 'csstype';

declare global {
    namespace EffCSS {
        interface Properties extends CSSProperties {}
    }
}

export {};

これで、すべてのスタイルルールにおいて、csstypeによる完全なオートコンプリートが利用できるようになります。

使用方法 ​

EffCSS でスタイルを作成するには、ユーティリティを呼び出すだけです。幸い、ユーティリティの数は非常に少なく、その名前から何をするものなのかが理解できるようになっています。

すべてのスタイルユーティリティは、デフォルトで**遅延評価(lazy)**されます。つまり、スタイルが実際に生成されるのは、リゾルバー(resolver() または resolver + '')が初めて使用されるタイミングです。スタイル内で他のトークンを参照する場合は、() => ({...}) のように関数でラップすることで、遅延評価の状態を維持できます。

それでは、各ユーティリティの具体的な使い方を詳しく見ていきましょう。

id / className ​

id は、指定された内容を含む単一のCSSルールを作成し、ID解決関数を返します。

ts
import { id } from 'effcss';

const badge = id({ backgroundColor: 'red' });
badge(); // 'f_5-0'
'' + badge; // 'f_5-0'

className は、指定された内容で単一の CSS ルールを作成し、クラス名を解決する関数を返します。

ts
import { className } from 'effcss';

const btn = className({ color: 'white' });
btn(); // 'f_6-0'
'' + btn; // 'f_6-0'

修飾子(Modifiers) ​

セレクター解決用関数(id や className)に1つ以上の修飾子を渡すことで、同一のベースルールからバリエーションを作成できます。各バリエーションには個別のトークンが割り当てられ、修飾子の組み合わせごとにキャッシュされます。擬似クラスや属性はセレクターの末尾に追加され、アットルール(@media、@container など)は、ルールを別のネストレベルでラップする形で適用されます。

ts
const box = className(() => ({ color: 'red' }));

box(); // 'f_6-0' -> `.f_6-0 { color: red; }`
box(':hover'); // 'f_6-1' -> `.f_6-1:hover { color: red; }`
box('[data-state="open"]'); // 'f_6-2' -> `.f_6-2[data-state="open"] { color: red; }`
box('@media (max-width: 700px)'); // 'f_6-3' -> `@media (max-width: 700px) { .f_6-3 { ... } }`
box('@media (max-width: 700px)', ':hover'); // 'f_6-4' -> `@media (...) { .f_6-4:hover { ... } }`

同じ基本ルールが複数のバリエーションで再利用されます。条件はルールが「使用される場所」で適用されるものであり、ルール自体の定義に組み込まれているわけではありません。

遅延評価されるスタイルとトークン ​

スタイルが他のトークン(リゾルバー)を参照している場合は、それらを () => ({...}) という関数でラップしてください。すでに評価済みのトークンを通常のオブジェクトとして渡すと、遅延評価の仕組みが損なわれ、モジュールに副作用が生じてしまうため、TypeError が発生します。

ts
const size = variable('12px');

// ❌ 使用時にスローされる:ルールにリゾルバーの結果が含まれている
className({ padding: size() });

// ✅ 遅延利用:トークンは、そのルールが使用されるときにのみ解決される。
className(() => ({ padding: size() }));

定義そのものは純粋(pure)な状態に保たれます。エラーが発生するのは、リゾルバーが最初に呼び出されたとき、あるいは文字列へと強制変換されたときです。これにより /*#__PURE__*/ の意図が正しく維持され、使用されていないスタイル定義は(モジュールをクラッシュさせることなく)ツリーシェイキングによって削除されます。

このガード機能は特定のコンテキストキーに依存しません。トークンの構造(シェイプ)に基づいて判定を行うため、別のコンテキスト(あるいはグローバルコンテキスト)由来のトークンであっても検知されます。

判定はトークンの構造(<key>_<type>-<counter>)に基づいているため、単にトークンと似た形式をしているだけの値は、誤検知(フォールスポジティブ)となります。そのような値については、関数形式を使用してください。

ts
// ❌ 誤検知: `_1-x` がトークンに見える
className({ backgroundImage: 'url(icon_1-x.svg)' })();

// ✅ 関数形式は保護されていません
className(() => ({ backgroundImage: 'url(icon_1-x.svg)' }))();

これは、className、id、style、animation といったトークンを含むオブジェクトを受け取るすべてのユーティリティに適用されます。

ネイティブCSSネスト ​

すべてのスタイルオブジェクトでセレクターのネストがサポートされているため、擬似クラス、属性、子要素、コンビネーターなどを追加できます。ここで & は、現在のセレクターを参照します。

ts
const card = className({
    padding: '1rem',
    '&:hover': { boxShadow: '0 0 8px' }, // `.f_6-0:hover`
    '&[data-active]': { borderColor: 'blue' }, // `.f_6-0[data-active]`
    '& > .title': { fontWeight: 600 } // `.f_6-0 > .title`
});

関数形式内では、セレクター(トークン)全体をネストされたキーとして渡すことさえ可能です。これらは、ルールが使用される際に遅延評価(レイジー評価)されます。

ts
const card = className(() => ({
    padding: '1rem',
    ['& > ' + btn]: { marginTop: '1rem' } // `& > .f_6-0`
}));

variable ​

variable は、単一の CSS @property ルールを作成し、変数を解決する関数を返します。

ts
import { variable } from 'effcss';

const accent = variable('#ff0');
'' + accent; // '--f_2-0'
accent(); // 'var(--f_2-0)'
accent('green'); // 'var(--f_2-0, green)'

設定オブジェクトを渡すことで、syntax、inherits、および initialValue を指定できます。

ts
const size = variable({ initialValue: '12px', syntax: '<length>', inherits: false });

対応するメソッドを使用することで、変数の初期値を取得したり設定したりできます。

ts
const size = variable('12px');
size(); // 変数を具体化する
size.get(); // '12px'
size.set('24px'); // @property ルールを更新する
size.set((prev) => prev.replace('px', 'rem')); // 関数による更新

INFO

setメソッドおよびgetメソッドは、変数が実体化(resolver()の呼び出し、またはresolver + ''による評価)された後にのみ機能します。それ以前の状態では、get()は空文字列を返し、set()は何の影響も及ぼしません。

animation ​

animation は、単一の CSS @keyframes ルールを作成し、キーフレーム名を解決する関数を返します。

ts
import { animation } from 'effcss';

const spin = animation({ from: { transform: 'rotate(0)' }, to: { transform: 'rotate(360deg)' } });
spin(); // 'f_3-0'
'' + spin; // 'f_3-0'

// キーフレームがEffCSSトークンを参照する場合の関数形式
const grow = animation(() => ({ from: { height: size() }, to: { height: '100px' } }));
grow(); // 'f_3-1'

container ​

container は、単一の CSS @container ルールを生成します。文字列に変換すると @container ルールが得られ、関数として呼び出すと name / type が得られます。

ts
import { container } from 'effcss';

const card = container('inline-size');
'' + card; // '@container f_4-0'
card(); // 'f_4-0 / inline-size'

// 型が指定されていない場合は、デフォルトが使用されます。
const plain = container();
'' + plain; // '@container f_4-1'
plain(); // 'f_4-1 / normal'

font ​

font は単一の CSS @font-face ルールを生成し、フォントファミリー名(および必要に応じてフォールバック)のみを含む、呼び出し可能な文字列を返します。

ts
import { font } from 'effcss';

const Inter = font({ src: "url('/inter.woff2')" });
Inter(); // 'f_1-0'
Inter('sans-serif'); // 'f_1-0, sans-serif'
Inter('Arial', 'sans-serif'); // 'f_1-0, Arial, sans-serif'

layers ​

layers は、渡されたすべてのレイヤーを含む単一の CSS @layer 宣言ルールを作成します。また、レイヤー名ごとに1つずつ、レイヤー解決関数を含むオブジェクトを返します。

ts
import { layers } from 'effcss';

const myLayers = layers(['reset', 'base']);
myLayers.reset(); // '@layer f_0-0'
'' + myLayers.reset; // '@layer f_0-0'
myLayers.base(); // '@layer f_0-1'

style ​

style は任意のスタイルを作成し、それらが最初に使用される際にスタイルを注入する、空文字列を返すリゾルバー関数を生成します。

ts
import { style } from 'effcss';

const custom = style({ body: { margin: 0 } });
custom(); // '' (スタイルが挿入されます)
'' + custom; // ''

// スタイル内でトークンを使用する場合は、関数を使用します。
const themed = style(() => ({ body: { color: accent(), ['& > ' + btn]: { marginTop: '1rem' } } }));
themed(); // ''

style は、ネイティブな CSS ネスティングおよび特殊な値をサポートしています。

ts
// 配列の値は宣言へとフラット化される
style(() => ({ body: { margin: ['0', 'auto'] } }));

// オブジェクトの配列からは、同じキーを持つ複数のルールが生成される
// (どの階層でも機能する)
style({
    '@font-face': [
        { fontFamily: '"Bitstream Vera Serif Bold"', src: 'url("fira.woff2")' },
        { fontFamily: '"MyHelvetica"', src: 'local("Helvetica Neue Bold")', fontWeight: 'bold' }
    ]
});

// 空の文字列値は、裸のatルール宣言を出力します。
style(() => ({ '@layer custom': '' })); // @layer custom;

// キャメルケースのプロパティはケバブケースです
style(() => ({ body: { fontFamily: 'serif' } }));

theme ​

theme は、変数とその値をテーマバリエーションとしてまとめることで、動的なスタイル切り替えを簡素化します。この関数は、vars、list、get、および set メソッドを備えたコントローラーオブジェクトを返します。

ts
import { theme } from 'effcss';

const myTheme = theme({
    vars: { bg: '#eff', fg: 'black' },
    options: {
        light: { bg: '#eff', fg: 'black' },
        dark: { bg: '#111', fg: 'white' }
    },
    initial: 'light'
});

myTheme.get(); // 'light'
myTheme.set('dark'); // true
myTheme.get(); // 'dark'

myTheme.vars.bg(); // 'var(--f_2-0)'
myTheme.vars.bg.get(); // '#111'
myTheme.vars.bg.set('#eee'); // override a single variable

serialize ​

serialize は、作成されたすべてのスタイルシートを、<style> タグからなる HTML 文字列に変換します。

ts
import { className, serialize } from 'effcss';

className({ color: 'white' })();

// 生成された `<style>` タグを HTML にレンダリングする
const html = serialize();
// '<style data-effcss-key="f-6" data-effcss-count="1">.f_6-0 { color: white; }</style>'

このようにすれば、スタイルをサーバー側で準備し、クライアント側で再利用できるようになります。これは特にSSGやSSRにおいて有用です。

CSR(クライアントサイドレンダリング)には、vite-plugin-effcss (v2) を利用できます。このプラグインは、EffCSSによって生成されたCSSをViteのSSRパイプライン経由で取得し、HTML内にインライン展開します。これにより、FOUC(Flash of Unstyled Content:スタイルが適用される前のコンテンツが一瞬表示されてしまう現象)を防ぎ、スタイルハイドレーションによって初回レンダリングを高速化することが可能です。開発モードとビルドモードの両方で動作し、追加の依存関係は一切不要です(esbuild、tsx、その他のローダーなどは必要ありません)。

ts
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import effcss from 'vite-plugin-effcss';

export default defineConfig({
  plugins: [react(), effcss()],
});

必要に応じて、HTML内に挿入先を示すプレースホルダーを追加することもできます。

html
<head>
  <!-- EFFCSS_INJECT -->
</head>

このプラグインは、以下のオプションをサポートしています。

オプション型デフォルト値説明
verbosebooleanfalse詳細なログ出力の有効化
entrystring—明示的なエントリーポイント(例: /src/main.tsx)。省略した場合は、index.html 内の <script type="module" src="..."> から自動的に検出されます。
placeholderstring'<!-- EFFCSS_INJECT -->'<style> タグに置き換えられる HTML プレースホルダー
injectTo'head' | 'head-prepend''head'プレースホルダーが見つからない場合に <style> タグを挿入する位置

setConfig / getConfig ​

getConfig は、グローバルスタイルコンテキストの現在の設定を返します。setConfig は設定を変更するものであり、最初のスタイルシートが作成される前に呼び出された場合には、スタイル生成に影響を及ぼします。

ts
import { setConfig, getConfig } from 'effcss';

setConfig({ key: 'app' }); // 最初に使用する前に呼び出す必要がある
getConfig(); // { key: 'app' }

setConfig は、設定が適用された場合は true を返し、プロバイダーがすでに使用されている場合は false を返します。キーは英字で始まる必要があり、英字、数字、_、または - のみで構成されている必要があります。

clear ​

clear は、生成されたすべてのスタイルを削除し、カウンターをリセットします。

ts
import { clear } from 'effcss';

clear();

context ​

context を使用すると、スタイルを別のグループとして作成できます。このメソッドは、context 自体を除くすべての EffCSS ユーティリティを含むオブジェクトを返します。各コンテキストは固有のキー、カウンター、およびスタイルシートを保持しているため、コンテキスト間でトークンが競合することはありません。

ts
import { context } from 'effcss';

const ctx = context({ key: 'app' });
const btn = ctx.className({ color: 'white' });
btn(); // 'app_6-0'

ctx.serialize(); // '<style data-effcss-key="app-6" data-effcss-count="1">...</style>'
ctx.getConfig(); // { key: 'app' }
ctx.clear(); // スタイルを消去し、コンテキストを利用可能な状態に保つ
ctx.dispose(); // コンテキストを恒久的に破壊する

getContexts は、すべてのアクティブなローカルコンテキストを、それぞれのキーをキーとして返します。破棄済みまたはガベージコレクションされたコンテキストは除外されます(参照は弱参照として保持されます)。

ts
import { getContexts } from 'effcss';

const ctx = context({ key: 'app' });
getContexts(); // { app: ctx }

context スタイルは個別にシリアライズおよびハイドレートが可能なため、この機能を利用してマイクロフロントエンド間でスタイルを分離することができます。また、コンテキストは Symbol.dispose を実装しているため、using 文で使用することも可能です。

Apache License 2.0に基づいて公開されています。