Skip to content

Svelte 作用域

🌐 Svelte Scoped

将为每个 Svelte 组件生成的实用样式 CSS 直接放入 Svelte 组件的 <style> 块中,而不是放在全局 CSS 文件中。

🌐 Place generated CSS for each Svelte component's utility styles directly into the Svelte component's <style> block instead of in a global CSS file.

该组件:

🌐 This component:

svelte
<div class="mb-1" />

转化为:

🌐 is transformed into:

svelte
<div class="uno-ei382o" />

<style>
  :global(.uno-ei382o) {
    margin-bottom: 0.25rem;
  }
</style>

何时使用

🌐 When to use

Use CaseDescriptionPackage to Use
Smaller appsHaving 1 global CSS file is more convenient. Use the regular Vite plugin for Svelte/SvelteKit.unocss/vite
Larger appsSvelte Scoped can help you avoid an ever-growing global CSS file.@unocss/svelte-scoped/vite
Component libraryGenerated styles are placed directly in built components without the need to use UnoCSS in a consuming app's build pipeline.@unocss/svelte-scoped/preprocess

怎么运行的

🌐 How it works

常规的 UnoCSS/Tailwind CSS 设置会将实用样式放在具有适当顺序的全局 CSS 文件中。相比之下,Svelte Scoped 会将你的样式分布在许多顺序任意的 Svelte 组件 CSS 文件中。然而,它必须保持实用样式为全局,以便在需要时(例如从右到左布局和下面列出的其他使用场景)能够感知上下文。这带来了一个挑战,可以通过使用 Svelte 的 :global() 封装器来解决,从而选择性地退出默认的 Svelte CSS 哈希方法,而是使用基于文件名 + 类名的哈希来编译唯一类名,使其可以在全局使用而不会产生样式冲突。

🌐 A regular UnoCSS/Tailwind CSS setup places utility styles in a global CSS file with proper ordering. In contrast, Svelte Scoped distributes your styles across many arbitrarily ordered Svelte component CSS files. However, it must keep the utility styles global to allow them to be context aware as needed for things like right-to-left and other use cases listed below. This presents a challenge that is solved by using Svelte's :global() wrapper to opt out of the default Svelte CSS hashing method and instead use a hash based on filename + class name(s) to compile unique class names that can be made global without style conflicts.

用法

🌐 Usage

由于 Svelte Scoped 会重写你的工具类名称,因此你可以在何处编写它们:

🌐 Because Svelte Scoped rewrites your utility class names, you are limited in where you can write them:

支持的语法示例
类属性<div class="mb-1" />
类指令<div class:mb-1={condition} />
类指令简写<div class:logo />
类属性传递<Button class="mb-1" />
类似 clsx<div class={["mb-1", { logo, 'font-bold': isBold() }, isUnderlined() && 'underline' ]} />

Svelte Scoped 旨在作为使用实用样式的项目的替代方案。因此,类属性中的表达式也是支持的(例如 <div class="mb-1 {foo ? 'mr-1' : 'mr-2'}" />),但我们建议你今后使用 clsx 语法。同时请注意,如果你以其他方式使用过类名,例如将它们放在 <script> 块中或使用 attributify 模式,那么在使用 Svelte Scoped 之前需要采取额外的步骤。你可以使用 safelist 选项,也可以查看下面的 presets 部分以获取更多提示。

🌐 Svelte Scoped is designed to be a drop-in replacement for a project that uses utility styles. As such, expressions found within class attributes are also supported (e.g. <div class="mb-1 {foo ? 'mr-1' : 'mr-2'}" />) but we recommend you use the clsx syntax moving forward. Note also that if you've used class names in other ways like placing them in a <script> block or using attributify mode then you'll need to take additional steps before using Svelte Scoped. You can utilize the safelist option and also check the presets section below for more tips.

上下文感知

🌐 Context aware

尽管样式分布在应用的 Svelte 组件中,它们仍然是全局类,并且会与特定组件之外的元素相互作用。下面是一些示例:

🌐 Even though styles are distributed across your app's Svelte components, they are still global classes and will work in relationship to elements found outside of their specific components. Here are some examples:

父级依赖

🌐 Parent dependent

依赖于父组件中的属性的类:

