当前位置: 首页 > 工具软件 > UnoCSS > 使用案例 >

第三章 使用UnoCSS原子化CSS

澹台展鹏
2023-12-01

第三章 使用UnoCSS原子化CSS

原子样式也有很多选择,最著名的就是 TailwindTailwind 虽然好,但是性能上有一些不足。由于Tailwind 会生成大量样式定义。全量的 CSS 文件往往体积会多至数 MB。这个对于页面性能是完全不可接受的。如果在开发时进行动态的按需剪裁,又会影响编译性能,降低开发体验。为了解决性能问题,开源界一个叫做 Antfu 的大神设计了 UnoCSSUnoCSS 是一个拥有高性能且具灵活性的即时原子化 CSS 引擎,可以兼顾产物体积和开发性能。

本章任务

  • 引入 UnoCSS 样式

  • 实现组件属性定制按钮样式

  • 实现【Icon图标按钮】

task1】引入UnoCSS样式

  • 安装依赖
pnpm i -D unocss@"0.45.6"
pnpm i -D @iconify-json/ic@"1.1.4" 

其中的@iconify-json/ic 是字体图标库

  • 在Vite配置文件中添加UnoCSS插件

文件名:vite.config.ts

import { presetUno, presetAttributify, presetIcons } from "unocss";
import Unocss from "unocss/vite";
export default defineConfig({
  plugins: [
    ...
    // 添加UnoCSS插件
    Unocss({
        presets: [presetUno(), presetAttributify(), presetIcons()],
    })
  ],
});

下面就可以在插件中引入 UnoCSS 了。加载 Unocss 插件后,Vite 会通过分析 class 的使用状况提供相应的样式定义。

  • Button 组件中引入 UnoCSS

文件名:src/button/index.tsx

注意: 这个地方文件名已经从 index.ts变为 index.tsx

import { defineComponent,PropType,toRefs} from "vue";
import "uno.css";
export default defineComponent({
  name: "SButton",
  setup(props, {slots}) {
    return () => <button 
      class={`
      py-2 
      px-4 
      font-semibold 
      rounded-lg 
      shadow-md 
      text-white 
      bg-green-500 
      hover:bg-green-700 
      border-none 
      cursor-pointer 
      `}
        > 
        {slots.default ? slots.default() : ''}
     </button>
  }
});
  • index.ts 中添加一个测试代码

文件名: src/index.ts

import { createApp } from "vue";
import SmartyUI from "./entry"

createApp({
  template:`
      <div>
          <SButton>普通按钮</SButton>
      </div>
  `
})
.use(SmartyUI)
.mount("#app");
  • 启动项目
pnpm dev
  • 在浏览器输入地址查看按钮组件
  VITE v3.0.7  ready in 644 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose

此时并没有看见页面出现按钮组件,且浏览器控制台抛出警告:

runtime-core.esm-bundler.js:38 [Vue warn]: Component provided template option but runtime compilation is not supported in this build of Vue. Configure your bundler to alias "vue" to "vue/dist/vue.esm-bundler.js". 
  at <App>

这个警告的意思是:组件提供 template 选项,但是在Vue的这个构建中不支持运行时编译,在你的打包工具里配置别名“vue: vue/dist/vue.esm-bundler.js”

  • 分析原因

项目的 vue/dist 目录下有很多不同的 Vue.js 构建版本,不同的环境使用不同的构建版本。使用构建工具的情况下,默认使用的是 vue.runtime.esm-bundler.js 这个仅运行时版本,不能处理 template 选项是字符串的情况,template 选项是字符串的情况要使用包含运行时编译器的版本 vue.esm-bundler.js

  • 解决方案

在vite配置文件中配置别名resolve

文件名:vite.config.ts

export default defineConfig({
  resolve: {
    alias: {
      vue: 'vue/dist/vue.esm-bundler.js',
    },
  },
  plugins: [
        ...
        // 插件
  ]
 })

修改好别名后,保存就可以在浏览器看见一个绿色按钮了。

到此为止,说明 UnoCSS 已经正常引入了。


task2】实现组件属性定制按钮样式

根据属性定制按钮样式功能,就是可以修改组件的属性来达到你想要的目的,例如,通过color属性定制颜色。

 <div>
  <SButton color="blue">蓝色按钮</SButton>
  <SButton color="green">绿色按钮</SButton>
  <SButton color="gray">灰色按钮</SButton>
  <SButton color="yellow">黄色按钮</SButton>
  <SButton color="red">红色按钮</SButton>
 </div>
  • 定义属性类型并注册组件属性

文件名:src/button/index.tsx

import { defineComponent,PropType,toRefs} from "vue";
import "uno.css";

// 颜色类型声明
export type IColor = 'black' | 'gray' | 'red' | 'yellow' | 'green'|'blue'|'indigo'|'purple'|'pink'

export const props = {
  color: {
    type: String as PropType<IColor>,
    default: 'blue'  // 设定默认颜色
  },
}
export default defineComponent({
  name: "SButton",
  props,  // 注册属性
  ...
  }
});
  • 属性变量拼装 UnoCSS

