-
-
Notifications
You must be signed in to change notification settings - Fork 780
/
index.ts
135 lines (117 loc) · 4.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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import type { SourceCodeTransformer } from '@unocss/core'
import { escapeRegExp, expandVariantGroup } from '@unocss/core'
export interface CompileClassOptions {
/**
* Trigger regex literal. The default trigger regex literal matches `:uno:`,
* for example: `<div class=":uno: font-bold text-white">`.
*
* @example
* The trigger additionally allows defining a capture group named `name`, which
* allows custom class names. One possible regex would be:
*
* ```
* export default defineConfig({
* transformers: [
* transformerCompileClass({
* trigger: /(["'`]):uno(?:-)?(?<name>[^\s\1]+)?:\s([^\1]*?)\1/g
* }),
* ],
* })
* ```
*
* This regular expression matches `:uno-MYNAME:` and uses `MYNAME` in
* combination with the class prefix as the final class name, for example:
* `.uno-MYNAME`. It should be noted that the regex literal needs to include
* the global flag `/g`.
*
* @note
* This parameter is backwards compatible. It accepts string only trigger
* words, like `:uno:` or a regex literal.
*
* @default `/(["'`]):uno:\s([^\1]*?)\1/g`
*/
trigger?: string | RegExp
/**
* Prefix for compile class name
* @default 'uno-'
*/
classPrefix?: string
/**
* Hash function
*/
hashFn?: (str: string) => string
/**
* Left unknown classes inside the string
*
* @default true
*/
keepUnknown?: boolean
/**
* The layer name of generated rules
*/
layer?: string
}
export default function transformerCompileClass(options: CompileClassOptions = {}): SourceCodeTransformer {
const {
trigger = /(["'`]):uno:\s([^\1]*?)\1/g,
classPrefix = 'uno-',
hashFn = hash,
keepUnknown = true,
} = options
// Provides backwards compatibility. We either accept a trigger string which
// gets turned into a regexp (like previously) or a regex literal directly.
const regexp = typeof trigger === 'string'
? RegExp(`(["'\`])${escapeRegExp(trigger)}\\s([^\\1]*?)\\1`, 'g')
: trigger
return {
name: '@unocss/transformer-compile-class',
enforce: 'pre',
async transform(s, _, { uno, tokens }) {
const matches = [...s.original.matchAll(regexp)]
if (!matches.length)
return
for (const match of matches) {
let body = (match.length === 4 && match.groups)
? expandVariantGroup(match[3].trim())
: expandVariantGroup(match[2].trim())
const start = match.index!
const replacements = []
if (keepUnknown) {
const result = await Promise.all(body.split(/\s+/).filter(Boolean).map(async i => [i, !!await uno.parseToken(i)] as const))
const known = result.filter(([, matched]) => matched).map(([i]) => i)
const unknown = result.filter(([, matched]) => !matched).map(([i]) => i)
replacements.push(...unknown)
body = known.join(' ')
}
if (body) {
body = body.split(/\s+/).sort().join(' ')
const className = (match.groups && match.groups.name)
? `${classPrefix}${match.groups.name}`
: `${classPrefix}${hashFn(body)}`
// FIXME: Ideally we should also check that the hash doesn't match. If the hash is the same, the same class
// name is allowed, as the applied styles are the same.
if (tokens && tokens.has(className))
throw new Error(`duplicate compile class name '${className}', please choose different class name`)
replacements.unshift(className)
if (options.layer)
uno.config.shortcuts.push([className, body, { layer: options.layer }])
else
uno.config.shortcuts.push([className, body])
if (tokens)
tokens.add(className)
}
s.overwrite(start + 1, start + match[0].length - 1, replacements.join(' '))
}
},
}
}
function hash(str: string) {
let i
let l
let hval = 0x811C9DC5
for (i = 0, l = str.length; i < l; i++) {
hval ^= str.charCodeAt(i)
hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24)
}
return (`00000${(hval >>> 0).toString(36)}`).slice(-6)
}