Skip to content

unocss 是怎么合并配置的

流程

  1. 首先调用 createGenerator 传入配置。
html
<script setup lang="ts">
import { createGenerator } from '../../../packages/core/src/generator/index'
import {presetUno} from '@unocss/preset-uno'

const generator = createGenerator({
  presets: [presetUno()],
  rules: [
    ['flex', { display: 'flex' }],
    [/^m-(\d)$/, ([, d]) => ({ margin: `${d}px` })],
  ],
}, {})

const code = 'm-1 text-red'

const res = generator.generate(code)
</script>
  1. 创建生成器
js
export function createGenerator<Theme extends {} = {}>(config?: UserConfig<Theme>, defaults?: UserConfigDefaults<Theme>) {
  return new UnoGenerator<Theme>(config, defaults)
}
  1. 生成器类的构造器初始化
js
  constructor(
    public userConfig: UserConfig<Theme> = {},
    public defaults: UserConfigDefaults<Theme> = {},
  ) {
    // 合并配置
    this.config = resolveConfig(userConfig, defaults)
    this.events.emit('config', this.config)
  }
  1. resolveConfig 函数
js
export function resolveConfig<Theme extends {} = {}>(
  userConfig: UserConfig<Theme> = {},
  defaults: UserConfigDefaults<Theme> = {},
): ResolvedConfig<Theme> {
  // 通过 Object.assign 简单合并用户传进来的配置
  const config = Object.assign({}, defaults, userConfig) as UserConfigDefaults<Theme>
  /**
   * 因为用户的预设里,可能也使用到了预设,所以将其拍平。
   * 然后使用 map 函数将每个数组元素转为数组类型。
   */
  const rawPresets = (config.presets || []).flatMap(toArray).map(resolvePreset)

  // 排序预设。pre 在前,post 在后。
  const sortedPresets = [
    ...rawPresets.filter(p => p.enforce === 'pre'),
    ...rawPresets.filter(p => !p.enforce),
    ...rawPresets.filter(p => p.enforce === 'post'),
  ]
  function mergePresets<T extends 'rules' | 'variants' | 'extractors' | 'shortcuts' | 'preflights' | 'preprocess' | 'postprocess' | 'extendTheme' | 'safelist' | 'separators'>(key: T): Required<UserConfig<Theme>>[T] {
    return uniq([
      ...sortedPresets.flatMap(p => toArray(p[key] || []) as any[]),
      ...toArray(config[key] || []) as any[],
    ])
  }
  // ...
  const rules = mergePresets('rules')
  const rulesStaticMap: ResolvedConfig<Theme>['rulesStaticMap'] = {}
  const rulesSize = rules.length

  const rulesDynamic = rules
    .map((rule, i) => {
      if (isStaticRule(rule)) {
        const prefixes = toArray(rule[2]?.prefix || '')
        prefixes.forEach((prefix) => {
          rulesStaticMap[prefix + rule[0]] = [i, rule[1], rule[2], rule]
        })
        // delete static rules so we can't skip them in matching
        // but keep the order
        // 删除静态规则,这样我们不能在匹配中跳过它们
        // 但是保持了顺序
        return undefined
      }
      return [i, ...rule]
    })
    .filter(Boolean)
    .reverse() as ResolvedConfig<Theme>['rulesDynamic']

  // 返回配置对象 
  return {
    mergeSelectors: true,
    warn: true,
    blocklist: [],
    sortLayers: layers => layers,
    ...config,
    presets: sortedPresets,
    envMode: config.envMode || 'build',
    shortcutsLayer: config.shortcutsLayer || 'shortcuts',
    layers,
    theme,
    rulesSize,
    rulesDynamic,
    rulesStaticMap,
    preprocess: mergePresets('preprocess') as Preprocessor[],
    postprocess: mergePresets('postprocess') as Postprocessor[],
    preflights: mergePresets('preflights'),
    autocomplete,
    variants: mergePresets('variants')
      .map(normalizeVariant)
      .sort((a, b) => (a.order || 0) - (b.order || 0)),
    shortcuts: resolveShortcuts(mergePresets('shortcuts')).reverse(),
    extractors,
    safelist: mergePresets('safelist'),
    separators,
  }
}

代码思想和封装的工具方法

flatMap

先调用数组的 flat 方法,展开层级为 1。然后调用 map 方法。这样比调用 flat,然后 map 方法的效率稍微高点。

利用 filter 排序

js
  // 排序预设。pre 在前,post 在后。
  const sortedPresets = [
    ...rawPresets.filter(p => p.enforce === 'pre'),
    ...rawPresets.filter(p => !p.enforce),
    ...rawPresets.filter(p => p.enforce === 'post'),
  ]

toArray

转为 数组

js
export function toArray<T>(value: T | T[] = []): T[] {
  return Array.isArray(value) ? value : [value]
}

uniq

数组去重

js
export function uniq<T>(value: T[]): T[] {
  return Array.from(new Set(value))
}

过滤数组假值

Boolean 的 MDN 解释

js
[].filter(Boolean)

Released under the MIT License.