Svelte Scoped
各Svelteコンポーネントのユーティリティスタイル用に生成されたCSSを、グローバルCSSファイルではなくSvelteコンポーネントの<style>
ブロックに直接配置します。
このコンポーネント:
<div class="mb-1" />
は次のように変換されます:
<div class="uno-ei382o" />
<style>
:global(.uno-ei382o) {
margin-bottom: 0.25rem;
}
</style>
どんな時に使うか
ユースケース | 説明 | 使用パッケージ | |
---|---|---|---|
小規模アプリ | ❌ | グローバルCSSファイル1つの方が便利です。Svelte/SvelteKit用の通常のViteプラグインを使ってください。 | unocss/vite |
大規模アプリ | ✅ | Svelte ScopedはグローバルCSSファイルの肥大化を防ぐのに役立ちます。 | @unocss/svelte-scoped/vite |
コンポーネントライブラリ | ✅ | 生成されたスタイルがビルド済みコンポーネントに直接配置されるため、利用側アプリのビルドパイプラインでUnoCSSを使う必要がありません。 | @unocss/svelte-scoped/preprocess |
仕組み
通常のUnoCSS/Tailwind CSSセットアップでは、ユーティリティスタイルはグローバルCSSファイルに適切な順序で配置されます。一方、Svelte Scopedはスタイルを多くのSvelteコンポーネントCSSファイルに分散します。ただし、右から左や下記のコンテキスト依存などの用途のため、ユーティリティスタイルはグローバルである必要があります。これを解決するため、Svelteの:global()
ラッパーを使い、デフォルトのSvelte CSSハッシュ方式を回避し、ファイル名+クラス名に基づくハッシュでユニークなクラス名をグローバル化します。
使い方
Svelte Scopedはユーティリティクラス名を書き換えるため、書ける場所が制限されます:
サポートされる構文 | 例 |
---|---|
class属性 | <div class="mb-1" /> |
classディレクティブ | <div class:mb-1={condition} /> |
classディレクティブ省略形 | <div class:logo /> |
classプロップ | <Button class="mb-1" /> |
Svelte Scopedはユーティリティスタイルを使うプロジェクトのドロップイン置換を目指しています。そのため、class属性内の式(例:<div class="mb-1 {foo ? 'mr-1' : 'mr-2'}" />
)もサポートしますが、今後はclassディレクティブ構文の利用を推奨します。<script>
ブロック内でクラス名を使ったり、attributifyモードを使っている場合は追加の対応が必要です。safelist
オプションやプリセットセクションも参照してください。
コンテキスト依存
スタイルはアプリ全体のSvelteコンポーネントに分散されますが、クラスはグローバルなので、特定コンポーネント外の要素とも連携します。例:
親依存
親コンポーネントの属性に依存するクラス:
<div class="dark:mb-2 rtl:right-0"></div>
は次のように変換されます:
<div class="uno-3hashz"></div>
<style>
:global(.dark .uno-3hashz) {
margin-bottom: 0.5rem;
}
:global([dir="rtl"] .uno-3hashz) {
right: 0rem;
}
</style>
子要素への影響
3つの子要素(いくつかは別コンポーネント)間にスペースを追加:
<div class="space-x-1">
<div>Status: online</div>
<Button>FAQ</Button>
<Button>Login</Button>
</div>
は次のように変換されます:
<div class="uno-7haszz">
<div>Status: online</div>
<Button>FAQ</Button>
<Button>Login</Button>
</div>
<style>
:global(.uno-7haszz > :not([hidden]) ~ :not([hidden])) {
--un-space-x-reverse: 0;
margin-left: calc(0.25rem * calc(1 - var(--un-space-x-reverse)));
margin-right: calc(0.25rem * var(--un-space-x-reverse));
}
</style>
子コンポーネントへのクラス伝播
コンポーネントにclass
プロップを追加して、どこで使ってもカスタムクラスを渡せます。
<Button class="px-2 py-1">Login</Button>
は次のように変換されます:
<Button class="uno-4hshza">Login</Button>
<style>
:global(.uno-4hshza) {
padding-left:0.5rem;
padding-right:0.5rem;
padding-top:0.25rem;
padding-bottom:0.25rem;
}
</style>
受け取り側コンポーネントでは、{$$props.class}
を使って要素にクラスを付与できます(例:div class="{$$props.class} foo bar" />
)。
Applyディレクティブ
<style>
ブロック内で--at-apply
や@apply
、またはapplyVariables
オプションで設定したカスタム値を使ってapplyディレクティブが利用できます。
Svelte Scopedは、dark:text-white
のようなコンテキスト依存クラスも正しく処理します(通常の@unocss/transformer-directives
パッケージはSvelteスタイルブロック向けではないため正しく処理できません)。例えば:
<div />
<style>
div {
--at-apply: rtl:ml-2;
}
</style>
は次のように変換されます:
<div />
<style>
:global([dir=\"rtl\"]) div {
margin-right: 0.5rem;
}
</style>
rtl:ml-2
を正しく動作させるため、[dir="rtl"]
セレクタは:global()
でラップされます。div
は:global()
に含めるとアプリ全体のdiv
に影響するため含めません。
その他のスタイルブロックディレクティブ
theme()もサポートされていますが、@screenは非対応です。
Viteプラグイン
SvelteやSvelteKitアプリで、生成されたスタイルをSvelteコンポーネントに直接注入し、必要最小限のスタイルのみグローバルスタイルシートに配置します。StackblitzのSvelteKitサンプルもご覧ください:
インストール
pnpm add -D unocss @unocss/svelte-scoped
yarn add -D unocss @unocss/svelte-scoped
npm install -D unocss @unocss/svelte-scoped
bun add -D unocss @unocss/svelte-scoped
プラグイン追加
Vite設定に@unocss/svelte-scoped/vite
を追加します:
import { sveltekit } from '@sveltejs/kit/vite'
import UnoCSS from '@unocss/svelte-scoped/vite'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
UnoCSS({
// injectReset: '@unocss/reset/normalize.css', // すべてのリセットオプションや独自リセットの指定方法は型定義を参照
// ...その他のSvelte Scopedオプション
}),
sveltekit(),
],
})
設定ファイル追加
uno.config.ts
ファイルを下記の通りにセットアップします。
グローバルスタイル
ほとんどのスタイルは個々のコンポーネントに配置されますが、preflights、safelist、オプションのリセット(injectReset
オプション使用時)はグローバルスタイルシートに配置する必要があります。
%unocss-svelte-scoped.global%
プレースホルダーを<head>
タグに追加します。Svelteではindex.html
、SvelteKitではapp.html
の%sveltekit.head%
の前に追加します:
<head>
<!-- ... -->
<title>SvelteKit using UnoCSS Svelte Scoped</title>
%unocss-svelte-scoped.global%
%sveltekit.head%
</head>
SvelteKitを使う場合は、src/hooks.server.js
ファイルのtransformPageChunk
フックにも以下を追加してください:
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
const response = await resolve(event, {
transformPageChunk: ({ html }) =>
html.replace(
'%unocss-svelte-scoped.global%',
'unocss_svelte_scoped_global_styles'
),
})
return response
}
この変換はパスにhooks
とserver
が含まれるファイル(例:src/hooks.server.js
やsrc/hooks.server.ts
)で行う必要があります。svelte-scoped
はサーバーフックファイル内でunocss_svelte_scoped_global_styles
をグローバルスタイルに置換します。[@sveltejs/kit/hooks]のsequenceなどで他ファイルからインポートしないよう注意してください。
通常のSvelteプロジェクトでは、ViteのtransformIndexHtml
フックが自動で処理します。
Svelteプリプロセッサ
ユーティリティスタイルを使って、コンパニオンCSSファイル不要のコンポーネントライブラリを構築できます。プリプロセッサで生成スタイルをビルド済みコンポーネントに直接配置します。StackblitzのSvelteKitライブラリサンプルもご覧ください:
インストール
pnpm add -D unocss @unocss/svelte-scoped
yarn add -D unocss @unocss/svelte-scoped
npm install -D unocss @unocss/svelte-scoped
bun add -D unocss @unocss/svelte-scoped
プリプロセッサ追加
Svelte設定に@unocss/svelte-scoped/preprocess
を追加します:
import adapter from '@sveltejs/adapter-auto'
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
import UnoCSS from '@unocss/svelte-scoped/preprocess'
const config = {
preprocess: [
vitePreprocess(),
UnoCSS({
// ... プリプロセッサオプション
}),
],
// その他のSvelte設定
}
開発時はクラス名を結合しない
Svelte Scopedを通常のアプリで使う場合、Viteプラグインは自動でdev
とbuild
を判別します。開発時はクラスが個別にハッシュ化され、ブラウザの開発者ツールで切り替えやすくなります。class="mb-1 mr-1"
はclass="_mb-1_9hwi32 _mr-1_84jfy4"
のようになります。本番では、デフォルトでuno-
プレフィックス+ファイル名+クラス名のハッシュで1つのクラス名(例:class="uno-84dke3"
)にまとめられます。
プリプロセッサ利用時も同様の挙動にしたい場合は、環境変数に応じてcombine
オプションを手動で設定してください。例えばcross-envをインストールし、devスクリプトを次のようにします:
"dev": "cross-env NODE_ENV=development vite dev"
そしてsvelte.config.js
を次のように調整します:
+const prod = process.env.NODE_ENV !== 'development'
const config = {
preprocess: [
vitePreprocess(),
UnoCSS({
+ combine: prod,
}),
],
}
設定ファイル追加
uno.config.ts
ファイルを下記の通りにセットアップします。
Preflights
プリプロセッサ利用時は、必要なコンポーネントでuno-preflights
属性付きのstyleタグを追加してpreflightsを含めることができます。
<style uno-preflights></style>
.prose :where(a):not(:where(.not-prose, .not-prose *))
のようなピリオドで始まる特殊なpreflightsは、Svelteコンパイラで自動的に除去されないよう:global()
でラップされます。
クラスがpreflightsに依存しない場合や、ビルド済みコンポーネントがpreflightsを含むアプリでのみ使われる場合は、個別コンポーネントへのpreflights追加は不要です。
Safelist
プリプロセッサ利用時は、uno-safelist
属性付きのstyleタグを追加してsafelistクラスを含めることができます。
<style uno-safelist></style>
safelistスタイルはSvelteコンパイラで自動的に除去されないよう:global()
でラップされます。
設定
UnoCSSの設定はuno.config.ts
ファイルに記述します:
import { defineConfig } from 'unocss'
export default defineConfig({
// ...UnoCSS options
})
extractorsは通常のUnoCSSグローバル利用とSvelte Scoped利用の違いによりサポートされていません。プリセットとトランスフォーマーは下記の通りサポートされています。他の詳細はConfig FileやConfig referenceを参照してください。
プリセットサポート
グローバルスタイルシートに必要なスタイルが少しだけあり、それ以外は各コンポーネントに含めるという性質上、プリセットは個別に対応が必要です:
プリセット | サポート | 備考 |
---|---|---|
✅ | これらや、rules/variants/preflightsのみに依存するコミュニティプラグイン(例:unocss-preset-forms)は動作します。 | |
@unocss/preset-typography | ✅ | このプリセットはpreflightsにルールセットを追加するため、使用時はsafelistにprose クラスを追加してください。その他のクラス(例:prose-pink )はコンポーネントスコープで利用可能です。 |
@unocss/preset-rem-to-px | ✅ | スタイル出力のみを変更するプリセットは動作します。 |
@unocss/preset-attributify | - | このプリセットは動作しません。代わりにunplugin-attributify-to-class Viteプラグイン(attributifyToClass({ include: [/\.svelte$/]}) )をSvelte Scoped Viteプラグインの前に使ってください。 |
@unocss/preset-tagify | - | カスタムextractorを追加するプリセットは動作しません。<text-red>Hi</text-red> を<span class="text-red">Hi</span> に変換するプリプロセッサを作成し、ここにリンクを追加してください。 |
他のプリセットについては、従来のclass="..."
利用に依存しない場合、まずクラス名をclass="..."
属性にプリプロセスする必要があります。typographyの.prose
クラスのようにプリセット追加がある場合は、safelistにトリガークラスを追加してください。
トランスフォーマーサポート
トランスフォーマーはCSSファイル(css|postcss|sass|scss|less|stylus|styl)でサポートされています。利用するには、vite.config.ts
のcssFileTransformers
オプションに追加してください:
import transformerDirectives from '@unocss/transformer-directives'
export default defineConfig({
plugins: [
UnoCSS({
cssFileTransformers: [transformerDirectives()],
}),
sveltekit(),
],
})
INFO
Svelteコンポーネント内ではSvelte Scopedの仕組み上、トランスフォーマーはサポートされていません。
スコープ付きユーティリティクラスで創造性を解放
グローバルスタイルシートの肥大化が気になり、例えば.md:max-w-[50vw]
のような一度しか使わないクラスを使うたびにためらいを感じるようになったら、このパッケージを試してみてください。まさに必要なクラスを使うことをためらうのは創造性の妨げです。もちろん、スタイルブロックで--at-apply: md:max-w-[50vw]
を使うこともできますが、手間がかかりますし、文脈に応じたスタイルは便利です。また、多様なアイコンをプロジェクトに含めたい場合、グローバルスタイルシートに追加する重みを感じるようになります。各コンポーネントが自身のスタイルやアイコンを持つことで、追加ごとにコストを気にせずプロジェクトを拡張できます。
ライセンス
- MIT License © 2022-PRESENT Jacob Bowdoin