Skip to content
Permalink
Browse files

feat: add safelist keyframes and css variables

- add safelist for keyframes (#418, #439)
- add safelist for css variables
  • Loading branch information
Ffloriel committed Jul 18, 2020
1 parent 41f1c48 commit dc59d309a4a4be9845c40966a19f9705c42a33a1
@@ -126,3 +126,53 @@ describe("safelist option: greedy", () => {
);
});
});

describe("safelist option: keyframes", () => {
let purgedCSS: string;
beforeAll(async () => {
const resultsPurge = await new PurgeCSS().purge({
content: [`${ROOT_TEST_EXAMPLES}safelist/safelist_keyframes.html`],
css: [`${ROOT_TEST_EXAMPLES}safelist/safelist_keyframes.css`],
safelist: {
keyframes: [/^scale/, "spin"],
},
keyframes: true,
});
purgedCSS = resultsPurge[0].css;
});

it("finds safelisted keyframes", () => {
findInCSS(
expect,
["@keyframes scale", "@keyframes scale-down", "@keyframes spin"],
purgedCSS
);
});

it("excludes non-safelisted keyframes", () => {
notFindInCSS(expect, ["flash"], purgedCSS);
});
});

describe("safelist option: variables", () => {
let purgedCSS: string;
beforeAll(async () => {
const resultsPurge = await new PurgeCSS().purge({
content: [`${ROOT_TEST_EXAMPLES}safelist/safelist_css_variables.html`],
css: [`${ROOT_TEST_EXAMPLES}safelist/safelist_css_variables.css`],
safelist: {
variables: [/^--b/, "--unused-color"],
},
variables: true,
});
purgedCSS = resultsPurge[0].css;
});

it("finds safelisted css variables", () => {
findInCSS(expect, ["--unused-color", "--button-color"], purgedCSS);
});

it("excludes non-safelisted css variables", () => {
notFindInCSS(expect, ["--tertiary-color:"], purgedCSS);
});
});
@@ -0,0 +1,22 @@
:root {
--primary-color: blue;
--secondary-color: indigo;
--tertiary-color: aqua;
--unused-color: violet;
--used-color: rebeccapurple;
--accent-color: orange;
}

.button {
--button-color: var(--tertiary-color);
--border-color: linear-gradient(to top, var(--secondary-color), var(--used-color, white));

background-color: var(--primary-color);
color: var(--accent-color);
border-color: var(--border-color);
}

.button:focus {
background-color: var(--accent-color);
color: var(--primary-color);
}
@@ -0,0 +1 @@
<button class="button">Click me!</button>
@@ -0,0 +1,61 @@
@keyframes bounce {
from, 20%, 53%, 80%, to {
animation-timing-function: cubic-bezier(0.3, 0.1, 0.9, 1.000);
transform: translate3d(1, 1, 0);
}
}

.bounce {
-webkit-animation-name: bounce;
animation-name: bounce;
-webkit-transform-origin: center bottom;
transform-origin: center bottom;
}

@keyframes flash {
from, 50%, to {
opacity: 1;
}

25%, 75% {
opacity: 0.5;
}
}

.flash {
animation: flash
}

@keyframes scale {
from {
transform: scale(1);
}

to {
transform: scale(2);
}
}

@keyframes scale-down {
from {
transform: scale(2);
}

to {
transform: scale(1);
}
}

@keyframes spin {
from {
transform: rotate(0deg);
}

to {
transform: rotate(360deg);
}
}

.scale-spin {
animation: spin 300ms linear infinite forwards,scale 300ms linear infinite alternate;
}
@@ -0,0 +1,2 @@
<div class="bounce">
</div>
@@ -1,4 +1,5 @@
import postcss from "postcss";
import { StringRegExpArray } from "./types";

class VariableNode {
public nodes: VariableNode[] = [];
@@ -13,6 +14,7 @@ class VariableNode {
class VariablesStructure {
public nodes: Map<string, VariableNode> = new Map();
public usedVariables: Set<string> = new Set();
public safelist: StringRegExpArray = [];

addVariable(declaration: postcss.Declaration): void {
const { prop } = declaration;
@@ -63,12 +65,20 @@ class VariablesStructure {
for (const used of this.usedVariables) {
this.setAsUsed(used);
}
for (const [, declaration] of this.nodes) {
if (!declaration.isUsed) {
for (const [name, declaration] of this.nodes) {
if (!declaration.isUsed && !this.isVariablesSafelisted(name)) {
declaration.value.remove();
}
}
}

isVariablesSafelisted(variable: string): boolean {
return this.safelist.some((safelistItem) => {
return typeof safelistItem === "string"
? safelistItem === variable
: safelistItem.test(variable);
});
}
}

export default VariablesStructure;
@@ -542,6 +542,18 @@ class PurgeCSS {
return sources;
}

/**
* Check if the keyframe is safelisted with the option safelist keyframes
* @param keyframesName name of the keyframe animation
*/
private isKeyframesSafelisted(keyframesName: string): boolean {
return this.options.safelist.keyframes.some((safelistItem) => {
return typeof safelistItem === "string"
? safelistItem === keyframesName
: safelistItem.test(keyframesName);
});
}

/**
* Check if the selector is safelisted with the option safelist standard
* @param selector css selector
@@ -590,7 +602,11 @@ class PurgeCSS {
...userOptions,
safelist: standardizeSafelist(userOptions.safelist),
};
const { content, css, extractors } = this.options;
const { content, css, extractors, safelist } = this.options;

if (this.options.variables) {
this.variablesStructure.safelist = safelist.variables || [];
}

const fileFormatContents = content.filter(
(o) => typeof o === "string"
@@ -637,7 +653,10 @@ class PurgeCSS {
*/
public removeUnusedKeyframes(): void {
for (const node of this.atRules.keyframes) {
if (!this.usedAnimations.has(node.params)) {
if (
!this.usedAnimations.has(node.params) &&
!this.isKeyframesSafelisted(node.params)
) {
node.remove();
}
}

0 comments on commit dc59d30

Please sign in to comment.
You can’t perform that action at this time.