任务分解(Task)

  • 引入 UnoCSS 样式;
  • 实现组件属性定制按钮样式;
  • 实现【Icon图标按钮】。

原子样式

每个组件库都有一套完备的样式系统,确保组件样式风格的统一。经典的组件库 (ElementUI) 的样式系统都是遵循「Semantic CSS 」(语义化 CSS )搭建的

组件库通常都需要搭建一个 CSS 子工程,用于实现自己的类型系统。比如 Element 就是基于 Sass + Gulp 搭建的,不过随着原子样式的出现,有了一些替代方案,无需搭建样式系统也可以完成类似效果 可以看一下 Vite 的社区模板库,tailwind 几乎就是标配

image.png

2020年一种叫做AtomicCSS的组件风格横空出世,代表作就是 TailwindCSS。最近两年,TailwindCSS 一直保持高热度。

为什么选 UnoCSS 实现 AtomicCSS 而不是 Tailwind


原子样式也有很多选择,最著名的就是 Tailwind。 Tailwind 虽然好,但是性能上有一些不足。由于Tailwind 会生成大量样式定义。全量的 CSS 文件往往体积会多至数 MB。这个对于页面性能是完全不可接受的。

开源界一个叫做 Antfu 的大神设计了 UnoCSS。UnoCSS 是一个拥有高性能且具灵活性的即时原子化 CSS 引擎,可以兼顾产物体积和开发性能。
GitHub - unocss/unocss: The instant on-demand atomic CSS engine.

入门使用

安装

  1. pnpm i -D unocss@"0.45.6"
  2. pnpm i -D @iconify-json/ic@"1.1.4"

图标库
Icônes

配置vite

  1. import { presetUno, presetAttributify, presetIcons } from "unocss";
  2. import Unocss from "unocss/vite";
  3. defineConfig({
  4. plugins:[
  5. ...,
  6. // 添加UnoCSS插件
  7. Unocss({
  8. presets: [presetUno(), presetAttributify(), presetIcons()],
  9. }),
  10. ...
  11. ]
  12. })

unocss的Vscode配套插件

image.png

测试使用unocss

  1. import { defineComponent} from "vue";
  2. import "uno.css";
  3. export default defineComponent({
  4. name: "SButton",
  5. setup(props, {slots}) {
  6. return () => <button
  7. class={`
  8. py-2
  9. px-4
  10. font-semibold
  11. rounded-lg
  12. shadow-md
  13. text-white
  14. bg-green-500
  15. hover:bg-green-700
  16. border-none
  17. cursor-pointer
  18. `}
  19. >
  20. {slots.default ? slots.default() : ''}
  21. </button>
  22. }
  23. });
  1. import { createApp } from "vue"
  2. import SmartyUI from './entry'
  3. createApp({
  4. template: `
  5. <div>
  6. <SButton>普通按钮</SButton>
  7. </div>
  8. `
  9. }).use(SmartyUI).mount("#app");
  1. import MyButton from "./button/index.jsx";
  2. import SFCButton from "./SFCButton.vue"; // 关于ts类型警告,通过.d.ts声明文件去除警告
  3. import JSXButton from "./JSXButton.jsx";
  4. import { App } from "vue";
  5. // 导出单独组件
  6. export { MyButton, SFCButton, JSXButton };
  7. // 编写一个插件,实现一个install方法
  8. export default {
  9. install(app: App): void {
  10. app.component(MyButton.name, MyButton);
  11. app.component(SFCButton.name, SFCButton);
  12. app.component(JSXButton.name, JSXButton);
  13. },
  14. };

