本章概述

本章将深入探讨 UnoCSS 的核心机制:预设系统和规则引擎。我们将学习如何使用内置预设、理解规则引擎的工作原理,以及如何创建自定义预设和规则。

预设系统概述

什么是预设

预设(Preset)是 UnoCSS 的核心概念,它是一组预定义的规则、变体、快捷方式和配置的集合。预设使得 UnoCSS 具有高度的模块化和可扩展性。

// 预设的基本结构
const myPreset = {
  name: 'my-preset',
  rules: [
    // 规则定义
  ],
  variants: [
    // 变体定义
  ],
  shortcuts: {
    // 快捷方式定义
  },
  theme: {
    // 主题配置
  },
  preflights: [
    // 预检样式
  ]
}

预设的优势

  1. 模块化:将相关功能组织在一起
  2. 可复用:可以在多个项目中使用
  3. 可组合:多个预设可以组合使用
  4. 可扩展:可以基于现有预设进行扩展
  5. 可维护:便于版本管理和更新

内置预设详解

1. @unocss/preset-uno

这是 UnoCSS 的默认预设,提供了最常用的工具类。

// 使用默认预设
import { defineConfig, presetUno } from 'unocss'

export default defineConfig({
  presets: [
    presetUno(),
  ],
})

包含的功能: - 基础工具类(margin、padding、color 等) - Flexbox 和 Grid 布局 - 响应式断点 - 状态变体(hover、focus 等) - 基础动画和过渡

示例用法

<!-- 基础样式 -->
<div class="p-4 m-2 bg-blue-500 text-white rounded-lg">
  基础样式
</div>

<!-- 布局 -->
<div class="flex items-center justify-center h-screen">
  <div class="grid grid-cols-3 gap-4">
    <div class="col-span-2">内容</div>
    <div>侧边栏</div>
  </div>
</div>

<!-- 响应式 -->
<div class="w-full md:w-1/2 lg:w-1/3">
  响应式宽度
</div>

<!-- 状态变体 -->
<button class="bg-blue-500 hover:bg-blue-600 focus:ring-2 focus:ring-blue-300">
  交互按钮
</button>

2. @unocss/preset-wind

提供与 Tailwind CSS 兼容的工具类。

import { defineConfig, presetWind } from 'unocss'

export default defineConfig({
  presets: [
    presetWind(),
  ],
})

特点: - 完全兼容 Tailwind CSS 语法 - 包含所有 Tailwind CSS 的工具类 - 支持 Tailwind CSS 的配置格式 - 便于从 Tailwind CSS 迁移

配置示例

import { defineConfig, presetWind } from 'unocss'

export default defineConfig({
  presets: [
    presetWind({
      // Tailwind CSS 兼容配置
      dark: 'class', // 或 'media'
      attributify: true,
    }),
  ],
  
  // Tailwind CSS 风格的主题配置
  theme: {
    colors: {
      primary: {
        50: '#eff6ff',
        500: '#3b82f6',
        900: '#1e3a8a',
      }
    },
    fontFamily: {
      sans: ['Inter', 'sans-serif'],
    },
    extend: {
      spacing: {
        '72': '18rem',
        '84': '21rem',
        '96': '24rem',
      }
    }
  }
})

3. @unocss/preset-mini

最小化的预设,只包含最基础的功能。

import { defineConfig, presetMini } from 'unocss'

export default defineConfig({
  presets: [
    presetMini(),
  ],
})

适用场景: - 需要最小化 CSS 输出 - 自定义程度要求很高的项目 - 性能要求极高的场景

4. @unocss/preset-icons

图标预设,支持数万个图标。

import { defineConfig, presetIcons } from 'unocss'

export default defineConfig({
  presets: [
    presetIcons({
      collections: {
        // 加载图标集
        carbon: () => import('@iconify-json/carbon/icons.json').then(i => i.default),
        mdi: () => import('@iconify-json/mdi/icons.json').then(i => i.default),
        tabler: () => import('@iconify-json/tabler/icons.json').then(i => i.default),
      },
      
      // 自定义图标
      customizations: {
        iconCustomizer(collection, icon, props) {
          // 自定义图标处理
        }
      },
      
      // 额外属性
      extraProperties: {
        'display': 'inline-block',
        'vertical-align': 'middle',
      }
    }),
  ],
})

