Skip to content

Commit

Permalink
strip unused keyframes, fixes #14 (#57)
Browse files Browse the repository at this point in the history
* strip unused keyframes, fixes #14

* remove console log

* excess star

* tsc confusion

* leftover comment
  • Loading branch information
Peter Bengtsson committed Jan 3, 2018
1 parent c023ff1 commit 31847f7
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 1 deletion.
9 changes: 9 additions & 0 deletions .editorconfig
@@ -0,0 +1,9 @@
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
4 changes: 4 additions & 0 deletions README.md
Expand Up @@ -100,6 +100,10 @@ It can optionally use `PhantomJS` to extract the HTML.
JavaScript code has changed the DOM. That means you can extract the
critical CSS needed to display properly before the JavaScript has kicked in.

* Ability to analyze the remaining CSS selectors to see which keyframe
animations that they use and use this to delete keyframe definitions
that are no longer needed.

## State of the project

This is highly experimental.
Expand Down
97 changes: 97 additions & 0 deletions examples/animations/index.css
@@ -0,0 +1,97 @@
.loading, .loaded {
margin-top: 100px;
text-align: center;
}

/* Portrait and Landscape */
@media only screen
and (min-device-width: 768px)
and (max-device-width: 1024px)
and (-webkit-min-device-pixel-ratio: 1) {
.loading, .loaded {
margin-top: 50px;
}
}

.loading {
animation: App-logo-spin infinite 5s linear;
height: 80px;
}

@keyframes
App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

.loaded {
animation-name: pulse;
animation-duration: 3s;
animation-iteration-count: infinite;
animation-timing-function: ease-out;
}

@keyframes pulse {
0% {
background-color: #fff5f5;
color: #000;
}
100% {
background-color: #8ac3ff;
color: #eee;
}
}

.stretcher {
height: 250px;
width: 250px;
margin: 0 auto;
background-color: red;
animation-name: stretch;
animation-duration: 1.5s;
animation-timing-function: ease-out;
animation-delay: 0;
animation-direction: alternate;
animation-iteration-count: infinite;
animation-fill-mode: none;
animation-play-state: running;
}

@keyframes stretch {
0% {
transform: scale(.3);
background-color: red;
border-radius: 100%;
}
50% {
background-color: orange;
}
100% {
transform: scale(1.5);
background-color: yellow;
}
}



@-webkit-keyframes progress-bar-stripes {
0% {
background-position: 1rem 0
}
to {
background-position: 0 0
}
}

@-o-keyframes progress-bar-stripes {
0% {
background-position: 1rem 0
}
to {
background-position: 0 0
}
}
21 changes: 21 additions & 0 deletions examples/animations/index.html
@@ -0,0 +1,21 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<link href="index.css" rel="stylesheet">
</head>
<body>
<p class="loading">
L O A D I N G
</p>
<script>
window.onload = () => {
setTimeout(() => {
let el = document.querySelector('.loading')
el.classList.remove('loading')
el.classList.add('loaded')
}, 10)
};
</script>
</body>
</html>
57 changes: 56 additions & 1 deletion src/run.js
Expand Up @@ -10,6 +10,58 @@ const cheerio = require('cheerio')
const utils = require('./utils')
const url = require('url')

/**
*
* @param {Object} ast
* @return Object
*/
const postProcessKeyframes = ast => {
const activeAnimationNames = new Set()
// First walk the AST to know which animations are ever mentioned
// by the remaining selectors.
csstree.walk(ast, node => {
if (node.type === 'Declaration') {
if (
node.property.search(/\banimation$/i) > -1 ||
node.property.search(/\banimation-name$/i) > -1
) {
// E.g. `animation: thename infinite 5s linear`
// Or `animation-name: thename`
let firstName = false
node.value.children.each(child => {
if (child.type === 'Identifier' && child.name && !firstName) {
activeAnimationNames.add(child.name.toLowerCase())
firstName = true
}
})
}
}
})
// This is the function we use to filter children out.
const cleanChildren = (children, callback) => {
return children.filter(child => {
// The reason for the '\bkeyframes$' regex here is because you might
// have CSS that looks like this:
// @-webkit-keyframes progress-bar-stripes {
// ...
// }
// Bootstrap v3 has this for example.
if (child.type === 'Atrule' && child.name.search(/\bkeyframes$/i) > -1) {
const keyframeName = child.prelude.children[0].name
return callback(keyframeName)
}
return true
})
}
// First convert the AST object into a plain object so we can mutate
// the plain array that is 'children'.
const obj = csstree.toPlainObject(ast)
obj.children = cleanChildren(obj.children, keyframename => {
return activeAnimationNames.has(keyframename.toLowerCase())
})
return csstree.fromPlainObject(obj)
}

/**
*
* @param {{ urls: Array<string>, debug: boolean, loadimages: boolean, skippable: function, browser: any, userAgent: string, withoutjavascript: boolean }} options
Expand Down Expand Up @@ -294,7 +346,10 @@ const minimalcss = async options => {
// The csso.minify() function will solve this, *and* whitespace minify
// it too.
let finalCss = utils.collectImportantComments(allCombinedCss)
finalCss = csso.minify(finalCss).css
let csstreeAst = csstree.parse(csso.minify(finalCss).css)
csstreeAst = postProcessKeyframes(csstreeAst)
finalCss = csstree.translate(csstreeAst)

const returned = { finalCss, stylesheetAstObjects, stylesheetContents }
return Promise.resolve(returned)
}
Expand Down

0 comments on commit 31847f7

Please sign in to comment.