Appearance
Svelte 作用域化
将为每个 Svelte 组件的实用样式生成的 CSS 直接放置到 Svelte 组件的 <style>
块中,而不是放在全局 CSS 文件中。
此组件:
svelte
<div class="mb-1" />
转换为:
svelte
<div class="uno-ei382o" />
<style>
:global(.uno-ei382o) {
margin-bottom: 0.25rem;
}
</style>
何时使用
使用场景 | 描述 | 要使用的包 | |
---|---|---|---|
小型应用 | ❌ | 使用一个全局 CSS 文件更为方便。请使用适用于 Svelte/SvelteKit 的常规 Vite 插件。 | unocss/vite |
大型应用 | ✅ | Svelte 作用域化可以帮助你避免全局 CSS 文件不断增大。 | @unocss/svelte-scoped/vite |
组件库 | ✅ | 生成的样式会直接放置到构建后的组件中,无需在使用该组件的应用的构建流程中使用 UnoCSS。 | @unocss/svelte-scoped/preprocess |
工作原理
常规的 UnoCSS/Tailwind CSS 设置会将实用样式按正确顺序放置在全局 CSS 文件中。相比之下,Svelte 作用域化会将你的样式分散到许多任意排序的 Svelte 组件 CSS 文件中。但是,它必须保持实用样式的全局性,以便在需要时(如从右到左排版以及下面列出的其他使用场景)能够感知上下文。这带来了一个挑战,通过使用 Svelte 的 :global()
包装器可以解决这个问题,该包装器可以跳过 Svelte 默认的 CSS 哈希方法,而是使用基于文件名 + 类名的哈希来编译唯一的类名,这样这些类名就可以全局使用而不会产生样式冲突。
使用方法
由于 Svelte 作用域化会重写你的实用类名,因此你在编写这些类名时会受到一些限制:
支持的语法 | 示例 |
---|---|
类属性 | <div class="mb-1" /> |
类指令 | <div class:mb-1={condition} /> |
类指令简写 | <div class:logo /> |
类属性传递 | <Button class="mb-1" /> |
Svelte 作用域化旨在作为使用实用样式的项目的直接替代品。因此,类属性中的表达式也受支持(例如 <div class="mb-1 {foo ? 'mr-1' : 'mr-2'}" />
),但我们建议你今后使用类指令语法。另外请注意,如果你以其他方式使用类名,例如将它们放在 <script>
块中或使用属性化模式,那么在使用 Svelte 作用域化之前,你需要采取额外的步骤。你可以使用 safelist
选项,还可以查看下面的 预设支持 部分以获取更多提示。
上下文感知
尽管样式分布在应用的 Svelte 组件中,但它们仍然是全局类,并且可以与特定组件之外的元素产生关联。以下是一些示例:
依赖父组件
依赖于父组件中属性的类:
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>
子元素影响
你可以在 3 个子元素之间添加间距,其中一些子元素可能位于不同的组件中:
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>
将类名传递给子组件
你可以向组件添加一个 class
属性,以便在使用该组件的任何地方传递自定义类名。
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>
在接收组件中实现类的一个简单方法是使用 {$$props.class}
将它们应用到元素上,例如 div class="{$$props.class} foo bar" />
。
应用指令
你可以在 <style>
块中使用 --at-apply
、@apply
或通过 applyVariables
选项设置的自定义值来使用应用指令。
Svelte 作用域化甚至可以正确处理像 dark:text-white
这样依赖上下文的类,而常规的 @unocss/transformer-directives
包无法正确处理这些类,因为它并非专门为 Svelte 样式块构建的。例如,使用 Svelte 作用域化时,这个组件:
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
正常工作,[dir="rtl"]
选择器会被 :global()
包裹,这样做是为了防止 Svelte 编译器在组件中没有带有该属性的元素时自动将其移除。然而,div
不能包含在 :global()
包装器中,因为那样的样式会影响应用中的每个 div
。
其他样式块指令
使用 theme() 也是支持的,但 @screen 不支持。
Vite 插件
在 Svelte 或 SvelteKit 应用中,将生成的样式直接注入到 Svelte 组件中,同时将必要的最小样式放置在全局样式表中。查看 Stackblitz 上的 SvelteKit 示例:
安装
bash
pnpm add -D unocss @unocss/svelte-scoped
bash
yarn add -D unocss @unocss/svelte-scoped
bash
npm install -D unocss @unocss/svelte-scoped
bash
bun add -D unocss @unocss/svelte-scoped
添加插件
将 @unocss/svelte-scoped/vite
添加到你的 Vite 配置中:
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(),
],
})
添加配置文件
按照下面的说明设置你的 uno.config.ts
文件。
全局样式
虽然几乎所有样式都被放置到各个单独的组件中,但仍有一些样式必须放置到全局样式表中:预检样式(preflights)、安全列表(safelist)以及可选的重置样式(如果你使用 injectReset
选项)。
将 %unocss-svelte-scoped.global%
占位符添加到你的 <head>
标签中。在 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,你还必须在 src/hooks.server.js
文件的 transformPageChunk
钩子中添加以下内容:
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
}
此转换必须位于 路径中包含 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 组件库示例:
安装
bash
pnpm add -D unocss @unocss/svelte-scoped
bash
yarn add -D unocss @unocss/svelte-scoped
bash
npm install -D unocss @unocss/svelte-scoped
bash
bun add -D unocss @unocss/svelte-scoped
添加预处理器
将 @unocss/svelte-scoped/preprocess
添加到你的 Svelte 配置中:
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
}
开发环境中不要合并类名
在普通应用中使用 Svelte Scoped 时,Vite 插件会自动检测是 开发
还是 构建
环境。在开发环境中,类名将保持独立并进行哈希处理,以便在浏览器的开发者工具中轻松切换开关。class="mr-1 mb-1"
会转换为类似 class="_mb-1_9hwi32 _mr-1_84jfy4"
的形式。在生产环境中,这些类名将使用你所需的前缀(默认为 uno-
)和基于文件名 + 类名的哈希值编译成单个类名,例如 class="uno-84dke3"
。
如果你在使用预处理器时也希望实现相同的行为,则必须根据环境手动设置 combine
选项。一种实现方法是安装 cross-env 并将你的开发脚本更新为以下内容:
"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,
}),
],
}
添加配置文件
按照下面的说明设置你的 uno.config.ts
文件。
预检样式
使用预处理器时,你可以通过添加 uno-preflights
作为样式属性,将预检样式包含在需要它们的特定组件中。
html
<style uno-preflights></style>
任何以句点开头的特殊预检样式,例如 .prose :where(a):not(:where(.not-prose, .not-prose *))
,都会用 :global()
包裹,以避免被 Svelte 编译器自动移除。
如果你的类不依赖预检样式,或者你构建的组件仅在已经包含预检样式的应用中使用,那么将预检样式添加到单个组件中是没有必要的。
安全列表
使用预处理器时,你可以通过添加 uno-safelist
作为样式属性,将安全列表类包含在组件中。
html
<style uno-safelist></style>
你的安全列表样式将使用 :global()
进行包裹,以避免被 Svelte 编译器自动移除。
配置
将你的 UnoCSS 设置放在 uno.config.ts
文件中:
ts
import { defineConfig } from 'unocss'
export default defineConfig({
// ...UnoCSS options
})
由于常规 UnoCSS 全局使用方式和 Svelte 作用域化使用方式存在差异,提取器(Extractors)不受支持。预设(Presets)和转换器(Transformers)的支持情况将在后续章节中说明。有关其他详细信息,请参阅 配置文件 和 配置参考。
预设支持
由于需要将一些必要的样式放在全局样式表中,而其他所有样式则根据需要包含在每个组件中,因此预设需要根据具体情况进行处理:
预设 | 支持情况 | 说明 |
---|---|---|
✅ | 这些预设以及所有社区插件,例如 unocss-preset-forms,只要仅依赖规则、变体和预检样式,就可以正常工作。 | |
@unocss/preset-typography | ✅ | 由于此预设会将规则集添加到预检样式中,因此在使用此预设时,必须将 prose 类添加到安全列表中,否则预检样式将不会被触发。此预设中的其他所有类,例如 prose-pink ,都可以进行组件作用域化。 |
@unocss/preset-rem-to-px | ✅ | 此预设以及所有类似的仅修改样式输出的预设都可以正常工作。 |
@unocss/preset-attributify | - | 该预设无法正常工作。请在 Svelte 作用域化 Vite 插件之前使用 unplugin-attributify-to-class Vite 插件 (attributifyToClass({ include: [/\.svelte$/]}) )。 |
@unocss/preset-tagify | - | 添加自定义提取器的预设将无法正常工作。创建一个预处理器,将 <text-red>Hi</text-red> 转换为 <span class="text-red">Hi</span> ,然后创建一个 PR 在此处添加链接。 |
对于其他预设,如果它们不依赖传统的 class="..."
使用方式,则需要先将这些类名预处理到 class="..."
属性中。如果它们添加了像排版预设中的 .prose
类这样的预设,则需要将触发预设添加的类放在安全列表中。
转换器支持
转换器支持你的 CSS 文件(css|postcss|sass|scss|less|stylus|styl)。要使用它们,请在 vite.config.ts
文件的 cssFileTransformers
选项中添加转换器:
ts
import transformerDirectives from '@unocss/transformer-directives'
export default defineConfig({
plugins: [
UnoCSS({
cssFileTransformers: [transformerDirectives()],
}),
sveltekit(),
],
})
信息
由于 Svelte 作用域化的工作方式,转换器在 Svelte 组件中不受支持。
作用域化实用类释放创造力
关于何时可能需要使用作用域化样式的一些建议:如果你在一个大型项目中,每次使用像 .md:max-w-[50vw]
这样你知道只会用一次的类时,都会因为感觉全局样式表越来越大而感到不安,那么不妨试试这个包。不敢使用你确切需要的类会抑制创造力。当然,你可以在样式块中使用 --at-apply: md:max-w-[50vw]
,但这会变得很繁琐,而且上下文中的样式很有用。此外,如果你想在项目中包含大量图标,你会开始感受到将它们添加到全局样式表中的负担。当每个组件承担自己的样式和图标的负担时,你就可以继续扩展项目,而无需分析每个新添加项的成本效益。
许可协议
- MIT 许可协议 © 2022 - 至今 Jacob Bowdoin