Skip to content

Commit 1158771

Browse files
authored
add animations report (#159)
1 parent a401060 commit 1158771

File tree

9 files changed

+421
-3
lines changed

9 files changed

+421
-3
lines changed

package-lock.json

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
"postcss": "^7.0.17",
7171
"postcss-values-parser": "^3.0.5",
7272
"specificity": "^0.4.1",
73+
"split-css-value": "^0.1.1",
7374
"split-lines": "^2.0.0",
7475
"string-natural-compare": "^2.0.3",
7576
"tinycolor2": "^1.4.1",

readme.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ yarn add @projectwallace/css-analyzer
2929
## Usage
3030

3131
```js
32-
const analyze = require('css-analyzer');
32+
const analyze = require('@projectwallace/css-analyzer');
3333

3434
analyze('foo{}')
3535
.then(result => console.log(result))
@@ -144,6 +144,12 @@ analyze('foo{}')
144144
// 'stylesheets.size': 5,
145145
// 'stylesheets.browserhacks.total': 0,
146146
// 'stylesheets.browserhacks.totalUnique': 0,
147+
// 'values.animations.durations.total': 0,
148+
// 'values.animations.durations.totalUnique': 0,
149+
// 'values.animations.durations.unique': [],
150+
// 'values.animations.timingFunctions.total': 0,
151+
// 'values.animations.timingFunctions.totalUnique': 0,
152+
// 'values.animations.timingFunctions.unique': [],
147153
// 'values.boxshadows.total': 0,
148154
// 'values.boxshadows.unique': [],
149155
// 'values.boxshadows.totalUnique': 0,

src/analyzer/values/animations.js

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
const {parse} = require('postcss-values-parser')
2+
const splitValue = require('split-css-value')
3+
const uniquer = require('../../utils/uniquer')
4+
const {KEYWORDS} = require('../../utils/css')
5+
const durationSort = require('../../utils/duration-sort')
6+
7+
function getSingleDuration(animation) {
8+
let duration
9+
10+
parse(animation).walkNumerics(node => {
11+
// The first time-value is always the duration, as per spec
12+
if (duration) {
13+
return
14+
}
15+
16+
duration = node.toString()
17+
})
18+
19+
return duration
20+
}
21+
22+
function getDuration(animation) {
23+
return splitValue(animation)
24+
.map(animation => getSingleDuration(animation))
25+
.filter(Boolean)
26+
}
27+
28+
function getSingleTimingFunction(animation) {
29+
let timingFunction
30+
31+
parse(animation).walk(node => {
32+
// There should only be one timing function per shorthand
33+
if (timingFunction) {
34+
return
35+
}
36+
37+
// Look for timing keywords
38+
if (
39+
node.type === 'word' &&
40+
[
41+
'ease',
42+
'ease-in',
43+
'ease-in-out',
44+
'ease-out',
45+
'linear',
46+
'step-start',
47+
'step-end'
48+
].includes(node.toString())
49+
) {
50+
timingFunction = node.toString()
51+
return
52+
}
53+
54+
// If there's no keyword, it should either be cubic-bezier() or steps()
55+
if (node.type === 'func' && ['cubic-bezier', 'steps'].includes(node.name)) {
56+
timingFunction = node.toString()
57+
}
58+
})
59+
60+
return timingFunction
61+
}
62+
63+
function getTimingFunction(animation) {
64+
return splitValue(animation)
65+
.map(animation => getSingleTimingFunction(animation))
66+
.filter(Boolean)
67+
}
68+
69+
module.exports = declarations => {
70+
const all = declarations.filter(({value}) => !KEYWORDS.includes(value))
71+
72+
const durations = all
73+
// First, find all durations directly
74+
.filter(({property}) =>
75+
['animation-duration', 'transition-duration'].includes(property)
76+
)
77+
.map(declaration => declaration.value)
78+
// Then, find more through the shorthand declarations
79+
.concat(
80+
...all
81+
.filter(({property}) => ['animation', 'transition'].includes(property))
82+
.map(({value}) => getDuration(value))
83+
)
84+
85+
const {unique: uniqueDurations, totalUnique: totalUniqueDurations} = uniquer(
86+
durations,
87+
durationSort
88+
)
89+
90+
const timingFunctions = all
91+
// First, find all timing-functions directly
92+
.filter(({property}) =>
93+
['animation-timing-function', 'transition-timing-function'].includes(
94+
property
95+
)
96+
)
97+
.map(declaration => declaration.value)
98+
// Then, find more through the shorthand declarations
99+
.concat(
100+
...all
101+
.filter(({property}) => ['animation', 'transition'].includes(property))
102+
.map(({value}) => getTimingFunction(value))
103+
)
104+
105+
const {
106+
unique: uniqueTimingFunctions,
107+
totalUnique: totalUniqueTimingFunctions
108+
} = uniquer(timingFunctions)
109+
110+
return {
111+
durations: {
112+
total: durations.length,
113+
unique: uniqueDurations,
114+
totalUnique: totalUniqueDurations
115+
},
116+
timingFunctions: {
117+
total: timingFunctions.length,
118+
unique: uniqueTimingFunctions,
119+
totalUnique: totalUniqueTimingFunctions
120+
}
121+
}
122+
}

src/analyzer/values/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ module.exports = declarations => {
99
const boxshadows = require('./box-shadows.js')(declarations)
1010
const textshadows = require('./text-shadows.js')(declarations)
1111
const zindexes = require('./z-indexes.js')(declarations)
12+
const animations = require('./animations.js')(declarations)
1213

1314
return {
1415
total: all.length,
@@ -19,6 +20,7 @@ module.exports = declarations => {
1920
browserhacks,
2021
boxshadows,
2122
textshadows,
22-
zindexes
23+
zindexes,
24+
animations
2325
}
2426
}

src/utils/duration-sort.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
function durationToSeconds(duration) {
2+
if (/\d+ms$/.test(duration)) {
3+
return parseInt(duration, 10) / 1000
4+
}
5+
6+
// Complicated workaround for parseInt '0.002s' => 0
7+
duration = duration.replace(/s|ms/, '') * 1000
8+
return parseInt(duration, 10) / 1000
9+
}
10+
11+
module.exports = (a, b) => {
12+
const A = durationToSeconds(a)
13+
const B = durationToSeconds(b)
14+
15+
if (A === B) {
16+
return a.endsWith('ms') ? -1 : 1
17+
}
18+
19+
return A - B
20+
}

test/analyzer/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,12 @@ test('it returns the correct analysis object structure', async t => {
131131
'stylesheets.size': 5,
132132
'stylesheets.browserhacks.total': 0,
133133
'stylesheets.browserhacks.totalUnique': 0,
134+
'values.animations.durations.total': 0,
135+
'values.animations.durations.totalUnique': 0,
136+
'values.animations.durations.unique': [],
137+
'values.animations.timingFunctions.total': 0,
138+
'values.animations.timingFunctions.totalUnique': 0,
139+
'values.animations.timingFunctions.unique': [],
134140
'values.boxshadows.total': 0,
135141
'values.boxshadows.unique': [],
136142
'values.boxshadows.totalUnique': 0,

0 commit comments

Comments
 (0)