深度了解后的UnoCSS

之前介绍过Tailwind CSS和UnoCSS,可以看这篇文章TailwindCSS vs UnoCSS:两大原子化CSS框架深度对比与最佳使用场景

最近我在项目中集成了UnoCSS,使用了一段时间,发现了它的优势与缺点,这篇文章就来细说一下我使用UnoCSS的感受。

1. 集成

因为Tailwind CSS 3出现了一个十分重要的新特性,那就是可以使用自定义类名,比如:px-[10px]这种写法,而在Tailwind 2中,这是不被支持的。

所以我最常用使用的是Tailwind CSS 3,但是Tailwind 3的集成有一个前置条件,那就是需要使用PostCSS 8,而在Vue-cli 4中,它默认集成的是PostCSS 7,而且还无法被替换,如果你想要使用PostCSS 8,那么将Vue-cli升级到5。

非常不巧的是,我们现在公司使用的项目年代已经非常久了,都是使用的Vue 2,并且已经迭代了好几年,而我主要负责的项目是小程序端,小程序使用Uniapp Vue 2的版本,Uniapp甚至在Vue-cli上面又再次封装了一层,就导致更难以使用到PostCSS 8。

经过我的研究后,我发现UnoCSS不需要PostCSS的支撑,也能集成到Vue 2的项目中。

而在Vue-cli 4中,我经过了很久的研究,发现需要使用0.48.9这个版本,因为从0.49.0开始,UnoCSS就只支持ESM的方式引入,可以参考官方文档@unocss/webpack

如果使用0.49.0以及后面的版本,需要通过下面的方式引入:

// vue.config.js
const process = require('node:process')

module.exports = function () {
  return import('@unocss/webpack').then(({ default: UnoCSS }) => ({
    configureWebpack: {
      devtool: 'inline-source-map',
      plugins: [
        UnoCSS()
      ],
      optimization: {
        realContentHash: true
      }
    },
    chainWebpack(config) {
      config.module.rule('vue').uses.delete('cache-loader')
      config.module.rule('tsx').uses.delete('cache-loader')
      config.merge({
        cache: false
      })
    },
    css: {
      extract: process.env.NODE_ENV === 'development'
        ? {
            filename: 'css/[name].css',
            chunkFilename: 'css/[name].css'
          }
        : true
    }
  }))
}

但我研究后发现,这种引入方式会报错,可能是因为UnoCSS内部的依赖问题,也可能是webpack的版本问题,总之就是无法正常使用。

所以最后我选择了0.48.9这个版本,然后使用UnoCSS的默认配置,通过vue.config.js引入。

npm install -D @unocss/[email protected] [email protected]
// vue.config.js
const process = require('node:process')
const UnoCSS = require('@unocss/webpack').default

module.exports = {
  configureWebpack: {
    devtool: 'inline-source-map',
    plugins: [
      UnoCSS()
    ],
    optimization: {
      realContentHash: true
    }
  },
  chainWebpack(config) {
    config.module.rule('vue').uses.delete('cache-loader')
    config.module.rule('tsx').uses.delete('cache-loader')
    config.merge({
      cache: false
    })
  },
  css: {
    extract: process.env.NODE_ENV === 'development'
      ? {
          filename: 'css/[name].css',
          chunkFilename: 'css/[name].css'
        }
      : true
  },
}

这种方式则可以正常使用,至于unocss.config.js文件,直接写入下面的代码:

// uno.config.js
const { defineConfig, presetUno, presetAttributify } = require('unocss')

module.exports = defineConfig({
  presets: [
    presetUno(),
    presetAttributify()
  ]
})

但需要注意的是,UnoCSS需要使用Node 18以上的版本,我尝试过这样引入后,项目的启动会非常快,但是项目启动后,UnoCSS不会被编译,需要修改一下文件,然后保存一下,UnoCSS才会被编译。

1.1 小程序

因为微信小程序是无法使用px-[10px]这种类的,所以需要靠第三方库来解决。

我们的小程序是用的Uniapp 2,Uniapp 2的Vue 2版本是用的Vue-cli 4,参考这个unocss-webpack-uniapp2,那么需要安装以下的依赖:

npm install -D unocss unocss-webpack-uniapp2 unocss-preset-weapp

然后按照它的文档上面,就可以很容易集成到项目中。

2. 深度使用

由于我之前自定义了很多Tailwind CSS的规则,所以如果要迁移到UnoCSS,需要重新自定义很多规则。

然后我就研究了一下UnoCSS的presets、rules、transformers。

2.1 presets

presets是UnoCSS的预设,它可以帮助我们快速生成很多规则,比如要兼容Tailwind的写法,就可以使用@unocss/preset-wind这个库。

UnoCSS目前支持多种预设,包括:

  • presetUno: 默认预设,结合了多个框架的常见功能
  • presetWind: 兼容Tailwind CSS的语法
  • presetAttributify: 支持属性化模式,如<div bg="blue-400" text="sm white">
  • presetIcons: 支持使用纯CSS图标
  • presetTypography: 提供排版样式
  • presetWebFonts: 方便使用网络字体

2.2 rules

rules是UnoCSS生成CSS的规则,可以让你将任意的类名写法,生成你想要的规则,比如:

rules: [
  ['px-1', { 'padding-left': '1px', 'padding-right': '1px' }]
]

这个代码对应的CSS就是:

.px-1 {
  padding-left: 1px;
  padding-right: 1px;
}

最重要的是它可以动态生成规则,比如:

