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

使用案例描述封装使用
较小的应用拥有 1 个全局 CSS 文件会更方便。使用 Svelte/SvelteKit 的常规 Vite 插件。unocss/vite
更大的应用Svelte Scoped 可以帮助你避免不断增长的全局 CSS 文件。@unocss/svelte-scoped/vite
组件库生成的样式直接放置在构建的组件中,无需在使用应用的构建管道中使用 UnoCSS。@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" />

Svelte Scoped 旨在成为使用工具样式的项目的直接替代品。因此,也支持在类属性中找到的表达式(例如 <div class="mb-1 {foo ? 'mr-1' : 'mr-2'}" />),但我们建议你继续使用类指令语法。另请注意,如果你以其他方式使用类名,例如将它们放在 <script> 块中或使用属性模式,那么你需要在使用 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 class directive 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> 块内使用带有 --at-apply@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:

Open in 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

添加插件

¥Add plugin

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

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

ts
// vite.config.ts
import { defineConfig } from 'vite'
import { sveltekit } from '@sveltejs/kit/vite'
import UnoCSS from '@unocss/svelte-scoped/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

按照 below 的描述设置 uno.config.ts 文件。

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

全局样式

¥Global styles

虽然几乎所有样式都放置在单独的组件中,但仍有一些样式必须放置在全局样式表中:预检、安全列表和可选重置(如果你使用 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%:

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

如果使用 SvelteKit,你还必须将以下内容添加到 src/hooks.server.js 文件中的 transformPageChunk 钩子中:

¥If using SvelteKit, you also must add the following to the transformPageChunk hook in your src/hooks.server.js file:

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
}

此转换必须位于其 路径包括 hooksserver(例如 src/hooks.server.jssrc/hooks.server.ts)的文件中,因为 svelte-scoped 将在你的服务器钩子文件中查找,以将 unocss_svelte_scoped_global_styles 替换为你的全局样式。确保不要从其他文件导入此转换,例如使用 @sveltejs/kit/hooks 中的 sequence 时。

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

在常规的 Svelte 项目中,Vite 的 transformIndexHtml hook 会自动执行此操作。

¥In a regular Svelte project, Vite's transformIndexHtml hook will do this automatically.

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:

Open in 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

添加预处理器

¥Add preprocessor

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

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

ts
// svelte.config.js
import adapter from '@sveltejs/adapter-auto'
import { vitePreprocess } from '@sveltejs/kit/vite'
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

按照 below 的描述设置 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:

ts
// uno.config.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:

预设支持的注意
@unocss/preset-uno@unocss/preset-mini@unocss/preset-wind@unocss/preset-icons@unocss/web-fonts这些和所有社区插件,例如 unocss-preset-forms,仅依赖规则/变体/预检即可工作。
@unocss/preset-typography由于此预设将规则集添加到预检中的方式,你在使用此预设时必须将 prose 类添加到安全列表中,否则永远不会触发预检。此预设中的所有其他类别,例如 prose-pink,可以是组件作用域。
@unocss/preset-rem-to-px此预设以及所有类似的预设仅修改样式输出才有效。
@unocss/preset-attributify*预设不起作用。而是在 Svelte Scoped 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 类之类的预设,那么你将需要将触发预设添加的类放入你的安全列表中。

¥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 文件支持 Transformer (css|postcss|sass|scss|less|stylus|styl)。要使用它们,请将转换器添加到 vite.config.tscssFileTransformers 选项中:

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

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

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

信息

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

¥Transformers are not supported in Svelte components due to how Svelte Scoped works.

作用域工具类释放创造力

¥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