使用示例

<!-- 基础图标 -->
<div class="i-carbon-user text-2xl"></div>
<div class="i-mdi-heart text-red-500"></div>
<div class="i-tabler-star text-yellow-500"></div>

<!-- 图标按钮 -->
<button class="flex items-center gap-2 px-4 py-2 bg-blue-500 text-white rounded">
  <div class="i-carbon-add"></div>
  添加项目
</button>

<!-- 图标列表 -->
<ul class="space-y-2">
  <li class="flex items-center gap-2">
    <div class="i-carbon-checkmark text-green-500"></div>
    已完成任务
  </li>
  <li class="flex items-center gap-2">
    <div class="i-carbon-time text-yellow-500"></div>
    进行中任务
  </li>
</ul>

5. @unocss/preset-typography

排版预设,提供美观的文本排版样式。

import { defineConfig, presetTypography } from 'unocss'

export default defineConfig({
  presets: [
    presetTypography({
      // 自定义 CSS 选择器前缀
      cssExtend: {
        'code': {
          color: '#8b5cf6',
        },
        'a:hover': {
          color: '#f59e0b',
        },
        'a:visited': {
          color: '#8b5cf6',
        },
      }
    }),
  ],
})

使用示例

<!-- 基础排版 -->
<article class="prose">
  <h1>文章标题</h1>
  <p>这是一段正文内容,包含了 <a href="#">链接</a> 和 <code>代码</code>。</p>
  <blockquote>
    这是一个引用块。
  </blockquote>
  <ul>
    <li>列表项 1</li>
    <li>列表项 2</li>
  </ul>
</article>

<!-- 不同尺寸 -->
<div class="prose prose-sm">小号排版</div>
<div class="prose prose-lg">大号排版</div>
<div class="prose prose-xl">超大号排版</div>

<!-- 深色模式 -->
<div class="prose dark:prose-invert">
  深色模式排版
</div>

6. @unocss/preset-web-fonts

Web 字体预设,简化字体加载和使用。

import { defineConfig, presetWebFonts } from 'unocss'

export default defineConfig({
  presets: [
    presetWebFonts({
      // Google Fonts
      provider: 'google',
      fonts: {
        sans: 'Roboto',
        mono: ['Fira Code', 'Fira Mono:400,700'],
        lobster: 'Lobster',
        custom: {
          name: 'My Custom Font',
          src: 'url("/fonts/custom.woff2")',
        }
      },
      
      // 字体显示策略
      display: 'swap',
      
      // 预加载
      preload: true,
      
      // 内联 CSS
      inlineImports: true,
    }),
  ],
})

使用示例

<!-- 使用预设字体 -->
<h1 class="font-lobster text-4xl">装饰性标题</h1>
<p class="font-sans">正文内容</p>
<code class="font-mono">代码内容</code>

<!-- 字体权重 -->
<p class="font-sans font-light">细体文本</p>
<p class="font-sans font-bold">粗体文本</p>

7. @unocss/preset-attributify

属性化预设,允许使用 HTML 属性来应用样式。

import { defineConfig, presetAttributify } from 'unocss'

export default defineConfig({
  presets: [
    presetAttributify({
      // 配置选项
      prefix: 'un-',
      prefixedOnly: false,
      nonValuedAttribute: true,
    }),
  ],
})

使用示例

<!-- 传统方式 -->
<div class="bg-blue-500 text-white p-4 rounded-lg hover:bg-blue-600">
  传统方式
</div>

<!-- 属性化方式 -->
<div 
  bg="blue-500 hover:blue-600" 
  text="white" 
  p="4" 
  rounded="lg"
>
  属性化方式
</div>

<!-- 布尔属性 -->
<div flex items-center justify-center>
  布尔属性
</div>

<!-- 带前缀 -->
<div 
  un-bg="red-500" 
  un-text="white"
  un-p="4"
>
  带前缀的属性
