Svelte Scoped
Umieść wygenerowany CSS dla narzędziowych stylów każdego komponentu Svelte bezpośrednio w bloku <style> komponentu Svelte zamiast w globalnym pliku CSS.
Ten komponent:
<div class="mb-1" />jest transformowany do:
<div class="uno-ei382o" />
<style>
:global(.uno-ei382o) {
margin-bottom: 0.25rem;
}
</style>Kiedy używać
| Przypadek użycia | Opis | Pakiet do użycia | |
|---|---|---|---|
| Mniejsze aplikacje | ❌ | Posiadanie 1 globalnego pliku CSS jest wygodniejsze. Użyj regularnej wtyczki Vite dla Svelte/SvelteKit. | unocss/vite |
| Większe aplikacje | ✅ | Svelte Scoped może pomóc Ci uniknąć stale rosnącego globalnego pliku CSS. | @unocss/svelte-scoped/vite |
| Biblioteka komponentów | ✅ | Wygenerowane style są umieszczane bezpośrednio w zbudowanych komponentach bez potrzeby używania UnoCSS w potoku budowania aplikacji konsumującej. | @unocss/svelte-scoped/preprocess |
Jak to działa
Regularna konfiguracja UnoCSS/Tailwind CSS umieszcza style narzędziowe w globalnym pliku CSS z odpowiednim porządkiem. W przeciwieństwie do tego, Svelte Scoped dystrybuuje Twoje style w wielu dowolnie uporządkowanych plikach CSS komponentów Svelte. Jednak musi zachować style narzędziowe globalne, aby umożliwić im świadomość kontekstu w razie potrzeby dla rzeczy takich jak od prawej do lewej i innych przypadków użycia wymienionych poniżej. Stanowi to wyzwanie, które jest rozwiązane poprzez użycie owijki :global() Svelte, aby zrezygnować z domyślnej metody hashowania CSS Svelte i zamiast tego użyć hashu opartego na nazwie pliku + nazwach klas, aby skompilować unikalne nazwy klas, które mogą być globalne bez konfliktów stylów.
Użycie
Ponieważ Svelte Scoped przepisuje Twoje nazwy klas narzędziowych, jesteś ograniczony w miejscach, gdzie możesz je pisać:
| Obsługiwana składnia | Przykład |
|---|---|
| Atrybut klasy | <div class="mb-1" /> |
| Dyrektywa klasy | <div class:mb-1={condition} /> |
| Skrót dyrektywy klasy | <div class:logo /> |
| Właściwość klasy | <Button class="mb-1" /> |
Svelte Scoped jest zaprojektowany jako zamiennik "drop-in" dla projektu, który używa stylów narzędziowych. W związku z tym, wyrażenia znajdujące się w atrybutach klas są również obsługiwane (np. <div class="mb-1 {foo ? 'mr-1' : 'mr-2'}" />), ale zalecamy używanie składni dyrektywy klasy w przyszłości. Zauważ również, że jeśli używałeś nazw klas w inny sposób, umieszczając je w bloku <script> lub używając trybu attributify, będziesz musiał podjąć dodatkowe kroki przed użyciem Svelte Scoped. Możesz skorzystać z opcji safelist i sprawdzić sekcję presets poniżej, aby uzyskać więcej wskazówek.
Świadomy kontekstu
Mimo że style są dystrybuowane w komponentach Svelte Twojej aplikacji, nadal są to globalne klasy i będą działać w relacji do elementów znajdujących się poza określonymi komponentami. Oto kilka przykładów:
Zależny od rodzica
Classes that depend on attributes found in a parent component:
<div class="dark:mb-2 rtl:right-0"></div>turn into:
<div class="uno-3hashz"></div>
<style>
:global(.dark .uno-3hashz) {
margin-bottom: 0.5rem;
}
:global([dir="rtl"] .uno-3hashz) {
right: 0rem;
}
</style>Children influencing
You can add space between 3 children elements of which some are in separate components:
<div class="space-x-1">
<div>Status: online</div>
<Button>FAQ</Button>
<Button>Login</Button>
</div>turns into:
<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>Passing classes to child components
You can add a class prop to a component to allow passing custom classes wherever that component is consumed.
<Button class="px-2 py-1">Login</Button>turns into:
<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>An easy way to implement the class in a receiving component would be to place them on to an element using {$$props.class} as in div class="{$$props.class} foo bar" />.
Apply directives
You can use apply directives inside your <style> blocks with either --at-apply or @apply or a custom value set using the applyVariables option.
Svelte Scoped even properly handles context dependent classes like dark:text-white that the regular @unocss/transformer-directives package can't handle properly because it wasn't built specifically for Svelte style blocks. For example, with Svelte Scoped this component:
<div />
<style>
div {
--at-apply: rtl:ml-2;
}
</style>will be transformed into:
<div />
<style>
:global([dir=\\"rtl\\"]) div {
margin-right: 0.5rem;
}
</style>In order for rtl:ml-2 to work properly, the [dir="rtl"] selector is wrapped with :global() to keep the Svelte compiler from stripping it out automatically as the component has no element with that attribute. However, div can't be included in the :global() wrapper because that style would then affect every div in your app.
Other style block directives
Using theme() is also supported, but @screen is not.
Vite Plugin
In Svelte or SvelteKit apps, inject generated styles directly into your Svelte components, while placing the minimum necessary styles in a global stylesheet. Check out the SvelteKit example in Stackblitz:
Install
pnpm add -D unocss @unocss/svelte-scopedyarn add -D unocss @unocss/svelte-scopednpm install -D unocss @unocss/svelte-scopedbun add -D unocss @unocss/svelte-scopedAdd plugin
Add @unocss/svelte-scoped/vite to your Vite config:
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', // see type definition for all included reset options or how to pass in your own
// ...other Svelte Scoped options
}),
sveltekit(),
],
})Add config file
Setup your uno.config.ts file as described below.
Global styles
While almost all styles are placed into individual components, there are still a few that must be placed into a global stylesheet: preflights, safelist, and an optional reset (if you use the injectReset option).
Add the %unocss-svelte-scoped.global% placeholder into your <head> tag. In Svelte this is index.html. In SvelteKit this will be in app.html before %sveltekit.head%:
<head>
<!-- ... -->
<title>SvelteKit using UnoCSS Svelte Scoped</title>
%unocss-svelte-scoped.global%
%sveltekit.head%
</head>If using SvelteKit, you also must add the following to the transformPageChunk hook in your src/hooks.server.js file:
/** @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
}This transformation must be in a file whose path includes hooks and server (e.g. src/hooks.server.js, src/hooks.server.ts) as svelte-scoped will be looking in your server hooks file to replace unocss_svelte_scoped_global_styles with your global styles. Make sure to not import this transformation from another file, such as when using sequence from @sveltejs/kit/hooks.
In a regular Svelte project, Vite's transformIndexHtml hook will do this automatically.
Svelte Preprocessor
Use utility styles to build a component library that is not dependent on including a companion CSS file by using a preprocessor to place generated styles directly into built components. Check out the SvelteKit Library example in Stackblitz:
Install
pnpm add -D unocss @unocss/svelte-scopedyarn add -D unocss @unocss/svelte-scopednpm install -D unocss @unocss/svelte-scopedbun add -D unocss @unocss/svelte-scopedAdd preprocessor
Add @unocss/svelte-scoped/preprocess to your Svelte config:
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({
// ... preprocessor options
}),
],
// other Svelte config
}Don't combine class names in development
When using Svelte Scoped in a normal app, the Vite plugin will automatically detect dev vs build. In development, classes will be kept distinct and hashed in place for ease of toggling on/off in your browser's developer tools. class="mb-1 mr-1" will turn into something like class="_mb-1_9hwi32 _mr-1_84jfy4. In production, these will be compiled into a single class name using your desired prefix, uno- by default, and a hash based on the filename + class names, e.g. class="uno-84dke3.
If you want this same behavior when using the preprocessor, you must manually set the the combine option based on environemnt. One way to do this is to install cross-env and update your dev script to this:
"dev": "cross-env NODE_ENV=development vite dev"Then adjust your svelte.config.js:
+const prod = process.env.NODE_ENV !== 'development'
const config = {
preprocess: [
vitePreprocess(),
UnoCSS({
+ combine: prod,
}),
],
}Add config file
Setup your uno.config.ts file as described below.
Preflights
When using the preprocessor you have the option to include preflights in the specific component(s) where they are needed by adding uno-preflights as a style attribute.
<style uno-preflights></style>Any special preflights that start with a period, such as .prose :where(a):not(:where(.not-prose, .not-prose *)), will be wrapped with :global() to avoid being automatically stripped out by the Svelte compiler.
Adding preflights into individual components is unnecessary if your classes do not depend on preflights or your built components are being consumed only in apps that already include preflights.
Safelist
When using the preprocessor you have the option to include safelist classes in a component by adding uno-safelist as a style attribute.
<style uno-safelist></style>Your safelist styles will be wrapped with :global() to avoid being automatically stripped out by the Svelte compiler.
Configuration
Place your UnoCSS settings in an uno.config.ts file:
import { defineConfig } from 'unocss'
export default defineConfig({
// ...UnoCSS options
})Extractors are not supported due to the differences in normal UnoCSS global usage and Svelte Scoped usage. Presets and Transformers are supported as described in the following sections. See Config File and Config reference for all other details.
Presets support
Do to the nature of having a few necessary styles in a global stylesheet and everything else contained in each component where needed, presets need to be handled on a case-by-case basis:
| Preset | Supported | Notes |
|---|---|---|
| ✅ | These and all community plugins, e.g. unocss-preset-forms, that only rely on rules/variants/preflights will work. | |
| @unocss/preset-typography | ✅ | Due to how this preset adds rulesets to your preflights you must add the prose class to your safelist when using this preset, otherwise the preflights will never be triggered. All other classes from this preset, e.g. prose-pink, can be component scoped. |
| @unocss/preset-rem-to-px | ✅ | This and all presets like it that only modify style output will work. |
| @unocss/preset-attributify | - | Preset won't work. Instead use unplugin-attributify-to-class Vite plugin (attributifyToClass({ include: [/\.svelte$/]})) before the Svelte Scoped Vite plugin |
| @unocss/preset-tagify | - | Presets that add custom extractors will not work. Create a preprocessor to convert <text-red>Hi</text-red> to <span class="text-red">Hi</span>, then create a PR to add the link here. |
For other presets, if they don't rely on traditional class="..." usage you will need to first preprocess those class names into the class="..." attribute. If they add presets like typography's .prose class then you will need to place the classes which trigger the preset additions into your safelist.
Transformers support
Transformers are supported for your CSS files (css|postcss|sass|scss|less|stylus|styl). To use them, add the transformer into the cssFileTransformers option in your vite.config.ts:
import transformerDirectives from '@unocss/transformer-directives'
export default defineConfig({
plugins: [
UnoCSS({
cssFileTransformers: [transformerDirectives()],
}),
sveltekit(),
],
})INFO
Transformers are not supported in Svelte components due to how Svelte Scoped works.
Scoped utility classes unleash creativity
Some advice on when you might want to use scoped styles: If you have come to the point in a large project's life when every time you use a class like .md:max-w-[50vw] that you know is only used once you cringe as you feel the size of your global style sheet getting larger and larger, then give this package a try. Hesitancy to use exactly the class you need inhibits creativity. Sure, you could use --at-apply: md:max-w-[50vw] in the style block but that gets tedious and styles in context are useful. Furthermore, if you would like to include a great variety of icons in your project, you will begin to feel the weight of adding them to the global stylesheet. When each component bears the weight of its own styles and icons you can continue to expand your project without having to analyze the cost benefit of each new addition.
License
- MIT License © 2022-PRESENT Jacob Bowdoin