🌐 Classes that depend on attributes found in a parent component:

svelte
<div class="dark:mb-2 rtl:right-0"></div>

变成:

🌐 turn into:

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 个子元素之间添加空格,其中一些子元素位于单独的组件中:

🌐 You can add space between 3 children elements of which some are in separate components:

svelte
<div class="space-x-1">
  <div>Status: online</div>
  <Button>FAQ</Button>
  <Button>Login</Button>
</div>

变成:

🌐 turns into:

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 属性,以便在使用该组件的任何地方传递自定义类。

🌐 You can add a class prop to a component to allow passing custom classes wherever that component is consumed.

svelte
<Button class="px-2 py-1">Login</Button>

变成:

🌐 turns into:

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" /> 中一样。

🌐 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

你可以在 <style> 块中使用 apply 指令,使用 --at-apply@apply,或者使用 applyVariables 选项设置的自定义值。

🌐 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 甚至可以正确处理依赖上下文的类,例如 dark:text-white,而普通的 @unocss/transformer-directives 包无法正确处理这些类,因为它不是专门为 Svelte 样式块构建的。例如,使用 Svelte Scoped 时,这个组件:

🌐 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:

svelte
<div />

<style>
  div {
    --at-apply: rtl:ml-2;
  }
</style>

将转化为:

🌐 will be transformed into:

svelte
<div />

<style>
  :global([dir=\\"rtl\\"]) div {
    margin-right: 0.5rem;
  }
</style>

为了使 rtl:ml-2 正常工作,[dir="rtl"] 选择器被 :global() 封装,以防 Svelte 编译器自动剥离它,因为组件中没有带该属性的元素。然而,div 不能包含在 :global() 封装中,因为那样的话,该样式会影响你应用中的每个 div

🌐 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

也支持使用 theme(),但 @screen 支持。

🌐 Using theme() is also supported, but @screen is not.

Vite 插件

🌐 Vite Plugin

在 Svelte 或 SvelteKit 应用中,将生成的样式直接注入到你的 Svelte 组件中,同时在全局样式表中仅放置必要的最少样式。请查看 Stackblitz 中的 SvelteKit 示例

🌐 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:

在 StackBlitz 中打开

安装

🌐 Install

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

添加插件

🌐 Add plugin

@unocss/svelte-scoped/vite 添加到你的 Vite 配置中:

🌐 Add @unocss/svelte-scoped/vite to your Vite config:

vite.config.ts
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 文件。

🌐 Setup your uno.config.ts file as described below.

全局样式

🌐 Global styles

虽然几乎所有样式都放在了各自的组件中,但仍有一些必须放在全局样式表中:预处理样式(preflights)、安全列表(safelist)以及可选的重置(如果你使用 injectReset 选项)。

🌐 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).

%unocss-svelte-scoped.global% 占位符添加到你的 <head> 标签中。在 Svelte 中,这是 index.html。在 SvelteKit 中,这将在 app.html%sveltekit.head% 之前进行:

🌐 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%:

index.html
html
<head>
  <!-- ... -->
  <title>SvelteKit using UnoCSS Svelte Scoped</title>
  %unocss-svelte-scoped.global%
  %sveltekit.head%
</head>

Svelte 预处理器

🌐 Svelte Preprocessor

使用实用样式构建组件库,而无需通过包含辅助 CSS 文件来依赖它,可以使用预处理器将生成的样式直接放入构建的组件中。请查看 Stackblitz 中的 SvelteKit 库示例

🌐 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:

在 StackBlitz 中打开

安装

🌐 Install

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

添加预处理器

🌐 Add preprocessor

@unocss/svelte-scoped/preprocess 添加到你的 Svelte 配置中:

🌐 Add @unocss/svelte-scoped/preprocess to your Svelte config:

svelte.config.js
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 插件会自动检测 devbuild 的区别。在开发过程中,类会保持独立,并在原位进行哈希处理,以便在浏览器开发者工具中轻松开关。class="mb-1 mr-1" 会变成类似 class="_mb-1_9hwi32 _mr-1_84jfy4 的东西。在生产环境中,这些类会被编译成一个使用你指定前缀(默认是 uno-)的单一类名,并基于文件名 + 类名生成哈希,例如 class="uno-84dke3