</div>

规则引擎详解

规则的类型

1. 静态规则

// 静态规则示例
const staticRules = [
  // 简单的键值对
  ['center', { 'text-align': 'center' }],
  ['red', { color: 'red' }],
  
  // 多个样式属性
  ['btn', {
    'padding': '0.5rem 1rem',
    'border-radius': '0.25rem',
    'font-weight': '500',
  }],
  
  // 使用 CSS 变量
  ['primary', {
    'color': 'var(--primary-color)',
    'background-color': 'var(--primary-bg)',
  }],
]

2. 动态规则

// 动态规则示例
const dynamicRules = [
  // 数字匹配
  [/^m-(\d+)$/, ([, d]) => ({ margin: `${d / 4}rem` })],
  [/^p-(\d+)$/, ([, d]) => ({ padding: `${d / 4}rem` })],
  
  // 颜色匹配
  [/^text-(.+)$/, ([, color]) => ({ color })],
  [/^bg-(.+)$/, ([, color]) => ({ 'background-color': color })],
  
  // 复杂匹配
  [/^grid-cols-(\d+)$/, ([, cols]) => ({
    'grid-template-columns': `repeat(${cols}, minmax(0, 1fr))`,
  })],
  
  // 条件匹配
  [/^w-(\d+)\/(\d+)$/, ([, numerator, denominator]) => {
    const percentage = (parseInt(numerator) / parseInt(denominator)) * 100
    return { width: `${percentage}%` }
  }],
]

3. 函数式规则

// 函数式规则示例
const functionRules = [
  // 访问主题配置
  [/^text-(.+)$/, ([, color], { theme }) => {
    const colorValue = theme.colors?.[color]
    if (colorValue) {
      return { color: colorValue }
    }
  }],
  
  // 复杂逻辑处理
  [/^shadow-(.+)$/, ([, type]) => {
    const shadows = {
      sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
      md: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
      lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1)',
      xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1)',
    }
    
    return shadows[type] ? { 'box-shadow': shadows[type] } : undefined
  }],
  
  // 生成多个样式
  [/^gradient-(.+)-to-(.+)$/, ([, from, to]) => {
    return {
      'background-image': `linear-gradient(to right, ${from}, ${to})`,
      'background-clip': 'text',
      '-webkit-background-clip': 'text',
      'color': 'transparent',
    }
  }],
]

规则匹配机制

// 规则匹配流程
class RuleEngine {
  constructor(rules) {
    this.rules = rules
    this.cache = new Map()
  }
  
  match(className) {
    // 检查缓存
    if (this.cache.has(className)) {
      return this.cache.get(className)
    }
    
    // 遍历规则
    for (const rule of this.rules) {
      const [pattern, handler] = rule
      
      // 静态规则匹配
      if (typeof pattern === 'string') {
        if (pattern === className) {
          const result = { css: handler, matched: true }
          this.cache.set(className, result)
          return result
        }
      }
      
      // 动态规则匹配
      if (pattern instanceof RegExp) {
        const match = className.match(pattern)
        if (match) {
          const css = typeof handler === 'function' 
            ? handler(match, { theme: this.theme })
            : handler
          
          if (css) {
            const result = { css, matched: true }
            this.cache.set(className, result)
            return result
          }
        }
      }
    }
    
    // 未匹配
    const result = { matched: false }
    this.cache.set(className, result)
    return result
  }
}

规则优先级

// 规则优先级配置
export default defineConfig({
  rules: [
    // 高优先级规则(后定义的优先级更高)
    ['important-rule', { color: 'red !important' }],
    
    // 普通规则
    [/^text-(.+)$/, ([, color]) => ({ color })],
    
    // 低优先级规则
    ['text-default', { color: 'black' }],
  ],
  
  // 层级配置
  layers: {
    'base': -1,
    'components': 0,
    'utilities': 1,
    'overrides': 2,
  },
})

创建自定义预设

基础预设结构

