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

refactor: improve sorting #1973

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
35 changes: 14 additions & 21 deletions packages/core/src/generator/index.ts
Expand Up @@ -144,17 +144,17 @@ export class UnoGenerator<Theme extends {} = {}> {
const sheet = new Map<string, StringifiedUtil<Theme>[]>()
let preflightsMap: Record<string, string> = {}

const tokenPromises = Array.from(tokens).map(async (raw) => {
if (matched.has(raw))
return

;(await Promise.all(Array.from(tokens).map(async (raw) => {
const payload = await this.parseToken(raw)
if (payload == null)

if (!payload)
return

matched.add(raw)

for (const item of payload) {
return payload
}))).filter(Boolean).forEach((payload) => {
for (const item of payload!) {
const parent = item[3] || ''
const layer = item[4]?.layer
if (!sheet.has(parent))
Expand All @@ -164,8 +164,6 @@ export class UnoGenerator<Theme extends {} = {}> {
layerSet.add(layer)
}
})

await Promise.all(tokenPromises)
await (async () => {
if (!preflights)
return
Expand Down Expand Up @@ -211,11 +209,11 @@ export class UnoGenerator<Theme extends {} = {}> {
let css = Array.from(sheet)
.sort((a, b) => ((this.parentOrders.get(a[0]) ?? 0) - (this.parentOrders.get(b[0]) ?? 0)) || a[0]?.localeCompare(b[0] || '') || 0)
.map(([parent, items]) => {
const size = items.length
const sorted: PreparedRule[] = items
.filter(i => (i[4]?.layer || LAYER_DEFAULT) === layer)
.sort((a, b) => a[0] - b[0] || (a[4]?.sort || 0) - (b[4]?.sort || 0) || a[1]?.localeCompare(b[1] || '') || a[2]?.localeCompare(b[2] || '') || 0)
.map(([, selector, body,, meta,, variantNoMerge]) => {
.sort((a, b) => (a[4]?.sort || 0) - (b[4]?.sort || 0) || a[0] - b[0] || 0)
.map((arr) => {
const [, selector, body,, meta,, variantNoMerge] = arr
const scopedSelector = selector ? applyScope(selector, scope) : selector
return [
[[scopedSelector ?? '', meta?.sort ?? 0]],
Expand All @@ -226,17 +224,14 @@ export class UnoGenerator<Theme extends {} = {}> {
if (!sorted.length)
return undefined
const rules = sorted
.reverse()
.map(([selectorSortPair, body, noMerge], idx) => {
if (!noMerge && this.config.mergeSelectors) {
// search for rules that has exact same body, and merge them
for (let i = idx + 1; i < size; i++) {
const current = sorted[i]
if (current && !current[2] && ((selectorSortPair && current[0]) || (selectorSortPair == null && current[0] == null)) && current[1] === body) {
if (selectorSortPair && current[0])
current[0].push(...selectorSortPair)
return null
}
const current = sorted[idx + 1]
if (current && !current[2] && ((selectorSortPair && current[0]) || (selectorSortPair == null && current[0] == null)) && current[1] === body) {
if (selectorSortPair && current[0])
current[0].push(...selectorSortPair)
return null
}
}

Expand All @@ -253,7 +248,6 @@ export class UnoGenerator<Theme extends {} = {}> {
: body
})
.filter(Boolean)
.reverse()
.join(nl)

if (!parent)
Expand Down Expand Up @@ -575,7 +569,6 @@ export class UnoGenerator<Theme extends {} = {}> {
})))
.flat(1)
.filter(Boolean)
.sort((a, b) => a[0] - b[0])

const [raw, , parentVariants] = parent
const rawStringfieldUtil: StringifiedUtil<Theme>[] = []
Expand Down
124 changes: 63 additions & 61 deletions packages/preset-icons/src/core.ts
@@ -1,4 +1,4 @@
import type { Preset } from '@unocss/core'
import type { DynamicMatcher, Preset, Rule } from '@unocss/core'
import { warnOnce } from '@unocss/core'
import type {
IconifyLoaderOptions,
Expand Down Expand Up @@ -51,72 +51,74 @@ export function createPresetIcons(lookupIconLoader: (options: IconsOptions) => P

let iconLoader: UniversalIconLoader

return {
name: '@unocss/preset-icons',
enforce: 'pre',
options,
layers: { icons: -30 },
rules: [[
/^([a-z0-9:-]+)(?:\?(mask|bg|auto))?$/,
async ([full, body, _mode = mode]) => {
let collection = ''
let name = ''
let svg: string | undefined
const ruleMatcher: DynamicMatcher = async ([full, body, _mode = mode]) => {
let collection = ''
let name = ''
let svg: string | undefined

iconLoader = iconLoader || await lookupIconLoader(options)
iconLoader = iconLoader || await lookupIconLoader(options)

const usedProps = {}
if (body.includes(':')) {
[collection, name] = body.split(':')
svg = await iconLoader(collection, name, { ...loaderOptions, usedProps })
}
else {
const parts = body.split(/-/g)
for (let i = COLLECTION_NAME_PARTS_MAX; i >= 1; i--) {
collection = parts.slice(0, i).join('-')
name = parts.slice(i).join('-')
svg = await iconLoader(collection, name, { ...loaderOptions, usedProps })
if (svg)
break
}
}
const usedProps = {}
if (body.includes(':')) {
[collection, name] = body.split(':')
svg = await iconLoader(collection, name, { ...loaderOptions, usedProps })
}
else {
const parts = body.split(/-/g)
for (let i = COLLECTION_NAME_PARTS_MAX; i >= 1; i--) {
collection = parts.slice(0, i).join('-')
name = parts.slice(i).join('-')
svg = await iconLoader(collection, name, { ...loaderOptions, usedProps })
if (svg)
break
}
}

if (!svg) {
if (warn)
warnOnce(`failed to load icon "${full}"`)
return
}
if (!svg) {
if (warn)
warnOnce(`failed to load icon "${full}"`)
return
}

const url = `url("data:image/svg+xml;utf8,${encodeSvgForCss(svg)}")`
const url = `url("data:image/svg+xml;utf8,${encodeSvgForCss(svg)}")`

if (_mode === 'auto')
_mode = svg.includes('currentColor') ? 'mask' : 'bg'
if (_mode === 'auto')
_mode = svg.includes('currentColor') ? 'mask' : 'bg'

if (_mode === 'mask') {
// Thanks to https://codepen.io/noahblon/post/coloring-svgs-in-css-background-images
return {
'--un-icon': url,
'mask': 'var(--un-icon) no-repeat',
'mask-size': '100% 100%',
'-webkit-mask': 'var(--un-icon) no-repeat',
'-webkit-mask-size': '100% 100%',
'background-color': 'currentColor',
// for Safari https://github.com/elk-zone/elk/pull/264
'color': 'inherit',
...usedProps,
}
}
else {
return {
'background': `${url} no-repeat`,
'background-size': '100% 100%',
'background-color': 'transparent',
...usedProps,
}
}
},
{ layer, prefix },
]],
if (_mode === 'mask') {
// Thanks to https://codepen.io/noahblon/post/coloring-svgs-in-css-background-images
return {
'--un-icon': url,
'mask': 'var(--un-icon) no-repeat',
'mask-size': '100% 100%',
'-webkit-mask': 'var(--un-icon) no-repeat',
'-webkit-mask-size': '100% 100%',
'background-color': 'currentColor',
// for Safari https://github.com/elk-zone/elk/pull/264
'color': 'inherit',
...usedProps,
}
}
else {
return {
'background': `${url} no-repeat`,
'background-size': '100% 100%',
'background-color': 'transparent',
...usedProps,
}
}
}

const rules = ['mask', 'bg', 'auto'].map((mode) => {
return [new RegExp(`^([a-z0-9:-]+)(?:\\?(${mode}))?$`), ruleMatcher, { layer, prefix }]
}) as Rule[]

return {
name: '@unocss/preset-icons',
enforce: 'pre',
options,
layers: { icons: -30 },
rules,
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/preset-mini/src/_rules/default.ts
Expand Up @@ -23,8 +23,6 @@ import { svgUtilities } from './svg'
import { containerParent } from './container'

export const rules: Rule[] = [
cssVariables,
cssProperty,
paddings,
margins,
displays,
Expand Down Expand Up @@ -78,6 +76,8 @@ export const rules: Rule[] = [
willChange,
containerParent,
contains,
cssVariables,
cssProperty,

// should be the last
questionMark,
Expand Down
7 changes: 5 additions & 2 deletions packages/preset-mini/src/_rules/position.ts
Expand Up @@ -2,10 +2,13 @@ import type { CSSEntries, Rule, RuleContext } from '@unocss/core'
import type { Theme } from '../theme'
import { globalKeywords, handler as h, insetMap, makeGlobalStaticRules } from '../utils'

const positionOrder = ['static', 'fixed', 'absolute', 'relative', 'sticky'].map(k => [
new RegExp(`^(?:position-|pos-)?(${k})$`),
([, v]) => ({ position: v }),
]) as Rule[]
export const positions: Rule[] = [
[/^(?:position-|pos-)?(relative|absolute|fixed|sticky)$/, ([, v]) => ({ position: v })],
...positionOrder,
[/^(?:position-|pos-)([-\w]+)$/, ([, v]) => globalKeywords.includes(v) ? { position: v } : undefined],
[/^(?:position-|pos-)?(static)$/, ([, v]) => ({ position: v })],
]

export const justifies: Rule[] = [
Expand Down
2 changes: 2 additions & 0 deletions packages/preset-mini/src/_utils/variants.ts
Expand Up @@ -15,6 +15,7 @@ export const variantMatcher = (name: string, handler: (input: VariantHandlerCont
...input,
...handler(input),
}),
sort: 2,
}
}
},
Expand All @@ -35,6 +36,7 @@ export const variantParentMatcher = (name: string, parent: string): VariantObjec
...input,
parent: `${input.parent ? `${input.parent} $$ ` : ''}${parent}`,
}),
sort: 2,
}
}
},
Expand Down
9 changes: 2 additions & 7 deletions packages/preset-mini/src/_variants/pseudo.ts
Expand Up @@ -77,11 +77,6 @@ const PseudoClassesStr = Object.entries(PseudoClasses).filter(([, pseudo]) => !p
const PseudoClassesColonStr = Object.entries(PseudoClassesColon).filter(([, pseudo]) => !pseudo.startsWith('::')).map(([key]) => key).join('|')
const PseudoClassFunctionsStr = PseudoClassFunctions.join('|')

const sortValue = (pseudo: string) => {
if (pseudo === 'active')
return 1
}

const taggedPseudoClassMatcher = (tag: string, parent: string, combinator: string): VariantObject => {
const rawRE = new RegExp(`^(${escapeRegExp(parent)}:)(\\S+)${escapeRegExp(combinator)}\\1`)
const pseudoRE = new RegExp(`^${tag}-(?:(?:(${PseudoClassFunctionsStr})-)?(${PseudoClassesStr}))(?:(/\\w+))?[:-]`)
Expand Down Expand Up @@ -121,7 +116,7 @@ const taggedPseudoClassMatcher = (tag: string, parent: string, combinator: strin
label,
input.slice(original.length),
`${parent}${escapeSelector(label)}${pseudo}`,
sortValue(pseudoKey),
1,
]
}

Expand Down Expand Up @@ -176,7 +171,7 @@ export const variantPseudoClassesAndElements: VariantObject = {
return next({
...input,
...selectors,
sort: sortValue(match[1]),
sort: 1,
})
},
}
Expand Down
6 changes: 3 additions & 3 deletions packages/preset-wind/src/rules/default.ts
Expand Up @@ -74,9 +74,6 @@ import { columns } from './columns'
import { placeholders } from './placeholder'

export const rules: Rule[] = [
miniCssVariables,
cssVariables,
cssProperty,
container,
screenReadersAccess,
pointerEvents,
Expand Down Expand Up @@ -157,6 +154,9 @@ export const rules: Rule[] = [
contents,
placeholders,
containerParent,
miniCssVariables,
cssVariables,
cssProperty,

// should be the last
questionMark,
Expand Down
20 changes: 11 additions & 9 deletions test/__snapshots__/order.test.ts.snap
Expand Up @@ -3,37 +3,39 @@
exports[`order > fully controlled rules merged and sorted by body 1`] = `
"/* layer: default */
.uno{--var:uno;}
/* sort: uno */ .foo{--foo:0}
/* sort: css */ .foo{--foo:0}
/* sort: uno */ .foo{--foo:0}
.bar-css{--bar:css;}
.bar-uno{--bar:uno;}
/* sort: css */ .foo{--foo:0}
.bar-css{--bar:css;}
.css{--var:css;}"
`;

exports[`order > movePseudoElementsEnd 1`] = `".part-\\\\[hello-2\\\\]\\\\:marker\\\\:file\\\\:hover\\\\:selection\\\\:mb-4:hover::part(hello-2)::marker::file-selector-button::selection"`;

exports[`order > multiple variant sorting 1`] = `
"/* layer: default */
.dark .group:hover:focus-within .dark\\\\:group-hover\\\\:group-focus-within\\\\:bg-blue-600{--un-bg-opacity:1;background-color:rgba(37,99,235,var(--un-bg-opacity));}
.group:hover:focus-within .dark .group-hover\\\\:group-focus-within\\\\:dark\\\\:bg-red-600{--un-bg-opacity:1;background-color:rgba(220,38,38,var(--un-bg-opacity));}
.parent:hover>.light .parent:focus-within>.parent-hover\\\\:light\\\\:parent-focus-within\\\\:bg-green-600{--un-bg-opacity:1;background-color:rgba(22,163,74,var(--un-bg-opacity));}
.parent:hover>.light .group:focus-within .parent-hover\\\\:light\\\\:group-focus-within\\\\:bg-yellow-600{--un-bg-opacity:1;background-color:rgba(202,138,4,var(--un-bg-opacity));}
.parent:hover>.light .parent:focus-within>.parent-hover\\\\:light\\\\:parent-focus-within\\\\:bg-green-600{--un-bg-opacity:1;background-color:rgba(22,163,74,var(--un-bg-opacity));}"
.dark .group:hover:focus-within .dark\\\\:group-hover\\\\:group-focus-within\\\\:bg-blue-600{--un-bg-opacity:1;background-color:rgba(37,99,235,var(--un-bg-opacity));}"
`;

exports[`order > variant ordering 1`] = `
"/* layer: default */
.group .dark .dark\\\\:group\\\\:foo-3{name:foo-3;}
.group .dark .group\\\\:dark\\\\:foo-4{name:foo-4;}
.group .light .light\\\\:group\\\\:foo-1{name:foo-1;}
.light .group .group\\\\:light\\\\:foo-2{name:foo-2;}"
.light .group .group\\\\:light\\\\:foo-2{name:foo-2;}
.group .dark .dark\\\\:group\\\\:foo-3{name:foo-3;}
.group .dark .group\\\\:dark\\\\:foo-4{name:foo-4;}"
`;

exports[`order > variant ordering reversed 1`] = `
"/* layer: default */
.dark .group .dark\\\\:group\\\\:foo-3{name:foo-3;}
.dark .group .group\\\\:dark\\\\:foo-4{name:foo-4;}
.light .group .light\\\\:group\\\\:foo-1{name:foo-1;}
.group .light .group\\\\:light\\\\:foo-2{name:foo-2;}
.light .group .light\\\\:group\\\\:foo-1{name:foo-1;}"
.dark .group .dark\\\\:group\\\\:foo-3{name:foo-3;}
.dark .group .group\\\\:dark\\\\:foo-4{name:foo-4;}"
`;

exports[`order > variant sorting 1`] = `
Expand Down
4 changes: 2 additions & 2 deletions test/__snapshots__/prefix.test.ts.snap
Expand Up @@ -3,10 +3,10 @@
exports[`prefix > preset prefix 2`] = `
"/* layer: default */
.h-container{max-width:100%;}
.hover\\\\:h-p4:hover{padding:1rem;}
.h-text-red{--un-text-opacity:1;color:rgba(248,113,113,var(--un-text-opacity));}
.bar-bar,
.bar-shortcut{color:bar;}
.hover\\\\:h-p4:hover{padding:1rem;}
@media (min-width: 640px){
.h-container{max-width:640px;}
}
Expand All @@ -26,8 +26,8 @@ exports[`prefix > preset prefix 2`] = `
.\\\\32 xl\\\\:h-container{max-width:1280px;}
}}
@media (min-width: 1536px){
.\\\\32 xl\\\\:h-container{max-width:100%;}
.h-container{max-width:1536px;}
.\\\\32 xl\\\\:h-container{max-width:100%;}
}
@media (min-width: 1536px){@media (min-width: 1536px){
.\\\\32 xl\\\\:h-container{max-width:1536px;}
Expand Down