Skip to content

Commit

Permalink
feat(core): support generator in rule matcher (unocss#3884)
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu authored and Simon-He95 committed Jun 12, 2024
1 parent b9a660c commit 82bdf08
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 51 deletions.
20 changes: 18 additions & 2 deletions packages/core/src/generator/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createNanoEvents } from '../utils/events'
import type { BlocklistMeta, BlocklistValue, CSSEntries, CSSObject, DynamicRule, ExtendedTokenInfo, ExtractorContext, GenerateOptions, GenerateResult, ParsedUtil, PreflightContext, PreparedRule, RawUtil, ResolvedConfig, RuleContext, RuleMeta, SafeListContext, Shortcut, ShortcutValue, StringifiedUtil, UserConfig, UserConfigDefaults, UtilObject, Variant, VariantContext, VariantHandler, VariantHandlerContext, VariantMatchedResult } from '../types'
import type { BlocklistMeta, BlocklistValue, CSSEntries, CSSObject, CSSValue, DynamicRule, ExtendedTokenInfo, ExtractorContext, GenerateOptions, GenerateResult, ParsedUtil, PreflightContext, PreparedRule, RawUtil, ResolvedConfig, RuleContext, RuleMeta, SafeListContext, Shortcut, ShortcutValue, StringifiedUtil, UserConfig, UserConfigDefaults, UtilObject, Variant, VariantContext, VariantHandler, VariantHandlerContext, VariantMatchedResult } from '../types'
import { resolveConfig } from '../config'
import { BetterMap, CONTROL_SHORTCUT_NO_MERGE, CountableSet, TwoKeyMap, e, entriesToCss, expandVariantGroup, isCountableSet, isRawUtil, isStaticShortcut, isString, noop, normalizeCSSEntries, normalizeCSSValues, notNull, toArray, uniq, warnOnce } from '../utils'
import { version } from '../../package.json'
Expand Down Expand Up @@ -557,13 +557,29 @@ export class UnoGenerator<Theme extends object = object> {
if (!match)
continue

const result = await handler(match, context)
let result = await handler(match, context)
if (!result)
continue

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

// Handle generator result
if (typeof result !== 'string') {
if (Symbol.asyncIterator in result) {
const entries: (CSSValue | string)[] = []
for await (const r of result) {
if (r)
entries.push(r)
}
result = entries
}
else if (Symbol.iterator in result && !Array.isArray(result)) {
result = Array.from(result)
.filter(notNull)
}
}

const entries = normalizeCSSValues(result).filter(i => i.length)
if (entries.length) {
return entries.map((e) => {
Expand Down
9 changes: 8 additions & 1 deletion packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,14 @@ export interface RuleMeta {
export type CSSValue = CSSObject | CSSEntries
export type CSSValues = CSSValue | CSSValue[]

export type DynamicMatcher<Theme extends object = object> = ((match: RegExpMatchArray, context: Readonly<RuleContext<Theme>>) => Awaitable<CSSValue | string | (CSSValue | string)[] | undefined>)
export type DynamicMatcher<Theme extends object = object> =
(
match: RegExpMatchArray,
context: Readonly<RuleContext<Theme>>
) => Awaitable<CSSValue | string | (CSSValue | string)[] | undefined>
| Generator<CSSValue | string | undefined>
| AsyncGenerator<CSSValue | string | undefined>

export type DynamicRule<Theme extends object = object> = [RegExp, DynamicMatcher<Theme>] | [RegExp, DynamicMatcher<Theme>, RuleMeta]
export type StaticRule = [string, CSSObject | CSSEntries] | [string, CSSObject | CSSEntries, RuleMeta]
export type Rule<Theme extends object = object> = DynamicRule<Theme> | StaticRule
Expand Down
48 changes: 0 additions & 48 deletions packages/core/test/generate-async.test.ts

This file was deleted.

46 changes: 46 additions & 0 deletions packages/core/test/rule-async.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { createGenerator } from '@unocss/core'
import { expect, it } from 'vitest'

it('rule-first', async () => {
const order: number[] = []
const uno = createGenerator({
rules: [
[/^rule$/, () => new Promise(resolve => setTimeout(() => {
order.push(1)
resolve('/* rule */')
}, 10))],
],
preflights: [
{
getCSS: () => new Promise(resolve => setTimeout(() => {
order.push(2)
resolve('/* preflight */')
}, 20)),
},
],
})
await uno.generate('rule')
expect(order).eql([1, 2])
})

it('preflight at the end', async () => {
const order: number[] = []
const uno = createGenerator({
rules: [
[/^rule$/, () => new Promise(resolve => setTimeout(() => {
order.push(1)
resolve('/* rule */')
}, 20))],
],
preflights: [
{
getCSS: () => new Promise((resolve) => {
order.push(2)
resolve('/* preflight */')
}),
},
],
})
await uno.generate('rule')
expect(order).eql([1, 2])
})
79 changes: 79 additions & 0 deletions packages/core/test/rule-generator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { createGenerator } from '@unocss/core'
import { expect, it } from 'vitest'

it('rule-generator', async () => {
const uno = createGenerator({
rules: [
[/^rule$/, function* () {
yield {
color: 'red',
}
yield {
'font-size': '12px',
}
}],
],
})
expect((await uno.generate('rule')).css)
.toMatchInlineSnapshot(`
"/* layer: default */
.rule{color:red;}
.rule{font-size:12px;}"
`)
})

it('rule-generator async', async () => {
const uno = createGenerator({
rules: [
[/^rule$/, async function* () {
yield {
color: 'red',
}
await new Promise(resolve => setTimeout(resolve, 2))
yield {
'font-size': '12px',
}
yield {
'font-weight': 'bold',
}
}],
],
})
expect((await uno.generate('rule')).css)
.toMatchInlineSnapshot(`
"/* layer: default */
.rule{color:red;}
.rule{font-size:12px;}
.rule{font-weight:bold;}"
`)
})

it('rule-generator bail out', async () => {
const uno = createGenerator({
rules: [
[/^rule-(.*)$/, function () {
return {
content: '"fallback"',
}
}],
[/^rule-(.*)$/, function* ([_, str]) {
if (str === 'bail') {
return // early return should still work
}
yield {
color: str,
}
yield {
'font-size': '12px',
}
}],
],
})
expect((await uno.generate('rule-bail rule-red')).css)
.toMatchInlineSnapshot(`
"/* layer: default */
.rule-bail{content:"fallback";}
.rule-red{color:red;}
.rule-red{font-size:12px;}"
`)
})

0 comments on commit 82bdf08

Please sign in to comment.