// my-preset.js
export function createMyPreset(options = {}) {
  return {
    name: 'my-preset',
    
    // 规则定义
    rules: [
      // 自定义规则
      ['my-center', {
        'display': 'flex',
        'align-items': 'center',
        'justify-content': 'center',
      }],
      
      // 动态规则
      [/^my-space-(\d+)$/, ([, d]) => ({
        'margin': `${d * 0.25}rem`,
        'padding': `${d * 0.25}rem`,
      })],
    ],
    
    // 变体定义
    variants: [
      // 自定义变体
      (matcher) => {
        if (!matcher.startsWith('my-hover:')) return matcher
        return {
          matcher: matcher.slice(9),
          selector: s => `${s}:hover`,
        }
      },
    ],
    
    // 快捷方式
    shortcuts: {
      'my-btn': 'px-4 py-2 rounded bg-blue-500 text-white hover:bg-blue-600',
      'my-card': 'p-6 bg-white rounded-lg shadow-md border',
    },
    
    // 主题配置
    theme: {
      colors: {
        'my-primary': '#3b82f6',
        'my-secondary': '#64748b',
      },
      spacing: {
        'my-xs': '0.5rem',
        'my-sm': '1rem',
        'my-md': '1.5rem',
        'my-lg': '2rem',
      },
    },
    
    // 预检样式
    preflights: [
      {
        getCSS: ({ theme }) => `
          .my-container {
            max-width: 1200px;
            margin: 0 auto;
            padding: 0 ${theme.spacing?.['my-sm'] || '1rem'};
          }
        `
      }
    ],
    
    // 选项处理
    options,
  }
}

高级预设功能

// advanced-preset.js
import { createGenerator } from 'unocss'

export function createAdvancedPreset(options = {}) {
  const {
    prefix = '',
    colors = {},
    utilities = true,
    components = true,
  } = options
  
  return {
    name: 'advanced-preset',
    
    // 条件规则
    rules: [
      // 仅在启用 utilities 时添加
      ...(utilities ? [
        [new RegExp(`^${prefix}u-(\\w+)$`), ([, utility]) => {
          // 处理工具类
          return generateUtility(utility)
        }],
      ] : []),
      
      // 仅在启用 components 时添加
      ...(components ? [
        [new RegExp(`^${prefix}c-(\\w+)$`), ([, component]) => {
          // 处理组件类
          return generateComponent(component)
        }],
      ] : []),
    ],
    
    // 动态主题
    theme: {
      colors: {
        // 合并用户颜色
        ...getDefaultColors(),
        ...colors,
      },
      
      // 动态断点
      breakpoints: generateBreakpoints(options.breakpoints),
    },
    
    // 后处理器
    postprocess: (util) => {
      // 添加前缀
      if (prefix && util.selector) {
        util.selector = util.selector.replace(/\./g, `.${prefix}`)
      }
      return util
    },
    
    // 扩展其他预设
    extend: options.extend,
  }
}

// 辅助函数
function generateUtility(utility) {
  const utilities = {
    'reset': {
      'margin': '0',
      'padding': '0',
      'box-sizing': 'border-box',
    },
    'clearfix': {
      '&::after': {
        'content': '""',
        'display': 'table',
        'clear': 'both',
      }
    },
  }
  
  return utilities[utility]
}

function generateComponent(component) {
  const components = {
    'button': {
      'display': 'inline-flex',
      'align-items': 'center',
      'justify-content': 'center',
      'padding': '0.5rem 1rem',
      'border': 'none',
      'border-radius': '0.375rem',
      'font-weight': '500',
      'cursor': 'pointer',
      'transition': 'all 0.2s',
    },
    'card': {
      'background': 'white',
      'border-radius': '0.5rem',
      'box-shadow': '0 1px 3px rgba(0, 0, 0, 0.1)',
      'padding': '1.5rem',
    },
  }
  
  return components[component]
}

预设组合和扩展

// preset-composition.js
import { presetUno, presetWind } from 'unocss'
import { createMyPreset } from './my-preset'
import { createAdvancedPreset } from './advanced-preset'

