/
index.ts
118 lines (100 loc) · 3.1 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import { cssIdRE, toArray } from '@unocss/core'
import type { SourceCodeTransformer, UnoGenerator } from '@unocss/core'
import type { CssNode, List, ListItem } from 'css-tree'
import { parse, walk } from 'css-tree'
import type MagicString from 'magic-string'
import { handleThemeFn, themeFnRE } from './theme'
import { handleScreen } from './screen'
import { handleApply } from './apply'
export interface TransformerDirectivesOptions {
enforce?: SourceCodeTransformer['enforce']
/**
* Throw an error if utils or themes are not found.
*
* @default true
*/
throwOnMissing?: boolean
/**
* Treat CSS variables as @apply directives for CSS syntax compatible.
*
* Pass `false` to disable.
*
* @default ['--at-apply', '--uno-apply', '--uno']
*/
applyVariable?: false | string | string[]
/**
* Treat CSS variables as directives for CSS syntax compatible.
*
* Pass `false` to disable, or a string to use as a prefix.
*
* @deprecated use `applyVariable` to specify the full var name instead.
* @default '--at-'
*/
varStyle?: false | string
}
export interface TransformerDirectivesContext {
code: MagicString
uno: UnoGenerator
options: TransformerDirectivesOptions
applyVariable: string[]
offset?: number
filename?: string
}
export default function transformerDirectives(options: TransformerDirectivesOptions = {}): SourceCodeTransformer {
return {
name: 'css-directive',
enforce: options?.enforce,
idFilter: id => !!id.match(cssIdRE),
transform: (code, id, ctx) => {
return transformDirectives(code, ctx.uno, options, id)
},
}
}
export async function transformDirectives(
code: MagicString,
uno: UnoGenerator,
options: TransformerDirectivesOptions,
filename?: string,
originalCode?: string,
offset?: number,
) {
let { applyVariable } = options
const varStyle = options.varStyle
if (applyVariable === undefined) {
if (varStyle !== undefined)
applyVariable = varStyle ? [`${varStyle}apply`] : []
applyVariable = ['--at-apply', '--uno-apply', '--uno']
}
applyVariable = toArray(applyVariable || [])
const hasApply = code.original.includes('@apply') || applyVariable.some(s => code.original.includes(s))
const hasScreen = code.original.includes('@screen')
const hasThemeFn = code.original.match(themeFnRE)
if (!hasApply && !hasThemeFn && !hasScreen)
return
const ast = parse(originalCode || code.original, {
parseAtrulePrelude: false,
positions: true,
filename,
})
if (ast.type !== 'StyleSheet')
return
const stack: Promise<void>[] = []
const ctx: TransformerDirectivesContext = {
options,
applyVariable,
uno,
code,
filename,
offset,
}
const processNode = async (node: CssNode, _item: ListItem<CssNode>, _list: List<CssNode>) => {
if (hasScreen && node.type === 'Atrule')
handleScreen(ctx, node)
if (hasThemeFn && node.type === 'Declaration')
handleThemeFn(ctx, node)
if (hasApply && node.type === 'Rule')
await handleApply(ctx, node)
}
walk(ast, (...args) => stack.push(processNode(...args)))
await Promise.all(stack)
}