@invisibleloop/pulse 0.18.0 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/public/.pulse-ui-version +1 -1
- package/scripts/css-purge.js +15 -0
- package/scripts/css-purge.test.js +37 -0
package/package.json
CHANGED
package/public/.pulse-ui-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.18.
|
|
1
|
+
0.18.1
|
package/scripts/css-purge.js
CHANGED
|
@@ -35,6 +35,21 @@ export function extractUsedClasses(htmlContents, jsContents, safelist = []) {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
// classList method arguments are class names BY DEFINITION — collect them even
|
|
39
|
+
// without a hyphen. The generic string scan above requires a hyphen to avoid
|
|
40
|
+
// swallowing every English word, which silently missed runtime state classes
|
|
41
|
+
// (classList.add('open'), .toggle('active')) and purged their compound rules
|
|
42
|
+
// (`.docs-sidebar.open`) from production CSS while dev looked fine.
|
|
43
|
+
for (const js of jsContents) {
|
|
44
|
+
const re = /classList\s*\.\s*(?:add|remove|toggle|replace)\s*\(\s*((?:['"][^'"]+['"]\s*,?\s*)+)/g
|
|
45
|
+
let m
|
|
46
|
+
while ((m = re.exec(js)) !== null) {
|
|
47
|
+
for (const [, cls] of m[1].matchAll(/['"]([^'"]+)['"]/g)) {
|
|
48
|
+
if (/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(cls)) used.add(cls)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
38
53
|
// Apply safelist from pulse.config.js — css.safelist accepts strings or RegExp.
|
|
39
54
|
// String entries are added directly; RegExp entries are matched against all
|
|
40
55
|
// class names defined in the CSS (resolved later in purgeCss via selectorUsed
|
|
@@ -92,3 +92,40 @@ test('minifyCss: calc() expressions keep their operator spacing', () => {
|
|
|
92
92
|
const out = minifyCss(`.x { width: calc(100% - 2rem); }`)
|
|
93
93
|
assert.ok(out.includes('calc(100% - 2rem)'), `calc broken: ${out}`)
|
|
94
94
|
})
|
|
95
|
+
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Runtime state classes — classList arguments
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// Regression: the JS string scan only keeps hyphenated names (to avoid keeping
|
|
100
|
+
// every English word), so runtime state classes like classList.add('open') were
|
|
101
|
+
// never collected — and selectorUsed checks the rightmost class token, so
|
|
102
|
+
// `.docs-sidebar.open { … }` was purged from production CSS. The mobile docs
|
|
103
|
+
// hamburger toggled the class, but the rule that slides the sidebar in was gone.
|
|
104
|
+
|
|
105
|
+
test('classList method arguments are collected regardless of hyphens', () => {
|
|
106
|
+
const used = extractUsedClasses([], [
|
|
107
|
+
`sidebar.classList.add('open')`,
|
|
108
|
+
`overlay.classList.remove("visible")`,
|
|
109
|
+
`el.classList.toggle('active', isOn)`,
|
|
110
|
+
`el.classList.replace('open', 'closed')`,
|
|
111
|
+
])
|
|
112
|
+
for (const cls of ['open', 'visible', 'active', 'closed']) {
|
|
113
|
+
assert.ok(used.has(cls), `Expected classList arg "${cls}" to be collected`)
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
test('multiple classList arguments in one call are all collected', () => {
|
|
118
|
+
const used = extractUsedClasses([], [`el.classList.add('one', 'two-x', 'three')`])
|
|
119
|
+
for (const cls of ['one', 'two-x', 'three']) assert.ok(used.has(cls), cls)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
test('runtime-toggled compound rule survives the purge', () => {
|
|
123
|
+
const css = `.docs-sidebar { transform: translateX(-100%); }\n.docs-sidebar.open { transform: translateX(0); }\n.unused { color: red; }`
|
|
124
|
+
const used = extractUsedClasses(
|
|
125
|
+
[`<aside class="docs-sidebar">`],
|
|
126
|
+
[`document.querySelector('.docs-sidebar').classList.add('open')`]
|
|
127
|
+
)
|
|
128
|
+
const out = purgeCss(css, used)
|
|
129
|
+
assert.ok(out.includes('.docs-sidebar.open'), `Compound state rule was purged: ${out}`)
|
|
130
|
+
assert.ok(!out.includes('.unused'), 'Unused rule should still be purged')
|
|
131
|
+
})
|