Svelte Scoped
วาง CSS ที่สร้างขึ้นสำหรับ utility styles ของแต่ละ Svelte component โดยตรงใน <style> block ของ Svelte component แทนที่จะเป็นใน global CSS file
คอมโพเนนต์นี้:
svelte
<div class="mb-1" />จะถูกแปลงเป็น:
svelte
<div class="uno-ei382o" />
<style>
:global(.uno-ei382o) {
margin-bottom: 0.25rem;
}
</style>เมื่อไรควรใช้
| Use Case | Description | Package to Use | |
|---|---|---|---|
| Smaller apps | ❌ | การมี 1 global CSS file สะดวกกว่า ใช้ regular Vite plugin สำหรับ Svelte/SvelteKit | unocss/vite |
| Larger apps | ✅ | Svelte Scoped สามารถช่วยคุณหลีกเลี่ยง global CSS file ที่โตขึ้นเรื่อยๆ | @unocss/svelte-scoped/vite |
| Component library | ✅ | Generated styles ถูกวางโดยตรงใน built components โดยไม่ต้องใช้ UnoCSS ใน build pipeline ของแอปที่ใช้ | @unocss/svelte-scoped/preprocess |
มันทำงานอย่างไร
UnoCSS/Tailwind CSS setup ปกติวาง utility styles ใน global CSS file พร้อม ordering ที่ถูกต้อง ในทางตรงกันข้าม Svelte Scoped กระจาย styles ของคุณไปทั่ว Svelte component CSS files หลายไฟล์ที่มีลำดับไม่แน่นอน อย่างไรก็ตาม มันต้องเก็บ utility styles เป็น global เพื่อให้สามารถรับรู้ context ตามที่จำเป็นสำหรับสิ่งต่างๆ เช่น right-to-left และ use cases อื่นๆ ที่แสดงด้านล่าง นี่เป็นความท้าทายที่แก้ไขโดยใช้ wrapper :global() ของ Svelte เพื่อ opt out จาก default Svelte CSS hashing method และใช้ hash ที่อิงจาก filename + class name(s) เพื่อ compile unique class names ที่สามารถทำให้เป็น global โดยไม่มี style conflicts
การใช้งาน
เนื่องจาก Svelte Scoped เขียน class names ของคุณใหม่ คุณจำกัดในตำแหน่งที่คุณสามารถเขียนพวกมัน:
| Supported Syntax | Example |
|---|---|
| Class attribute | <div class="mb-1" /> |
| Class directive | <div class:mb-1={condition} /> |
| Class directive shorthand | <div class:logo /> |
| Class prop | <Button class="mb-1" /> |
Svelte Scoped ออกแบบมาเป็น drop-in replacement สำหรับโปรเจคที่ใช้ utility styles ดังนั้น expressions ที่พบใน class attributes ยังได้รับการรองรับ (เช่น <div class="mb-1 {foo ? 'mr-1' : 'mr-2'}" />) แต่เราแนะนำให้คุณใช้ class directive syntax ต่อไป โปรดทราบด้วยว่าหากคุณใช้ class names ในวิธีอื่นๆ เช่นการวางใน <script> block หรือใช้ attributify mode คุณจะต้องทำตามขั้นตอนเพิ่มเติมก่อนใช้ Svelte Scoped คุณสามารถใช้ตัวเลือก safelist และตรวจสอบส่วน presets ด้านล่างสำหรับเคล็ดลับเพิ่มเติม
Context aware
แม้ว่า styles จะกระจายไปทั่ว Svelte components ของแอปของคุณ พวกมันยังคงเป็น global classes และจะทำงานในความสัมพันธ์กับ elements ที่พบนอกคอมโพเนนต์เฉพาะของพวกมัน นี่คือบางตัวอย่าง:
Parent dependent
Classes ที่ขึ้นอยู่กับ attributes ที่พบใน parent component:
svelte
<div class="dark:mb-2 rtl:right-0"></div>จะกลายเป็น:
svelte
<div class="uno-3hashz"></div>
<style>
:global(.dark .uno-3hashz) {
margin-bottom: 0.5rem;
}
:global([dir="rtl"] .uno-3hashz) {
right: 0rem;
}
</style>Children influencing
คุณสามารถเพิ่มช่องว่างระหว่าง 3 children elements ซึ่งบางตัวอยู่ใน separate components:
svelte
<div class="space-x-1">
<div>Status: online</div>
<Button>FAQ</Button>
<Button>Login</Button>
</div>จะกลายเป็น:
svelte
<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
คุณสามารถเพิ่ม class prop ให้กับ component เพื่ออนุญาตให้ส่ง custom classes ได้ทุกที่ที่ component นั้นถูกใช้
svelte
<Button class="px-2 py-1">Login</Button>จะกลายเป็น:
svelte
<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>วิธีง่ายๆ ในการ implement class ใน receiving component คือการวางพวกมันบน element โดยใช้ {$$props.class} เช่น div class="{$$props.class} foo bar" />
Apply directives
คุณสามารถใช้ apply directives ใน <style> blocks ของคุณด้วย --at-apply หรือ @apply หรือค่า custom ที่ตั้งค่าโดยใช้ตัวเลือก applyVariables
Svelte Scoped ยังจัดการ context dependent classes เช่น dark:text-white ได้อย่างถูกต้องซึ่ง package @unocss/transformer-directives ปกติไม่สามารถจัดการได้อย่างถูกต้องเพราะมันไม่ได้สร้างมาโดยเฉพาะสำหรับ Svelte style blocks ตัวอย่างเช่น กับ Svelte Scoped component นี้:
svelte
<div />
<style>
div {
--at-apply: rtl:ml-2;
}
</style>จะถูกแปลงเป็น:
svelte
<div />
<style>
:global([dir=\\"rtl\\"]) div {
margin-right: 0.5rem;
}
</style>เพื่อให้ rtl:ml-2 ทำงานอย่างถูกต้อง selector [dir="rtl"] ถูกห่อด้วย :global() เพื่อป้องกัน Svelte compiler จากการ strip มันออกโดยอัตโนมัติเนื่องจาก component ไม่มี element ที่มี attribute นั้น อย่างไรก็ตาม div ไม่สามารถรวมใน wrapper :global() เพราะ style นั้นจะส่งผลต่อทุก div ในแอปของคุณ
Other style block directives
การใช้ theme() ยังได้รับการรองรับ แต่ @screen ไม่ได้รับการรองรับ
Vite Plugin
ใน Svelte หรือ SvelteKit apps ให้ inject generated styles โดยตรงใน Svelte components ของคุณ ในขณะที่วาง styles ที่จำเป็นขั้นต่ำใน global stylesheet ตรวจสอบ SvelteKit example ใน Stackblitz:
Install
bash
pnpm add -D unocss @unocss/svelte-scopedbash
yarn add -D unocss @unocss/svelte-scopedbash
npm install -D unocss @unocss/svelte-scopedbash
bun add -D unocss @unocss/svelte-scopedAdd plugin
เพิ่ม @unocss/svelte-scoped/vite ใน Vite config ของคุณ:
ts
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
ตั้งค่าไฟล์ uno.config.ts ของคุณตามที่อธิบาย ด้านล่าง
Global styles
ในขณะที่ styles เกือบทั้งหมดถูกวางใน individual components ยังมีบางตัวที่ต้องวางใน global stylesheet: preflights, safelist, และ optional reset (หากคุณใช้ตัวเลือก injectReset)
เพิ่ม placeholder %unocss-svelte-scoped.global% ใน <head> tag ของคุณ ใน Svelte นี่คือ index.html ใน SvelteKit นี่จะอยู่ใน app.html ก่อน %sveltekit.head%:
html
<head>
<!-- ... -->
<title>SvelteKit using UnoCSS Svelte Scoped</title>
%unocss-svelte-scoped.global%
%sveltekit.head%
</head>หากใช้ SvelteKit คุณยังต้องเพิ่มสิ่งต่อไปนี้ใน hook transformPageChunk ในไฟล์ src/hooks.server.js:
js
/** @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
}การแปลงนี้ต้องอยู่ในไฟล์ที่ path includes hooks and server (เช่น src/hooks.server.js, src/hooks.server.ts) เนื่องจาก svelte-scoped จะค้นหาใน server hooks file ของคุณเพื่อแทนที่ unocss_svelte_scoped_global_styles ด้วย global styles ของคุณ ตรวจสอบให้แน่ใจว่าไม่ import การแปลงนี้จากไฟล์อื่น เช่น เมื่อใช้ sequence จาก @sveltejs/kit/hooks
ใน Svelte project ปกติ Vite's transformIndexHtml hook จะทำสิ่งนี้โดยอัตโนมัติ
Svelte Preprocessor
ใช้ utility styles เพื่อสร้าง component library ที่ไม่ขึ้นอยู่กับการรวม companion CSS file โดยใช้ preprocessor เพื่อวาง generated styles โดยตรงใน built components ตรวจสอบ SvelteKit Library example ใน Stackblitz:
Install
bash
pnpm add -D unocss @unocss/svelte-scopedbash
yarn add -D unocss @unocss/svelte-scopedbash
npm install -D unocss @unocss/svelte-scopedbash
bun add -D unocss @unocss/svelte-scopedAdd preprocessor
เพิ่ม @unocss/svelte-scoped/preprocess ใน Svelte config ของคุณ:
ts
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
เมื่อใช้ Svelte Scoped ในแอปปกติ Vite plugin จะตรวจจับ dev vs build โดยอัตโนมัติ ใน development classes จะถูกเก็บแยกและ hashed ในตำแหน่งเพื่อความสะดวกในการ toggle on/off ใน developer tools ของเบราว์เซอร์ class="mb-1 mr-1" จะกลายเป็นอะไรที่คล้าย class="_mb-1_9hwi32 _mr-1_84jfy4 ใน production พวกมันจะถูก compile เป็น single class name โดยใช้ prefix ที่คุณต้องการ uno- เป็นค่าเริ่มต้น และ hash ที่อิงจาก filename + class names เช่น class="uno-84dke3
หากคุณต้องการ behavior เดียวกันนี้เมื่อใช้ preprocessor คุณต้องตั้งค่าตัวเลือก combine ด้วยตนเองตาม environment วิธีหนึ่งคือติดตั้ง cross-env และอัปเดต dev script เป็น:
"dev": "cross-env NODE_ENV=development vite dev"จากนั้นปรับ svelte.config.js:
diff
+const prod = process.env.NODE_ENV !== 'development'
const config = {
preprocess: [
vitePreprocess(),
UnoCSS({
+ combine: prod,
}),
],
}Add config file
ตั้งค่าไฟล์ uno.config.ts ของคุณตามที่อธิบาย ด้านล่าง
Preflights
เมื่อใช้ preprocessor คุณมีตัวเลือกที่จะรวม preflights ใน component(s) เฉพาะที่พวกมันจำเป็นโดยเพิ่ม uno-preflights เป็น style attribute
html
<style uno-preflights></style>Preflights พิเศษใดๆ ที่เริ่มต้นด้วย period เช่น .prose :where(a):not(:where(.not-prose, .not-prose *)) จะถูกห่อด้วย :global() เพื่อหลีกเลี่ยงการถูก strip ออกโดยอัตโนมัติโดย Svelte compiler
การเพิ่ม preflights ใน individual components ไม่จำเป็นหาก classes ของคุณไม่ขึ้นอยู่กับ preflights หรือ built components ของคุณถูกใช้เฉพาะในแอปที่มี preflights แล้ว
Safelist
เมื่อใช้ preprocessor คุณมีตัวเลือกที่จะรวม safelist classes ใน component โดยเพิ่ม uno-safelist เป็น style attribute
html
<style uno-safelist></style>Safelist styles ของคุณจะถูกห่อด้วย :global() เพื่อหลีกเลี่ยงการถูก strip ออกโดยอัตโนมัติโดย Svelte compiler
Configuration
วาง UnoCSS settings ของคุณในไฟล์ uno.config.ts:
ts
import { defineConfig } from 'unocss'
export default defineConfig({
// ...UnoCSS options
})Extractors ไม่ได้รับการรองรับเนื่องจากความแตกต่างใน normal UnoCSS global usage และ Svelte Scoped usage Presets และ Transformers ได้รับการรองรับตามที่อธิบายในส่วนต่อไปนี้ ดู Config File และ Config reference สำหรับรายละเอียดอื่นๆ ทั้งหมด
Presets support
เนื่องจากธรรมชาติของการมี styles ที่จำเป็นไม่กี่ตัวใน global stylesheet และทุกอย่างอื่นอยู่ในแต่ละ component ที่จำเป็น presets ต้องได้รับการจัดการเป็นกรณีไป:
| Preset | Supported | Notes |
|---|---|---|
| ✅ | และ community plugins ทั้งหมด เช่น unocss-preset-forms ที่พึ่งพาเฉพาะ rules/variants/preflights จะทำงานได้ | |
| @unocss/preset-typography | ✅ | เนื่องจาก preset นี้เพิ่ม rulesets ใน preflights ของคุณ คุณต้องเพิ่ม class prose ใน safelist เมื่อใช้ preset นี้ มิฉะนั้น preflights จะไม่ถูก trigger ทุกๆ classes อื่นจาก preset นี้ เช่น prose-pink สามารถเป็น component scoped ได้ |
| @unocss/preset-rem-to-px | ✅ | และ presets ทั้งหมดที่คล้ายกันที่แก้ไขเฉพาะ style output จะทำงานได้ |
| @unocss/preset-attributify | - | Preset จะไม่ทำงาน ให้ใช้ unplugin-attributify-to-class Vite plugin (attributifyToClass({ include: [/\.svelte$/]})) ก่อน Svelte Scoped Vite plugin |
| @unocss/preset-tagify | - | Presets ที่เพิ่ม custom extractors จะไม่ทำงาน สร้าง preprocessor เพื่อแปลง <text-red>Hi</text-red> เป็น <span class="text-red">Hi</span> จากนั้นสร้าง PR เพื่อเพิ่ม link ที่นี่ |
สำหรับ presets อื่นๆ หากพวกมันไม่พึ่งพาการใช้ class="..." แบบดั้งเดิม คุณจะต้อง preprocess class names เหล่านั้นเป็น attribute class="..." ก่อน หากพวกมันเพิ่ม presets เช่น class .prose ของ typography คุณจะต้องวาง classes ที่ trigger preset additions ใน safelist ของคุณ
Transformers support
Transformers ได้รับการรองรับสำหรับ CSS files ของคุณ (css|postcss|sass|scss|less|stylus|styl) เพื่อใช้พวกมัน เพิ่ม transformer ในตัวเลือก cssFileTransformers ใน vite.config.ts:
ts
import transformerDirectives from '@unocss/transformer-directives'
export default defineConfig({
plugins: [
UnoCSS({
cssFileTransformers: [transformerDirectives()],
}),
sveltekit(),
],
})INFO
Transformers ไม่ได้รับการรองรับใน Svelte components เนื่องจากวิธีที่ Svelte Scoped ทำงาน
Scoped utility classes unleash creativity
คำแนะนำบางอย่างเกี่ยวกับเมื่อคุณอาจต้องการใช้ scoped styles: หากคุณมาถึงจุดในชีวิตของโปรเจคใหญ่ที่ทุกครั้งที่คุณใช้ class เช่น .md:max-w-[50vw] ที่คุณรู้ว่าใช้เพียงครั้งเดียว คุณรู้สึกหดหู่ใจขณะรู้สึกถึงขนาดของ global style sheet ที่ใหญ่ขึ้นเรื่อยๆ ลองใช้ package นี้ ความลังเลที่จะใช้ class ที่คุณต้องการทำให้ความคิดสร้างสรรค์ลดลง แน่นอน คุณสามารถใช้ --at-apply: md:max-w-[50vw] ใน style block แต่นั่นน่าเบื่อและ styles ใน context มีประโยชน์ ยิ่งไปกว่านั้น หากคุณต้องการรวม icons หลากหลายในโปรเจคของคุณ คุณจะเริ่มรู้สึกถึงน้ำหนักของการเพิ่มพวกมันใน global stylesheet เมื่อแต่ละ component แบกรับน้ำหนักของ styles และ icons ของตัวเอง คุณสามารถขยายโปรเจคของคุณต่อไปโดยไม่ต้องวิเคราะห์ต้นทุน-ผลประโยชน์ของแต่ละสิ่งใหม่
License
- MIT License © 2022-PRESENT Jacob Bowdoin