文件名:src/button/index.tsx

export default defineComponent({
  name: "SButton",
  props,
  setup(props, {slots}) {
    return () => <button 
        class={`
          py-2 
          px-4 
          font-semibold 
          rounded-lg 
          shadow-md 
          text-white 
          bg-${props.color}-500 
          hover:bg-${props.color}-700 
          border-none 
          cursor-pointer 
          m-1
          `}
        > 
        {slots.default ? slots.default() : ''}
     </button>
  }
});
  • 修改index.ts文件,添加测试用例

文件名:src/index.ts

import { createApp } from "vue";
import SmartyUI from './entry'

createApp({
  template:`
  <div>
    <SButton color="blue">蓝色按钮</SButton>
    <SButton color="green">绿色按钮</SButton>
    <SButton color="gray">灰色按钮</SButton>
    <SButton color="yellow">黄色按钮</SButton>
    <SButton color="red">红色按钮</SButton>
 </div>
  `
})
.use(SmartyUI)
.mount("#app");
  • 启动项目
pnpm dev
  • 在浏览器页面中查看

可以看到组件,但是但是灰色的,并没有看到我们给组件属性配置的颜色。这是为什么?

仔细研究 UnoCSS 的文档才发现问题。主要原因是 UnoCSS 默认是按需生成方式。也就是说只生成代码中使用过的样式。那如果在 class 属性中使用变量,是无法分析变量的取值的。这样也就无法动态生成样式了。

为了解决这个问题,UnoCSS 提供了安全列表选项。也就是说,把样式定义中变量的取值添加到 Safelist 中去。这样 UnoCSS 就会根据 Safelist 生成样式了。

  • 开始定制安全列表

安全列表属性应该定义在 UnoCSS 插件的配置中。

这里面要做一个配置上的重构。考虑到后续会在 Safelist 中添加大量配置,所以我们将 UnoCSS 配置拆成一个新的 ts 模块,然后引用到 vite.config.ts 中。

项目在搭建的过程中会不断地进行重构。希望大家在开发的过程中,一定要积极思考如何编写更加合理易于维护的代码。

文件名:config/unocss.ts

import { presetUno, presetAttributify, presetIcons } from "unocss";
import Unocss from "unocss/vite";

const colors = [
  "white",
  "black",
  "gray",
  "red",
  "yellow",
  "green",
  "blue",
  "indigo",
  "purple",
  "pink",
];

const safelist = [

  ...colors.map((v) => `bg-${v}-500`),
  ...colors.map((v) => `hover:bg-${v}-700`),
];

export default () =>
  Unocss({
    safelist,
    presets: [presetUno(), presetAttributify(), presetIcons()],
  });
  • vite配置中引入重构的unocss配置

文件名:vite.config.ts

import Unocss from "./config/unocss";

export default defineConfig({

  plugins: [
	// 重构后的unocss配置
    Unocss(),
  ],

})
  • 重启项目
pnpm dev
  • 在浏览器查看结果

此时可以看到组件,以及我们想要的对应的组件属性所配置的颜色了。


task3】Icon 图标按钮实现

接下来要给按钮增加图标定制功能。实现图标按钮,首先需要引入字体图标库。

UnoCSS 中引入图标,只需要加载 @unocss/preset-icons 预设就可以了。它提供了 iconify 图标框架中大量的图表集。

iconfy官方网站

  • 开始引入这个功能,首先在 Unocss 插件中添加 presetIcons 预设。

文件名: config/unocss.ts

export default () =>
  Unocss({
    safelist,
    presets: [
        presetUno(),   
        presetAttributify(), 
        presetIcons(),  // 添加图标预设
    ]
  });
  • 定制图标安全列表

为了能够在 UnoCSS 中使用变量定义字体图标,需要将使用的图标名加入到 safelist 中。

文件名:config/unocss.ts

const safelist = [
  ...[
    "search",
    "edit",
    "check",
    "message",
    "star-off",
    "delete",
    "add",
    "share",
  ].map((v) => `i-ic-baseline-${v}`),
];
  • Button组件中注册icon属性

文件名:src/button/index.tsx

export const props = {
  icon: { // 注册icon属性
    type: String,
    default: ''
  }
}
  • Button组件 中添加字体图标

文件名:src/button/index.tsx

return () => <button 
        class={`
            ...
          mx-1
          `}
        > 
        { props.icon !== "" ? <i class={`i-ic-baseline-${props.icon} p-3`}></i> : ""}
        {slots.default ? slots.default() : ''}
 </button>
  • index.ts中添加测试用例

文件名:src/index.ts

import { createApp } from 'vue'
import SmartyUI from './entry'

createApp({
  template: `
 <div style="margin-top:20px;">
    <SButton color="blue"   icon="search" ></SButton>
    <SButton color="green" icon="edit"></SButton>
    <SButton color="gray"  icon="check"></SButton>
    <SButton color="yellow"  icon="message"></SButton>
    <SButton color="red"  icon="delete"></SButton>
  </div>
  `,
})
  .use(SmartyUI)
  .mount('#app')
  • 重启项目