测试运行-报错 [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 image.png

  1. import { createApp } from "vue/dist/vue.esm-bundler.js";
  2. // import { createApp } from "vue"
  3. import SmartyUI from './entry'
  4. createApp({
  5. template: `
  6. <div>
  7. <SButton>普通按钮</SButton>
  8. </div>
  9. `
  10. }).use(SmartyUI).mount("#app");

image.png

将unocss组合自定义组件实现自定义样式配置

  1. import { defineComponent} from "vue";
  2. import "uno.css";
  3. export const props = {
  4. color: {
  5. type: String,
  6. default: 'blue' // 设定默认颜色
  7. },
  8. }
  9. export default defineComponent({
  10. name: "SButton",
  11. props, // 注册属性
  12. setup(props, {slots}) {
  13. return () => <button
  14. class={`
  15. py-2
  16. px-4
  17. font-semibold
  18. rounded-lg
  19. shadow-md
  20. text-white
  21. bg-${props.color}-500
  22. hover:bg-${props.color}-700
  23. border-none
  24. cursor-pointer
  25. `}
  26. >
  27. {slots.default ? slots.default() : ''}
  28. </button>
  29. }
  30. });

浏览器显示
image.png

这里出现了异常,我们配置的默认颜色没有生效 ,应该是unocss的相关配置问题,需要到官网或者论坛查找答案 主要原因是 UnoCSS 默认是按需生成方式。也就是说只生成代码中使用过的样式。那如果在 class 属性中使用变量,是无法分析变量的取值的。这样也就无法动态生成样式了。

重构Unocss配置

  1. import { presetUno, presetAttributify, presetIcons } from "unocss";
  2. import Unocss from "unocss/vite";
  3. const colors = [
  4. "white",
  5. "black",
  6. "gray",
  7. "red",
  8. "yellow",
  9. "green",
  10. "blue",
  11. "indigo",
  12. "purple",
  13. "pink",
  14. ];
  15. const safelist = [
  16. ...colors.map((v) => `bg-${v}-100`),
  17. ...colors.map((v) => `bg-${v}-400`),
  18. ...colors.map((v) => `bg-${v}-500`),
  19. ...colors.map((v) => `hover:bg-${v}-100`),
  20. ...colors.map((v) => `hover:bg-${v}-300`),
  21. ...colors.map((v) => `hover:bg-${v}-400`),
  22. ...colors.map((v) => `hover:bg-${v}-500`),
  23. ...colors.map((v) => `border-${v}-400`),
  24. ...colors.map((v) => `border-${v}-500`),
  25. ...colors.map((v) => `text-${v}-500`),
  26. ...colors.map((v) => `hover:text-${v}-500`),
  27. 'text-white',
  28. ...Array.from({ length: 8 }, (_, i) => `px-${i + 1}`),
  29. ...Array.from({ length: 8 }, (_, i) => `py-${i + 1}`),
  30. ...["xs", "sm", "base", "lg", "xl", "2xl", "3xl"].map((v) => `text-${v}`),
  31. ...["rounded-full", "rounded-lg"],
  32. ...[
  33. "search",
  34. "edit",
  35. "check",
  36. "message",
  37. "star-off",
  38. "delete",
  39. "add",
  40. "share",
  41. ].map((v) => `i-ic-baseline-${v}`),
  42. ];
  43. export default () =>
  44. Unocss({
  45. safelist,
  46. presets: [presetUno(), presetAttributify(), presetIcons()],
  47. });

修改vite配置

  1. import Unocss from "./config/unpcss";
  2. defineConfig({
  3. ...
  4. plugins: [
  5. vue(),
  6. // 添加UnoCSS插件
  7. Unocss(),
  8. // 添加JSX插件
  9. vueJsx({
  10. // options are passed on to @vue/babel-plugin-jsx
  11. }),
  12. ],
  13. ...
  14. })

按钮组件重构

  1. import { defineComponent , PropType} from "vue";
  2. import "uno.css";
  3. export type ISize = "small" | "medium" | "large";
  4. export type IColor =
  5. | "black"
  6. | "gray"
  7. | "red"
  8. | "yellow"
  9. | "green"
  10. | "blue"
  11. | "indigo"
  12. | "purple"
  13. | "pink";
  14. export const props = {
  15. // 新增
  16. size: {
  17. type: String as PropType<ISize>,
  18. default: "medium",
  19. },
  20. color: {
  21. type: String as PropType<IColor>,
  22. default: "blue",
  23. },
  24. textColor: {
  25. type: String,
  26. default: "#ffffff", // 设定默认颜色
  27. },
  28. round: {
  29. type: Boolean,
  30. default: false,
  31. },
  32. plain: {
  33. type: Boolean,
  34. default: false,
  35. },
  36. icon: {
  37. type: String,
  38. default: "",
  39. },
  40. } as const;
  41. // as const 也是类型断言
  42. export default defineComponent({
  43. name: "SButton",
  44. props, // 注册属性
  45. setup(props, { slots }) {
  46. console.log(`html`, document.querySelector(`#app`)?.innerHTML);
  47. const size = {
  48. small: {
  49. x: "2",
  50. y: "1",
  51. text: "sm",
  52. },
  53. medium: {
  54. x: "3",
  55. y: "1.5",
  56. text: "base",
  57. },
  58. large: {
  59. x: "4",
  60. y: "2",
  61. text: "lg",
  62. },
  63. };
  64. return () => (
  65. <button class={`
  66. py-${size[props.size].y}
  67. px-${size[props.size].x}
  68. ${props.round ? "rounded-full" : "rounded-lg"}
  69. bg-${props.color}-${props.plain ? "100" : "500"}
  70. hover:bg-${props.color}-400
  71. border-${props.color}-${props.plain ? "500" : "500"}
  72. cursor-pointer
  73. border-solid
  74. text-${props.plain ? props.color + "-500" : "white"}
  75. text-${size[props.size].text}
  76. hover:text-white
  77. transition duration-300 ease-in-out transform hover:scale-105
  78. mx-1
  79. `}
  80. >
  81. {props.icon !== "" ? (
  82. <i class={`i-ic-baseline-${props.icon} p-3`}></i>
  83. ) : (
  84. ""
  85. )}
  86. {slots.default ? slots.default() : ""}
  87. </button>
  88. );
  89. },
  90. });

结果预览

image.png

源代码参考

webkubor/KUI