Skip to content

Commit 076c027

Browse files
committed
feat: enhance keyframes handling to preserve animations referenced via CSS variables
#478
1 parent 4a24603 commit 076c027

File tree

3 files changed

+65
-0
lines changed

3 files changed

+65
-0
lines changed

docs/configuration.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,20 @@ await new PurgeCSS().purge({
206206

207207
If you are using a CSS animation library such as animate.css, you can remove unused keyframes by setting the `keyframes` option to `true`.
208208

209+
PurgeCSS detects used keyframes by scanning `animation` and `animation-name` property values, as well as CSS custom property (variable) values. This means keyframes referenced via CSS variables will be correctly preserved:
210+
211+
```css
212+
.component {
213+
animation: var(--component-animation);
214+
}
215+
.component--animated {
216+
--component-animation: fadeIn 0.4s;
217+
}
218+
@keyframes fadeIn {
219+
/* This keyframe will be preserved because "fadeIn" appears in --component-animation */
220+
}
221+
```
222+
209223
```js
210224
await new PurgeCSS().purge({
211225
content: ['index.html', '**/*.js', '**/*.html', '**/*.vue'],

packages/purgecss/__tests__/keyframes.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,47 @@ describe("keep webkit keyframe decimals", () => {
121121
expect(purgedCSS.includes("99.9%")).toBe(true);
122122
});
123123
});
124+
125+
describe("keep keyframes referenced via CSS variables", () => {
126+
let purgedCSS: string;
127+
beforeAll(async () => {
128+
const resultsPurge = await new PurgeCSS().purge({
129+
content: [
130+
{
131+
raw: '<div class="component component--animated"></div>',
132+
extension: "html",
133+
},
134+
],
135+
css: [
136+
{
137+
raw: `
138+
.component {
139+
animation: var(--component-animation);
140+
}
141+
.component--animated {
142+
--component-animation: fadeIn 0.4s;
143+
}
144+
@keyframes fadeIn {
145+
from { opacity: 0; }
146+
to { opacity: 1; }
147+
}
148+
@keyframes unused {
149+
from { opacity: 1; }
150+
to { opacity: 0; }
151+
}
152+
`,
153+
},
154+
],
155+
keyframes: true,
156+
});
157+
purgedCSS = resultsPurge[0].css;
158+
});
159+
160+
it("keeps `@keyframes fadeIn` referenced via CSS variable", () => {
161+
expect(purgedCSS.includes("@keyframes fadeIn")).toBe(true);
162+
});
163+
164+
it("removes `@keyframes unused`", () => {
165+
expect(purgedCSS.includes("@keyframes unused")).toBe(false);
166+
});
167+
});

packages/purgecss/src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,13 @@ class PurgeCSS {
404404
}
405405
return;
406406
}
407+
// Also check CSS custom properties for animation names
408+
// e.g., --my-animation: fadeIn 0.4s;
409+
if (prop.startsWith("--")) {
410+
for (const word of value.split(/[\s,]+/)) {
411+
this.usedAnimations.add(word);
412+
}
413+
}
407414
}
408415

409416
// collect font faces data

0 commit comments

Comments
 (0)