-
Notifications
You must be signed in to change notification settings - Fork 4
/
random-keyword-plugin.js
89 lines (72 loc) · 2.36 KB
/
random-keyword-plugin.js
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
import postcss from 'postcss';
let uid = 0;
const randomRegex = /--[\w\-]+|"[^"]+"|'[^']+'|url\([^\(]*\)|(random)/g;
const selectorRegex = /\[[^\]]+\]|(,)/g;
const pseudoElementsRegex =
/(:(?:before|after|first-letter|first-line)|::[a-z\-]+)$/;
export default postcss.plugin('random-keyword', (options) => {
return (css) => {
css.walkRules((rule) => {
const newRules = {};
rule.walkDecls((decl, i) => {
if (includesRandomKeyword(decl.value)) {
for (const el of document.querySelectorAll(rule.selector)) {
const pid = el.dataset.pid || (el.dataset.pid = uid++);
const newRule = newRules[pid] || (newRules[pid] = rule.clone({
selector: appendToSelectors(rule.selector, `[data-pid="${pid}"]`),
nodes: [],
}));
newRule.nodes.push(decl.clone({
value: resolveRandomKeywords(decl.value),
}));
}
decl.remove();
}
});
// Clone the current rule and update the selector.
rule.parent.insertBefore(rule, rule.clone({
selector: appendToSelectors(rule.selector, ':not(.z)')
}))
// Insert all the new rules before the current rule.
for (const id of Object.keys(newRules)) {
rule.parent.insertBefore(rule, newRules[id]);
}
// Remove the current rule and continue iterating.
rule.remove();
});
};
});
const appendToSelectors = (selectors, addition) => {
const parts = [];
let startIndex = 0;
let matches;
while (matches = selectorRegex.exec(selectors)) {
if (matches[1]) {
const endIndex = selectorRegex.lastIndex;
parts.push(selectors.slice(startIndex, endIndex - 1).trim());
startIndex = endIndex;
}
}
parts.push(selectors.slice(startIndex).trim());
return parts
.map((selector) => appendToSelector(selector, addition))
.join(', ');
};
const appendToSelector = (selector, addition) => {
if (pseudoElementsRegex.test(selector)) {
return selector.replace(pseudoElementsRegex, `${addition}$1`);
} else {
return selector + addition;
}
}
const includesRandomKeyword = (str) => {
let matches;
while (matches = randomRegex.exec(str)) {
if (matches[1]) return true;
}
}
const resolveRandomKeywords = (str) => {
return str.replace(randomRegex, (match, p1) => {
return p1 ? Math.random() : match;
});
}