diff --git a/packages/core/src/generator/index.ts b/packages/core/src/generator/index.ts index c3f503b167..aa7fe0923b 100644 --- a/packages/core/src/generator/index.ts +++ b/packages/core/src/generator/index.ts @@ -1,5 +1,5 @@ import { createNanoEvents } from '../utils/events' -import type { CSSEntries, CSSObject, DynamicRule, ExtendedTokenInfo, ExtractorContext, GenerateOptions, GenerateResult, ParsedUtil, PreflightContext, PreparedRule, RawUtil, ResolvedConfig, RuleContext, RuleMeta, Shortcut, ShortcutValue, StringifiedUtil, UserConfig, UserConfigDefaults, UtilObject, Variant, VariantContext, VariantHandler, VariantHandlerContext, VariantMatchedResult } from '../types' +import type { 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 { 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' @@ -182,11 +182,18 @@ export class UnoGenerator { : input if (safelist) { - this.config.safelist.forEach((s) => { - // We don't want to increment count if token is already in the set - if (!tokens.has(s)) - tokens.add(s) - }) + const safelistContext: SafeListContext = { + generator: this, + theme: this.config.theme, + } + + this.config.safelist + .flatMap(s => typeof s === 'function' ? s(safelistContext) : s) + .forEach((s) => { + // We don't want to increment count if token is already in the set + if (!tokens.has(s)) + tokens.add(s) + }) } const nl = minify ? '' : '\n' @@ -284,7 +291,7 @@ export class UnoGenerator { || a[2]?.localeCompare(b[2] || '') // body || 0 }) - .map(([, selector, body,, meta,, variantNoMerge]) => { + .map(([, selector, body, , meta, , variantNoMerge]) => { const scopedSelector = selector ? applyScope(selector, scope) : selector return [ [[scopedSelector ?? '', meta?.sort ?? 0]], @@ -725,8 +732,8 @@ export class UnoGenerator { } const merges = [ - [e.filter(([, noMerge]) => noMerge).map(([entries,, sort]) => [entries, sort]), true], - [e.filter(([, noMerge]) => !noMerge).map(([entries,, sort]) => [entries, sort]), false], + [e.filter(([, noMerge]) => noMerge).map(([entries, , sort]) => [entries, sort]), true], + [e.filter(([, noMerge]) => !noMerge).map(([entries, , sort]) => [entries, sort]), false], ] as [[CSSEntries, number][], boolean][] return merges.map(([e, noMerge]) => [ diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index aad2842626..98c6ff4127 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -131,6 +131,8 @@ export interface PreflightContext { theme: Theme } +export interface SafeListContext extends PreflightContext { } + export interface Extractor { name: string order?: number @@ -359,7 +361,7 @@ export interface ConfigBase { /** * Utilities that always been included */ - safelist?: string[] + safelist?: (string | ((context: SafeListContext) => Arrayable))[] /** * Extractors to handle the source file and outputs possible classes/selectors @@ -686,7 +688,7 @@ export interface ContentOptions { /** * Inline text to be extracted */ - inline?: (string | { code: string, id?: string } | (() => Awaitable)) [] + inline?: (string | { code: string, id?: string } | (() => Awaitable))[] /** * Filters to determine whether to extract certain modules from the build tools' transformation pipeline. @@ -722,7 +724,7 @@ export interface ContentOptions { /** * @deprecated Renamed to `inline` */ - plain?: (string | { code: string, id?: string }) [] + plain?: (string | { code: string, id?: string })[] } /** @@ -778,12 +780,12 @@ export interface PluginOptions { exclude?: FilterPattern } -export interface UserConfig extends ConfigBase, UserOnlyOptions, GeneratorOptions, PluginOptions, CliOptions {} -export interface UserConfigDefaults extends ConfigBase, UserOnlyOptions {} +export interface UserConfig extends ConfigBase, UserOnlyOptions, GeneratorOptions, PluginOptions, CliOptions { } +export interface UserConfigDefaults extends ConfigBase, UserOnlyOptions { } export interface ResolvedConfig extends Omit< -RequiredByKey, 'mergeSelectors' | 'theme' | 'rules' | 'variants' | 'layers' | 'extractors' | 'blocklist' | 'safelist' | 'preflights' | 'sortLayers'>, -'rules' | 'shortcuts' | 'autocomplete' + RequiredByKey, 'mergeSelectors' | 'theme' | 'rules' | 'variants' | 'layers' | 'extractors' | 'blocklist' | 'safelist' | 'preflights' | 'sortLayers'>, + 'rules' | 'shortcuts' | 'autocomplete' > { presets: Preset[] shortcuts: Shortcut[] diff --git a/test/safelist.test.ts b/test/safelist.test.ts index 01160cf724..5459b80431 100644 --- a/test/safelist.test.ts +++ b/test/safelist.test.ts @@ -10,10 +10,13 @@ describe('safelist', () => { ], safelist: [ 'm1', + () => ['m3', 'm4'], ], }) const { css } = await uno.generate('m2') expect(css).toContain('.m1') expect(css).toContain('.m2') + expect(css).toContain('.m3') + expect(css).toContain('.m4') }) })