rules: [
  [/^m-(\d+)$/, ([, d]) => ({ margin: `${d / 4}rem` })],
  [/^p-(\d+)$/, match => ({ padding: `${match[1] / 4}rem` })],
]

这个代码对应的CSS就是:

.m-1 {
  margin: 0.25rem;
}
.p-1 {
  padding: 0.25rem;
}

并且它可以匹配到m-p-所有写法。

2.3 transformers

transformers是UnoCSS的转换器,它可以将类名转换成为其它的类名,比如小程序这种不支持px-[10px],使用transformers就可以转换成px-10px或者px-10这种写法。

UnoCSS提供了以下转换器:

  • transformerVariantGroup: 支持组合多个变体,如hover:(bg-gray-400 font-medium)
  • transformerDirectives: 支持在CSS中使用@apply等指令
  • transformerAttributifyJSX: 针对JSX属性化模式的优化
  • transformerCompileClass: 可以编译类名为其他格式

格局再打开一点,它甚至可以将px-[10px]这种写法,转换为px-[20rpx]这种写法,然后由UnoCSS生成对应的CSS。

至于其它的配置,可以看一下unocss.dev/config/

3. 优点与缺点

说完了如何集成,那么我就来说一下相关的优点与缺点,当然这是对比Tailwind CSS来说的。

3.1 优点

  1. 更灵活的配置体系 - 比Tailwind更丰富的配置,你可以实现各种类与类的转化。
  2. 更好的兼容性 - 可以兼容Tailwind的写法,而Tailwind无法兼容UnoCSS的写法。
  3. 更简单的集成 - 不依赖PostCSS就可以集成,所以如果是Vue 2的老项目,可以无压力集成。
  4. 卓越的性能 - 性能极佳,根据基准测试,UnoCSS比Tailwind快约200倍,资源消耗更少。
  5. 原生图标支持 - 对纯CSS图标的支持,让你可以使用任何来自Iconify的图标作为单个类。
  6. 强大的扩展性 - 可以轻松创建自己的规则和预设,定制性更强。
  7. 变体组功能 - 提供了变体组功能,可以让多个状态变体更加整洁,如hover:(bg-blue-500 text-white)

3.2 缺点

  1. 写法过于自由 - 写法太过于自由,如果是多人开发,会让项目中的类名变得非常繁杂,比如px-10pxpx-[10px]都是可行的。而Tailwind CSS的写法是固定的,比如只有px-[10px]
  2. 稳定性问题 - 不如Tailwind稳定,我个人在使用过程中,无论是集成上,还是使用上,都遇到了不少问题,而Tailwind CSS一旦集成成功,很少遇到其它问题。
  3. 文档体验 - 虽然文档全面但有时不够直观,新手上手可能需要更多时间,而Tailwind CSS的文档结构清晰、示例丰富。
  4. 生成静态CSS文件限制 - 不能直接根据项目代码生成独立的CSS文件,从而不破坏项目结构。这在某些特定场景下可能是个限制。

Tailwind是可以直接根据项目代码,生成CSS文件,我之前一直是以这种方法,将Tailwind应用到项目中。

因为Tailwind提供了一个CLI工具,只需要使用:

npx tailwindcss -i ./src/assets/css/tailwind.css -o ./src/assets/css/tailwind.output.css --watch

然后就可以在项目中使用Tailwind CSS的写法,然后Tailwind CSS会根据项目代码,生成CSS文件,然后我们只需要在项目中引入这个CSS文件即可。

但这么使用有几个缺点:

  1. Tailwind CSS有一部分特性无法使用,比如@apply
  2. Tailwind CSS CLI在处理大型项目时会占用大量内存,有时会因为内存不足而崩溃
  3. 在Uniapp项目中,每次保存代码都会触发两次编译(一次是Uniapp的编译,一次是Tailwind的编译),导致开发效率降低

而UnoCSS的优势之一就是其高效的按需编译机制,几乎不会有内存问题,编译速度也快得多。根据官方基准测试,UnoCSS的性能可以比Tailwind JIT快200倍,这在大型项目中尤为明显。

4. 总结

UnoCSS的优点是比Tailwind更丰富的配置,可以兼容Tailwind的写法,但是Tailwind无法兼容UnoCSS的写法。性能方面,UnoCSS远超Tailwind,占用更少的系统资源,编译速度更快。此外,UnoCSS提供了许多创新功能,如纯CSS图标、变体组、属性化模式等,这些都是Tailwind所不具备的。

UnoCSS的缺点是写法太过于自由,如果是多人开发,会让项目中的类名变得非常繁杂。另外,对于新手来说,UnoCSS的灵活性可能反而会增加学习成本,而Tailwind更加结构化的方法可能更容易掌握。

我个人建议:

  1. 如果项目是全新的,且团队熟悉Tailwind:优先考虑使用Tailwind CSS,因为它的写法固定,使用更加稳定,可以让项目风格更加统一。
  2. 如果是Vue 2的老项目或需要高性能:考虑使用UnoCSS,特别是如果你的项目规模较大或有性能问题,UnoCSS的高效性能可能会带来显著改善。
  3. 如果不想破坏现有架构:使用Tailwind CSS的生成CSS文件的方式是一个完全没有风险的方案,相当于给项目中引入了一个CSS文件。不过要注意内存占用和双重编译可能带来的开发体验问题。

随着UnoCSS的不断发展和改进,它已经成为了原子化CSS领域的一个强有力的竞争者。它不仅在性能上有明显优势,还提供了许多创新功能。但根据项目需求和团队情况,选择最适合的工具仍然是最重要的。