pnpm dev
  • 在浏览器查看结果

可以看到有字体图标的按钮了。

后续属性优化可以参考其他组件库,如roundsize等属性。


Build 时单独导出 CSS

使用 unocss 后,如果运行 pnpm build 的时候会报错。

vite v3.0.7 building for production...
✓ 7 modules transformed.
dist/smarty-ui.mjs   1.58 KiB / gzip: 0.69 KiB
dist/style.css       8.17 KiB / gzip: 1.75 KiB
Entry module "src/entry.ts" is using named and default exports together. Consumers of your bundle will have to use `SmartyUI["default"]` to access the default export, which may not be what you want. Use `output.exports: "named"` to disable this warning
rendering chunks (1)...[unocss:global:build:generate] [unocss] does not found CSS placeholder in the generated chunks
It seems you are building in library mode, it's recommanded to set `build.cssCodeSplit` to true.
See https://github.com/vitejs/vite/issues/1579
error during build:
Error: [unocss] does not found CSS placeholder in the generated chunks
It seems you are building in library mode, it's recommanded to set `build.cssCodeSplit` to true.
See https://github.com/vitejs/vite/issues/1579
    at Object.generateBundle (D:\MyWorkSpace\VUE3_WORKSPACE\StudyVueUI\node_modules\.pnpm\registry.npmmirror.com+@unocss+vite@0.45.6_vite@3.0.7\node_modules\@unocss\vite\dist\index.cjs:374:22)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async Bundle.generate (file:///D:/MyWorkSpace/VUE3_WORKSPACE/StudyVueUI/node_modules/.pnpm/registry.npmmirror.com+rollup@2.77.3/node_modules/rollup/dist/es/shared/rollup.js:15973:9)
    at async file:///D:/MyWorkSpace/VUE3_WORKSPACE/StudyVueUI/node_modules/.pnpm/registry.npmmirror.com+rollup@2.77.3/node_modules/rollup/dist/es/shared/rollup.js:23709:27
    at async catchUnfinishedHookActions (file:///D:/MyWorkSpace/VUE3_WORKSPACE/StudyVueUI/node_modules/.pnpm/registry.npmmirror.com+rollup@2.77.3/node_modules/rollup/dist/es/shared/rollup.js:23041:20)
    at async doBuild (file:///D:/MyWorkSpace/VUE3_WORKSPACE/StudyVueUI/node_modules/.pnpm/registry.npmmirror.com+vite@3.0.7/node_modules/vite/dist/node/chunks/dep-0f13c890.js:43585:26)
    at async build (file:///D:/MyWorkSpace/VUE3_WORKSPACE/StudyVueUI/node_modules/.pnpm/registry.npmmirror.com+vite@3.0.7/node_modules/vite/dist/node/chunks/dep-0f13c890.js:43413:16)
    at async CAC.<anonymous> (file:///D:/MyWorkSpace/VUE3_WORKSPACE/StudyVueUI/node_modules/.pnpm/registry.npmmirror.com+vite@3.0.7/node_modules/vite/dist/node/cli.js:747:9)
 ELIFECYCLE  Command failed with exit code 1.
  • 解决方案

解决办法是根据提示在vite配置文件中增加编译选项: cssCodeSplit

文件名:vite.config.ts

 build: {
    ...
    cssCodeSplit: true,   // 追加
    ...
  },

简单解释一下原因: cssCodeSplit 这个选项是为了决定在编译的时候是否要独立输出 css。显然这里面应该选择为 true

同样在调用组件库的时候需要引入 style.css 才可以让样式生效。

  • 再次进行项目打包
pnpm build

此时并无报错。

测试组件库

  • 修改demo测试文件

文件名:demo/esm/index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="../../dist/style.css">
</head>
<body>
  <h1>全量加载组件</h1>
<div id="app"></div>
<script type="module">
  import { createApp } from "../../node_modules/vue/dist/vue.esm-bundler.js";
  import SmartyUI from "../../dist/smarty-ui.mjs";

  createApp({
  template: `
  <div>
    <SButton color="blue">蓝色按钮</SButton>
    <SButton color="green">绿色按钮</SButton>
    <SButton color="gray">灰色按钮</SButton>
    <SButton color="yellow">黄色按钮</SButton>
    <SButton color="red">红色按钮</SButton>
 </div>
 <div style="margin-top:20px;">
    <SButton color="blue"   icon="search" ></SButton>
    <SButton color="green" icon="edit"></SButton>
    <SButton color="gray"  icon="check"></SButton>
    <SButton color="yellow"  icon="message"></SButton>
    <SButton color="red"  icon="delete"></SButton>
  </div>
  `,
})
  .use(SmartyUI)
  .mount('#app')
</script>
</body>
</html>
  • 重启项目
pnpm dev
  • 浏览器查看结果

查看地址:http://localhost:5173/demo/esm/index.html

组件的颜色和字体图标正常显示。

 类似资料: