-
Notifications
You must be signed in to change notification settings - Fork 9
/
index.ts
105 lines (92 loc) · 3.08 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
import type { SourceCodeTransformer, UnocssPluginContext } from 'unocss'
export interface RenameClassOptions {
/**
* Prefix for compile class name
* @default 'uno-'
*/
classPrefix?: string
/**
* Hash function
*/
hashFn?: (str: string) => string
/**
* The layer name of generated rules
*/
layer?: string
}
export default function transformerRenameClass(options: RenameClassOptions = {}): SourceCodeTransformer {
const {
classPrefix = 'uno-',
hashFn = hash,
layer = 'applet_shortcuts',
} = options
// Regular expression of characters to be escaped
const charReg = /[.:%!#()[\/\],]/
const classRE = /:?(hover-)?class=\".*?\"/g
const stringRE = /(['\`]).*?(['\`])/g
async function compileApplet(body: string, ctx: UnocssPluginContext): Promise<string[]> {
const { uno, tokens } = ctx
const replacements = []
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)
console.log('known', known)
console.log('unknown', unknown)
replacements.push(...unknown)
body = known.join(' ')
if (body) {
const hash = hashFn(body)
const className = `${classPrefix}${hash}`
replacements.unshift(className)
uno.config.shortcuts.push([className, body, { layer }])
tokens.add(className)
}
return replacements
}
return {
name: 'rename-class',
enforce: 'pre',
async transform(s, _, ctx) {
const classMatches = [...s.original.matchAll(classRE)]
for (const match of classMatches) {
// skip `... ? ... : ...`
if (/\?.*:/g.test(match[0]))
continue
const start = match.index!
const matchSplit = match[0].split('=')
const body = matchSplit[1].slice(1, -1)
if (charReg.test(body)) {
const replacements = await compileApplet(body, ctx)
s.overwrite(start, start + match[0].length, `${matchSplit[0]}="${replacements.join(' ')}"`)
}
}
const stringMatches = [...s.original.matchAll(stringRE)]
for (const match of stringMatches) {
// skip `${...}`
if (/\$\{.*\}/g.test(match[0]))
continue
// skip all the image formats in HTML
if (/\.(png|jpg|jpeg|gif|svg)/g.test(match[0]))
continue
// skip http(s)://
if (/^http(s)?:\/\//g.test(match[0]))
continue
const start = match.index!
const body = match[0].slice(1, -1)
if (charReg.test(body)) {
const replacements = await compileApplet(body, ctx)
s.overwrite(start, start + match[0].length, `'${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)
}