// 组合多个预设
export function createCompositePreset(options = {}) {
  return {
    name: 'composite-preset',
    
    // 继承其他预设
    presets: [
      presetUno(),
      createMyPreset(options.myPreset),
      createAdvancedPreset(options.advanced),
    ],
    
    // 额外的规则
    rules: [
      // 覆盖或扩展规则
      ['composite-special', {
        'background': 'linear-gradient(45deg, #ff6b6b, #4ecdc4)',
        'color': 'white',
        'padding': '1rem',
        'border-radius': '0.5rem',
      }],
    ],
    
    // 合并主题
    theme: {
      extend: {
        colors: {
          'composite': {
            50: '#f0f9ff',
            500: '#0ea5e9',
            900: '#0c4a6e',
          }
        }
      }
    },
  }
}

实际应用示例

企业级预设

// enterprise-preset.js
export function createEnterprisePreset(brandConfig) {
  const { colors, fonts, spacing, components } = brandConfig
  
  return {
    name: 'enterprise-preset',
    
    rules: [
      // 品牌颜色
      ...Object.entries(colors).map(([name, value]) => [
        `brand-${name}`,
        { color: value }
      ]),
      
      // 组件规则
      ['enterprise-header', {
        'background': colors.primary,
        'color': colors.onPrimary,
        'padding': spacing.lg,
        'box-shadow': '0 2px 4px rgba(0,0,0,0.1)',
      }],
      
      ['enterprise-sidebar', {
        'background': colors.surface,
        'border-right': `1px solid ${colors.outline}`,
        'width': '240px',
        'height': '100vh',
      }],
    ],
    
    shortcuts: {
      // 企业级组件快捷方式
      'enterprise-btn-primary': `px-6 py-3 rounded font-medium transition-colors`,
      'enterprise-card': `bg-white rounded-lg shadow border p-6`,
      'enterprise-form-field': `w-full px-3 py-2 border rounded focus:outline-none focus:ring-2`,
    },
    
    theme: {
      colors,
      fontFamily: {
        brand: fonts.primary,
        mono: fonts.mono,
      },
      spacing,
    },
    
    preflights: [
      {
        getCSS: () => `
          :root {
            --enterprise-primary: ${colors.primary};
            --enterprise-secondary: ${colors.secondary};
            --enterprise-surface: ${colors.surface};
          }
          
          .enterprise-layout {
            font-family: ${fonts.primary};
            color: ${colors.onSurface};
            background: ${colors.background};
          }
        `
      }
    ],
  }
}

// 使用企业级预设
const brandConfig = {
  colors: {
    primary: '#1976d2',
    secondary: '#dc004e',
    surface: '#ffffff',
    background: '#f5f5f5',
    onPrimary: '#ffffff',
    onSurface: '#212121',
    outline: '#e0e0e0',
  },
  fonts: {
    primary: ['Roboto', 'sans-serif'],
    mono: ['Roboto Mono', 'monospace'],
  },
  spacing: {
    xs: '0.5rem',
    sm: '1rem',
    md: '1.5rem',
    lg: '2rem',
    xl: '3rem',
  },
}

export default defineConfig({
  presets: [
    createEnterprisePreset(brandConfig),
  ],
})

响应式预设

// responsive-preset.js
export function createResponsivePreset(breakpoints = {}) {
  const defaultBreakpoints = {
    xs: '320px',
    sm: '640px',
    md: '768px',
    lg: '1024px',
    xl: '1280px',
    '2xl': '1536px',
    ...breakpoints,
  }
  
  return {
    name: 'responsive-preset',
    
    rules: [
      // 容器规则
      ['container', {
        'width': '100%',
        'margin-left': 'auto',
        'margin-right': 'auto',
        'padding-left': '1rem',
        'padding-right': '1rem',
      }],
      
      // 响应式显示
      [/^(block|inline|flex|grid|hidden)-(xs|sm|md|lg|xl|2xl)$/, ([, display, bp]) => {
        const mediaQuery = `@media (min-width: ${defaultBreakpoints[bp]})`
        return {
          [mediaQuery]: {
            display: display === 'hidden' ? 'none' : display,
          }
        }
      }],
    ],
    
    variants: [
      // 响应式变体
      ...Object.entries(defaultBreakpoints).map(([name, size]) => {
        return (matcher) => {
          if (!matcher.startsWith(`${name}:`)) return matcher
          return {
            matcher: matcher.slice(name.length + 1),
            parent: `@media (min-width: ${size})`,
          }
        }
      }),
    ],
    
    theme: {
      screens: defaultBreakpoints,
    },
    
    preflights: [
      {
        getCSS: () => {
          // 生成容器响应式样式
          const containerStyles = Object.entries(defaultBreakpoints)
            .map(([name, size]) => {
              const maxWidth = {
                sm: '640px',
                md: '768px',
                lg: '1024px',
                xl: '1280px',
                '2xl': '1536px',
              }[name]
              
              if (maxWidth) {
                return `
                  @media (min-width: ${size}) {
                    .container {
                      max-width: ${maxWidth};
                    }
                  }
                `
              }
              return ''
            })
            .join('')
          
          return containerStyles
        }
      }
    ],
  }
}

