Vue + UnoCSS 避免影响全局样式

Vue + UnoCSS 避免影响全局样式

最近尝试使用 Vite 将一个 Vue 3 组件打包成 JS,以便插入一个不是 Vue 写的 HTML 中。本人属于前端小白,不会写CSS。由于 AI 写这种类名样式比较厉害,且听说 UnoCSS 比较厉害,故使用 UnoCSS + presetWind4 的方案。

不过实际部署后发现一个严重的问题,由于缺少隔离机制,UnoCSS / Tailwind CSS 会导致全局样式被覆盖,表现为外部的原生样式丢失。为了避免造成更大的影响,需要对其进行处理,避免全局样式被影响。

尝试 vue-scoped 模式

经过阅读 UnoCSS 文档,发现其支持vue-scoped功能,感觉可以完美解决这个问题,将样式限制到 Vue 的 <style scope> 内:

https://unocss.dev/integrations/vite#modes

Modes

The Vite plugin comes with a set of modes that enable different behaviors.

  • global (default)

    This is the default mode for the plugin: in this mode you need to add the import of uno.css on your entry point.

    This mode enables a set of Vite plugins for build and for dev with HMR support.

    The generated css will be a global stylesheet injected on the index.html.

  • vue-scoped

    This mode will inject generated CSS to Vue SFCs <style scoped> for isolation.

具体使用方法:

1
2
3
4
5
6
7
8
9
10
11
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { resolve } from "path";
import UnoCSS from "unocss/vite";

export default defineConfig({
  plugins: [vue(), UnoCSS({
      mode: "vue-scoped",
  })],
  ...
});

但经过实测发现样式只正常一半,和 Tailwind CSS 基本一样,经过查找发现这给问题还没修复。

这个 PR 已经无法跟上最新版本,所以这招暂时行不通,否则应该是最优解。

Tailwind CSS 完整用法

这个时候感觉到 UnoCSS 存在问题,又换回 Tailwind CSS,发现其存在一个 preflight 机制(https://tailwindcss.com/docs/preflight):简单来说就是给浏览器的那些自带样式清空。

正常情况下,我们用 Tailwind CSS v4,只需要在 CSS 里面引入一句:

1
@import "tailwindcss";

实际上里面包含了:

1
2
3
4
@layer theme, base, components, utilities;
@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/preflight.css" layer(base);
@import "tailwindcss/utilities.css" layer(utilities);

这个 @layer 是什么意思呢?查询 MDN 发现,这是一个样式作用的过程,相当于不同的优先级,相当于定义CSS的作用域。

我可以给我的 Vue 组件加个 id,然后用这玩意限定一下清理的范围即可。

1
2
3
4
5
6
7
@layer theme, base, components, utilities;

@import "tailwindcss/theme.css" layer(theme);
#extension-root {
  @import "tailwindcss/preflight.css" layer(base);
}
@import "tailwindcss/utilities.css" layer(utilities);

(来自:https://j5cookie.medium.com/scoping-tailwindcss-preflight-for-injected-apps-c30152f6dd8d)

但是,经过实测,这样在 Vue Scope 内并不好使,样式还是存在小问题。

回归 UnoCSS

基于上面对 Tailwind CSS 的了解,继续看回 UnoCSS 的文档,发现 UnoCSS 的 presetWind4 支持开关 Reset 样式,貌似找到了希望,紧接着还发现其支持合并+随机类名,这样可以解决冲突的问题,那就不得不用 UnoCSS 了。

比如:

1
2
3
<div class=":uno: text-center sm:text-left">
  <div class=":uno: text-sm font-bold hover:text-red" />
</div>

会合并成:

1
2
3
<div class="uno-qlmcrp">
  <div class="uno-0qw2gr" />
</div>

通过这种合并,可以避免污染外部环境,实现“隔离”的效果。但此时 Reset 样式还没解决。

使用 Vue <style scope> 解决 Reset 样式污染

首先去到 uno.config.js/ts 里面 reset: false,然后在对应的 Vue 组件里面手动导入 @unocss/reset/tailwind-v4.css,结果样式出现了部分泄露,通过对其 @layer 进行限制,即可解决这个问题。

经过测试,外部样式几乎未受影响,组件内部样式没有问题。

完整配置

vite.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { resolve } from "path";
import UnoCSS from "unocss/vite";

export default defineConfig({
  plugins: [vue(), UnoCSS()],
  define: {
    "process.env": JSON.stringify({
      NODE_ENV: process.env.NODE_ENV || "production",
    }),
  },
  build: {
    lib: {
      entry: resolve(__dirname, "src/main.js"),
      name: "VueApp",
      fileName: (format) => `vue-app.${format}.js`,
      formats: ["umd"],
    },
    cssCodeSplit: true,
  },
});

uno.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { defineConfig, presetWind4, transformerCompileClass } from "unocss";

export default defineConfig({
  content: {
    pipeline: {
      include: [/\.(vue)($|\?)/],
    },
  },
  presets: [
    presetWind4({
      preflights: {
        reset: false,
      },
    }),
  ],
  transformers: [
    transformerCompileClass({
      classPrefix: "vapp-",
    }),
  ],
});

src/App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script setup>
import "virtual:uno.css";
</script>
<html>
  <div class="flex items-center justify-center min-h-screen bg-gray-100 ">
    <button
      class="p-2 bg-green-600 hover:bg-green-700 text-white rounded-lg shadow-md transition-all"
    >
      UnoCSS
    </button>
  </div>
</html>
<style scoped>
@layer base;
@import "@unocss/reset/tailwind-v4.css" layer(base);
</style>

最后

Vue 牛逼!!!UnoCSS 牛逼!!!antfu 神!!!