unocss 是怎么合并配置的
流程
- 首先调用 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>- 创建生成器
js
export function createGenerator<Theme extends {} = {}>(config?: UserConfig<Theme>, defaults?: UserConfigDefaults<Theme>) {
return new UnoGenerator<Theme>(config, defaults)
}- 生成器类的构造器初始化
js
constructor(
public userConfig: UserConfig<Theme> = {},
public defaults: UserConfigDefaults<Theme> = {},
) {
// 合并配置
this.config = resolveConfig(userConfig, defaults)
this.events.emit('config', this.config)
}- 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))
}过滤数组假值
js
[].filter(Boolean)