性能优化

规则缓存

// 规则缓存优化
class OptimizedRuleEngine {
  constructor(rules) {
    this.rules = rules
    this.cache = new Map()
    this.staticRules = new Map()
    this.dynamicRules = []
    
    // 预处理规则
    this.preprocessRules()
  }
  
  preprocessRules() {
    for (const rule of this.rules) {
      const [pattern, handler] = rule
      
      if (typeof pattern === 'string') {
        // 静态规则直接存储
        this.staticRules.set(pattern, handler)
      } else {
        // 动态规则存储到数组
        this.dynamicRules.push(rule)
      }
    }
  }
  
  match(className) {
    // 检查缓存
    if (this.cache.has(className)) {
      return this.cache.get(className)
    }
    
    // 优先检查静态规则
    if (this.staticRules.has(className)) {
      const result = {
        css: this.staticRules.get(className),
        matched: true
      }
      this.cache.set(className, result)
      return result
    }
    
    // 检查动态规则
    for (const [pattern, handler] of this.dynamicRules) {
      const match = className.match(pattern)
      if (match) {
        const css = typeof handler === 'function' 
          ? handler(match, { theme: this.theme })
          : handler
        
        if (css) {
          const result = { css, matched: true }
          this.cache.set(className, result)
          return result
        }
      }
    }
    
    // 未匹配
    const result = { matched: false }
    this.cache.set(className, result)
    return result
  }
}

懒加载预设

// 懒加载预设
export function createLazyPreset() {
  return {
    name: 'lazy-preset',
    
    // 懒加载规则
    rules: [
      // 基础规则立即加载
      ['flex', { display: 'flex' }],
      ['block', { display: 'block' }],
      
      // 复杂规则懒加载
      [/^complex-(.+)$/, async ([, type]) => {
        const { generateComplexRule } = await import('./complex-rules')
        return generateComplexRule(type)
      }],
    ],
    
    // 懒加载主题
    theme: {
      get colors() {
        return import('./theme-colors').then(m => m.colors)
      },
      
      get spacing() {
        return import('./theme-spacing').then(m => m.spacing)
      },
    },
  }
}

本章总结

通过本章学习,我们深入了解了:

  1. 预设系统:UnoCSS 的模块化核心机制
  2. 内置预设:各种官方预设的功能和使用方法
  3. 规则引擎:静态、动态和函数式规则的工作原理
  4. 自定义预设:如何创建和组合自定义预设
  5. 性能优化:规则缓存和懒加载等优化技巧

核心要点

  • 预设是 UnoCSS 功能的载体,提供了高度的模块化
  • 规则引擎支持多种类型的规则,满足不同需求
  • 自定义预设可以封装项目特定的样式逻辑
  • 性能优化对于大型项目至关重要

下一步

在下一章中,我们将学习原子化 CSS 的具体应用和各种工具类的使用方法。

练习题

  1. 预设创建:创建一个包含你常用样式的自定义预设
  2. 规则编写:编写一个动态规则来处理渐变背景
  3. 预设组合:组合多个预设并解决可能的冲突
  4. 性能测试:测试不同规则类型的性能差异
  5. 企业应用:为一个企业项目设计完整的预设系统