🌐 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.

如果你希望在使用预处理器时获得相同的行为,你必须根据环境手动设置 combine 选项。实现这一点的一种方法是安装 cross-env,并将你的开发脚本更新为如下内容:

🌐 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"

然后调整你的 svelte.config.js:

🌐 Then adjust your svelte.config.js:

diff
+const prod = process.env.NODE_ENV !== 'development'
const config = {
  preprocess: [
    vitePreprocess(),
    UnoCSS({
+      combine: prod,
    }),
  ],
}

添加配置文件

🌐 Add config file

按照下面描述的方式设置你的 uno.config.ts 文件。

🌐 Setup your uno.config.ts file as described below.

预检

🌐 Preflights

在使用预处理器时,你可以选择在需要的特定组件中包含预检查,只需将 uno-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.

html
<style uno-preflights></style>

任何以句点开头的特殊预检,例如 .prose :where(a):not(:where(.not-prose, .not-prose *)),都会用 :global() 封装,以避免被 Svelte 编译器自动剥离。

🌐 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

使用预处理器时,你可以通过将 uno-safelist 添加为样式属性,将安全列表类包含在组件中。

🌐 When using the preprocessor you have the option to include safelist classes in a component by adding uno-safelist as a style attribute.

html
<style uno-safelist></style>

你的安全列表样式将被 :global() 封装,以避免被 Svelte 编译器自动删除。

🌐 Your safelist styles will be wrapped with :global() to avoid being automatically stripped out by the Svelte compiler.

配置

🌐 Configuration

将你的 UnoCSS 设置放在一个 uno.config.ts 文件中:

🌐 Place your UnoCSS settings in an uno.config.ts file:

uno.config.ts
ts
import { defineConfig } from 'unocss'

export default defineConfig({
  // ...UnoCSS options
})

由于普通 UnoCSS 全局用法与 Svelte Scoped 用法的差异,不支持提取器。预设和转换器按以下各节描述支持。有关其他所有详细信息,请参见 配置文件配置参考

🌐 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:

PresetSupportedNotes
@unocss/preset-uno, @unocss/preset-mini, @unocss/preset-wind3, @unocss/preset-icons, @unocss/web-fontsThese and all community plugins, e.g. unocss-preset-forms, that only rely on rules/variants/preflights will work.
@unocss/preset-typographyDue 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.
After v66.5.0, prose styles refactored into a rule, which means you don't need to add the class to your safelist anymore.
@unocss/preset-rem-to-pxThis 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.

对于其他预设,如果它们不依赖传统的 class="..." 用法,你需要先将这些类名预处理到 class="..." 属性中。如果它们添加了像排版的 .prose 类这样的预设,那么你需要将触发预设添加的类放入你的安全列表中。

🌐 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

你的 CSS 文件(css|postcss|sass|scss|less|stylus|styl)支持使用 Transformers。要使用它们,请将 Transformer 添加到你的 vite.config.ts 中的 cssFileTransformers 选项:

🌐 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:

vite.config.ts
ts
import transformerDirectives from '@unocss/transformer-directives'

export default defineConfig({
  plugins: [
    UnoCSS({
      cssFileTransformers: [transformerDirectives()],
    }),
    sveltekit(),
  ],
})

INFO

由于 Svelte Scoped 的工作原理,Svelte 组件不支持 Transformer。

作用域工具类释放创造力

🌐 Scoped utility classes unleash creativity

关于何时可能想使用作用域样式的一些建议:如果你已经到了这样一个阶段,在一个大型项目中,每次使用像 .md:max-w-[50vw] 这样的类时你都会感到不适,因为你知道这个类只使用过一次,而全局样式表的体积也在不断增加,那么可以尝试使用这个包。犹豫是否使用你真正需要的类会抑制创造力。当然,你可以在样式块中使用 --at-apply: md:max-w-[50vw],但那会变得单调,而且上下文中的样式是非常有用的。此外,如果你想在项目中包含各种各样的图标,你会开始感受到将它们添加到全局样式表的负担。当每个组件都承担自己的样式和图标时,你可以继续扩展项目,而不必分析每个新添加内容的成本效益。

🌐 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