Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core, preset-mini): generate preflights on demand #4252

Merged
merged 19 commits into from
Nov 30, 2024
Merged
8 changes: 6 additions & 2 deletions docs/presets/mini.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,11 @@ Prefix for CSS custom properties.
Utils prefix.

### preflight
- **Type:** `boolean`
- **Type:** `boolean` | `on-demand`
- **Default:** `true`

Generate preflight.
Generate preflight css. It can be:

- `true`: always generate preflight.
- `false`: no preflight.
- `on-demand`: only generate preflight for used utilities.
zyyv marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 1 addition & 1 deletion packages/autocomplete/src/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ export function createAutocomplete(
templates.length = 0
templates.push(
...uno.config.autocomplete.templates || [],
...uno.config.rulesDynamic.flatMap(i => toArray(i?.[3]?.autocomplete || [])),
...uno.config.rulesDynamic.flatMap(i => toArray(i?.[2]?.autocomplete || [])),
...uno.config.shortcuts.flatMap(i => toArray(i?.[2]?.autocomplete || [])),
)
}
Expand Down
22 changes: 10 additions & 12 deletions packages/core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,19 +167,16 @@ export async function resolveConfig<Theme extends object = object>(
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((rule) => {
if (!isStaticRule(rule))
return true
// Put static rules into the map for faster lookup
const prefixes = toArray(rule[2]?.prefix || '')
prefixes.forEach((prefix) => {
rulesStaticMap[prefix + rule[0]] = rule
})
return false
})
.filter(Boolean)
.reverse() as ResolvedConfig<Theme>['rulesDynamic']

let theme: Theme = mergeThemes(sources.map(p => p.theme))
Expand Down Expand Up @@ -213,6 +210,7 @@ export async function resolveConfig<Theme extends object = object>(
shortcutsLayer: config.shortcutsLayer || 'shortcuts',
layers,
theme,
rules,
rulesSize,
rulesDynamic,
rulesStaticMap,
Expand Down
20 changes: 13 additions & 7 deletions packages/core/src/generator/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { BlocklistMeta, BlocklistValue, ControlSymbols, ControlSymbolsEntry, CSSEntries, CSSEntriesInput, CSSObject, CSSValueInput, DynamicRule, ExtendedTokenInfo, ExtractorContext, GenerateOptions, GenerateResult, ParsedUtil, PreflightContext, PreparedRule, RawUtil, ResolvedConfig, RuleContext, RuleMeta, SafeListContext, Shortcut, ShortcutValue, StringifiedUtil, UserConfig, UserConfigDefaults, UtilObject, Variant, VariantContext, VariantHandlerContext, VariantMatchedResult } from '../types'
import type { BlocklistMeta, BlocklistValue, ControlSymbols, ControlSymbolsEntry, CSSEntries, CSSEntriesInput, CSSObject, CSSValueInput, DynamicRule, ExtendedTokenInfo, ExtractorContext, GenerateOptions, GenerateResult, ParsedUtil, PreflightContext, PreparedRule, RawUtil, ResolvedConfig, Rule, RuleContext, RuleMeta, SafeListContext, Shortcut, ShortcutValue, StringifiedUtil, UserConfig, UserConfigDefaults, UtilObject, Variant, VariantContext, VariantHandlerContext, VariantMatchedResult } from '../types'
import { version } from '../../package.json'
import { resolveConfig } from '../config'
import { LAYER_DEFAULT, LAYER_PREFLIGHTS } from '../constants'
Expand All @@ -19,6 +19,7 @@ class UnoGeneratorInternal<Theme extends object = object> {
public config: ResolvedConfig<Theme> = undefined!
public blocked = new Set<string>()
public parentOrders = new Map<string, number>()
public activatedRules = new Set<Rule<Theme>>()
public events = createNanoEvents<{
config: (config: ResolvedConfig<Theme>) => void
}>()
Expand Down Expand Up @@ -49,6 +50,7 @@ class UnoGeneratorInternal<Theme extends object = object> {
this.userConfig = userConfig
this.blocked.clear()
this.parentOrders.clear()
this.activatedRules.clear()
this._cache.clear()
this.config = await resolveConfig(userConfig, this.defaults)
this.events.emit('config', this.config)
Expand Down Expand Up @@ -577,10 +579,11 @@ class UnoGeneratorInternal<Theme extends object = object> {
const staticMatch = this.config.rulesStaticMap[processed]
if (staticMatch) {
if (staticMatch[1] && (internal || !staticMatch[2]?.internal)) {
context.generator.activatedRules.add(staticMatch)
if (this.config.details)
context.rules!.push(staticMatch[3])
context.rules!.push(staticMatch)

const index = staticMatch[0]
const index = this.config.rules.indexOf(staticMatch)
const entry = normalizeCSSEntries(staticMatch[1])
const meta = staticMatch[2]
if (isString(entry))
Expand All @@ -595,7 +598,8 @@ class UnoGeneratorInternal<Theme extends object = object> {
const { rulesDynamic } = this.config

// match rules
for (const [i, matcher, handler, meta] of rulesDynamic) {
for (const rule of rulesDynamic) {
const [matcher, handler, meta] = rule
// ignore internal rules
if (meta?.internal && !internal)
continue
Expand Down Expand Up @@ -626,8 +630,9 @@ class UnoGeneratorInternal<Theme extends object = object> {
if (!result)
continue

context.generator.activatedRules.add(rule)
if (this.config.details)
context.rules!.push([matcher, handler, meta] as DynamicRule<Theme>)
context.rules!.push(rule as DynamicRule<Theme>)

// Handle generator result
if (typeof result !== 'string') {
Expand All @@ -647,9 +652,10 @@ class UnoGeneratorInternal<Theme extends object = object> {

const entries = normalizeCSSValues(result).filter(i => i.length) as (string | CSSEntriesInput)[]
if (entries.length) {
const index = this.config.rules.indexOf(rule)
return entries.map((css): ParsedUtil | RawUtil => {
if (isString(css))
return [i, css, meta]
return [index, css, meta]

// Extract variants from special symbols
let variants = variantHandlers
Expand Down Expand Up @@ -680,7 +686,7 @@ class UnoGeneratorInternal<Theme extends object = object> {
}
}

return [i, raw, css as CSSEntries, meta, variants]
return [index, raw, css as CSSEntries, meta, variants]
})
}
}
Expand Down
16 changes: 12 additions & 4 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export interface ExtractorContext {
envMode?: 'dev' | 'build'
}

export interface PreflightContext<Theme extends object = object> {
interface BaseContext<Theme extends object = object> {
/**
* UnoCSS generator instance
*/
Expand All @@ -148,7 +148,9 @@ export interface PreflightContext<Theme extends object = object> {
theme: Theme
}

export interface SafeListContext<Theme extends object = object> extends PreflightContext<Theme> { }
export interface PreflightContext<Theme extends object = object> extends BaseContext<Theme> { }

export interface SafeListContext<Theme extends object = object> extends BaseContext<Theme> { }

export interface Extractor {
name: string
Expand Down Expand Up @@ -202,6 +204,11 @@ export interface RuleMeta {
* @private
*/
__hash?: string

/**
* Custom metadata
*/
custom?: Record<string, any>
}

export type CSSValue = CSSObject | CSSEntries
Expand Down Expand Up @@ -850,8 +857,9 @@ export interface ResolvedConfig<Theme extends object = object> extends Omit<
preprocess: Preprocessor[]
postprocess: Postprocessor[]
rulesSize: number
rulesDynamic: [number, ...DynamicRule<Theme>][]
rulesStaticMap: Record<string, [number, CSSObject | CSSEntries, RuleMeta | undefined, Rule<Theme>] | undefined>
rules: readonly Rule<Theme>[]
rulesDynamic: readonly DynamicRule<Theme>[]
rulesStaticMap: Record<string, StaticRule | undefined>
autocomplete: {
templates: (AutoCompleteFunction | AutoCompleteTemplate)[]
extractors: AutoCompleteExtractor[]
Expand Down
3 changes: 2 additions & 1 deletion packages/preset-mini/src/_rules/ring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const ringBase = {
'--un-ring-color': 'rgb(147 197 253 / 0.5)',
'--un-shadow': '0 0 rgb(0 0 0 / 0)',
}
const preflightKeys = Object.keys(ringBase)

export const rings: Rule<Theme>[] = [
// ring
Expand All @@ -24,7 +25,7 @@ export const rings: Rule<Theme>[] = [
'box-shadow': 'var(--un-ring-offset-shadow), var(--un-ring-shadow), var(--un-shadow)',
}
}
}, { autocomplete: 'ring-$ringWidth' }],
}, { custom: { preflightKeys }, autocomplete: 'ring-$ringWidth' }],

// size
[/^ring-(?:width-|size-)(.+)$/, handleWidth, { autocomplete: 'ring-(width|size)-$lineWidth' }],
Expand Down
3 changes: 2 additions & 1 deletion packages/preset-mini/src/_rules/shadow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const boxShadowsBase = {
'--un-shadow-inset': varEmpty,
'--un-shadow': '0 0 rgb(0 0 0 / 0)',
}
const preflightKeys = Object.keys(boxShadowsBase)

export const boxShadows: Rule<Theme>[] = [
// color
Expand All @@ -25,7 +26,7 @@ export const boxShadows: Rule<Theme>[] = [
}
}
return colorResolver('--un-shadow-color', 'shadow', 'shadowColor')(match, context)
}, { autocomplete: ['shadow-$colors', 'shadow-$boxShadow'] }],
}, { custom: { preflightKeys }, autocomplete: ['shadow-$colors', 'shadow-$boxShadow'] }],
[/^shadow-op(?:acity)?-?(.+)$/, ([, opacity]) => ({ '--un-shadow-opacity': h.bracket.percent.cssvar(opacity) }), { autocomplete: 'shadow-(op|opacity)-<percent>' }],

// inset
Expand Down
31 changes: 19 additions & 12 deletions packages/preset-mini/src/_rules/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,15 @@ export const transformBase = {
'--un-translate-y': 0,
'--un-translate-z': 0,
}
const preflightKeys = Object.keys(transformBase)

export const transforms: Rule[] = [
// origins
[/^(?:transform-)?origin-(.+)$/, ([, s]) => ({ 'transform-origin': positionMap[s] ?? h.bracket.cssvar(s) }), { autocomplete: [`transform-origin-(${Object.keys(positionMap).join('|')})`, `origin-(${Object.keys(positionMap).join('|')})`] }],
[
/^(?:transform-)?origin-(.+)$/,
([, s]) => ({ 'transform-origin': positionMap[s] ?? h.bracket.cssvar(s) }),
{ autocomplete: [`transform-origin-(${Object.keys(positionMap).join('|')})`, `origin-(${Object.keys(positionMap).join('|')})`] },
],

// perspectives
[/^(?:transform-)?perspect(?:ive)?-(.+)$/, ([, s]) => {
Expand All @@ -94,23 +99,25 @@ export const transforms: Rule[] = [
}],

// modifiers
[/^(?:transform-)?translate-()(.+)$/, handleTranslate],
[/^(?:transform-)?translate-([xyz])-(.+)$/, handleTranslate],
[/^(?:transform-)?rotate-()(.+)$/, handleRotate],
[/^(?:transform-)?rotate-([xyz])-(.+)$/, handleRotate],
[/^(?:transform-)?skew-()(.+)$/, handleSkew],
[/^(?:transform-)?skew-([xy])-(.+)$/, handleSkew, { autocomplete: ['transform-skew-(x|y)-<percent>', 'skew-(x|y)-<percent>'] }],
[/^(?:transform-)?scale-()(.+)$/, handleScale],
[/^(?:transform-)?scale-([xyz])-(.+)$/, handleScale, { autocomplete: [`transform-(${transformValues.join('|')})-<percent>`, `transform-(${transformValues.join('|')})-(x|y|z)-<percent>`, `(${transformValues.join('|')})-<percent>`, `(${transformValues.join('|')})-(x|y|z)-<percent>`] }],
[/^(?:transform-)?translate-()(.+)$/, handleTranslate, { custom: { preflightKeys } }],
[/^(?:transform-)?translate-([xyz])-(.+)$/, handleTranslate, { custom: { preflightKeys } }],
[/^(?:transform-)?rotate-()(.+)$/, handleRotate, { custom: { preflightKeys } }],
[/^(?:transform-)?rotate-([xyz])-(.+)$/, handleRotate, { custom: { preflightKeys } }],
[/^(?:transform-)?skew-()(.+)$/, handleSkew, { custom: { preflightKeys } }],
[/^(?:transform-)?skew-([xy])-(.+)$/, handleSkew, { custom: { preflightKeys }, autocomplete: ['transform-skew-(x|y)-<percent>', 'skew-(x|y)-<percent>'] }],
[/^(?:transform-)?scale-()(.+)$/, handleScale, { custom: { preflightKeys } }],
[/^(?:transform-)?scale-([xyz])-(.+)$/, handleScale, { custom: { preflightKeys }, autocomplete: [`transform-(${transformValues.join('|')})-<percent>`, `transform-(${transformValues.join('|')})-(x|y|z)-<percent>`, `(${transformValues.join('|')})-<percent>`, `(${transformValues.join('|')})-(x|y|z)-<percent>`] }],

// style
[/^(?:transform-)?preserve-3d$/, () => ({ 'transform-style': 'preserve-3d' })],
[/^(?:transform-)?preserve-flat$/, () => ({ 'transform-style': 'flat' })],

// base
['transform', { transform }],
['transform-cpu', { transform: transformCpu }],
['transform-gpu', { transform: transformGpu }],
['transform', { transform }, { custom: { preflightKeys } }],
['transform-cpu', { transform: transformCpu }, {
custom: { preflightKeys: ['--un-translate-x', '--un-translate-y', '--un-rotate', '--un-rotate-z', '--un-skew-x', '--un-skew-y', '--un-scale-x', '--un-scale-y'] },
}],
['transform-gpu', { transform: transformGpu }, { custom: { preflightKeys } }],
['transform-none', { transform: 'none' }],
...makeGlobalStaticRules('transform'),
]
Expand Down
23 changes: 3 additions & 20 deletions packages/preset-mini/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Postprocessor, Preflight, PreflightContext, PresetOptions } from '@unocss/core'
import type { Postprocessor, PresetOptions } from '@unocss/core'
import type { Theme, ThemeAnimation } from './theme'
import { definePreset } from '@unocss/core'
import { extractorArbitraryVariants } from '@unocss/extractor-arbitrary-variants'
Expand Down Expand Up @@ -60,7 +60,7 @@ export interface PresetMiniOptions extends PresetOptions {
*
* @default true
*/
preflight?: boolean
preflight?: boolean | 'on-demand'

/**
* Enable arbitrary variants, for example `<div class="[&>*]:m-1 [&[open]]:p-2"></div>`.
Expand Down Expand Up @@ -90,9 +90,7 @@ export const presetMini = definePreset((options: PresetMiniOptions = {}) => {
options,
prefix: options.prefix,
postprocess: VarPrefixPostprocessor(options.variablePrefix),
preflights: options.preflight
? normalizePreflights(preflights, options.variablePrefix)
: [],
preflights: preflights(options),
extractorDefault: options.arbitraryVariants === false
? undefined
: extractorArbitraryVariants(),
Expand All @@ -115,18 +113,3 @@ export function VarPrefixPostprocessor(prefix: string): Postprocessor | undefine
}
}
}

export function normalizePreflights<Theme extends object>(preflights: Preflight<Theme>[], variablePrefix: string) {
if (variablePrefix !== 'un-') {
return preflights.map(p => ({
...p,
getCSS: (() => async (ctx: PreflightContext<Theme>) => {
const css = await p.getCSS(ctx)
if (css)
return css.replace(/--un-/g, `--${variablePrefix}`)
})(),
}))
}

return preflights
}
40 changes: 28 additions & 12 deletions packages/preset-mini/src/preflights.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,32 @@
import type { Preflight } from '@unocss/core'
import type { PresetMiniOptions } from '.'
import type { Theme } from './theme'
import { entriesToCss, toArray } from '@unocss/core'

export const preflights: Preflight<Theme>[] = [
{
layer: 'preflights',
getCSS(ctx) {
if (ctx.theme.preflightBase) {
const css = entriesToCss(Object.entries(ctx.theme.preflightBase))
const roots = toArray(ctx.theme.preflightRoot ?? ['*,::before,::after', '::backdrop'])
return roots.map(root => `${root}{${css}}`).join('')
}
},
},
]
export function preflights(options: PresetMiniOptions): Preflight<Theme>[] | undefined {
if (options.preflight) {
return [
{
layer: 'preflights',
getCSS({ theme, generator }) {
if (theme.preflightBase) {
let entries = Object.entries(theme.preflightBase)
if (options.preflight === 'on-demand') {
const keys = new Set(Array.from(generator.activatedRules).map(r => r[2]?.custom?.preflightKeys).filter(Boolean).flat())
entries = entries.filter(([k]) => keys.has(k))
}

if (entries.length > 0) {
let css = entriesToCss(entries)
if (options.variablePrefix !== 'un-') {
css = css.replace(/--un-/g, `--${options.variablePrefix}`)
}
const roots = toArray(theme.preflightRoot ?? ['*,::before,::after', '::backdrop'])
return roots.map(root => `${root}{${css}}`).join('')
}
}
},
},
]
}
}
Loading