Minification Techniques - contivero/hasmin GitHub Wiki
- Erasing Unnecesary Whitespace
- Removing Comments
- Removing Semicolons at the End of Blocks
- Normalizing Dimensions of Absolute Units
- Removing Units from Null Dimensions
- Normalizing Numbers
- Removing Quotation Characters
- Using the Older Pseudo-Element Syntax
- Replacing 0% for 0
- Minifying <color>
- Minifying <gradient>
- Minifying <position>
- Minifying <repeat-style>
- Minifying <timing-function>
- Minifying the <An+B> Microsyntax
- Minifying <shadow>
- Minifying <transform-function>
- Minifying the transform property
- Minifying <filter-function>
- Minifying <bg-size>
- Minifying <border-radius>
- Minifying <basic-shape>
- Minifying Selectors
- Using Property Traits
- Simplifying Shorthands
- Replacing Property Specific Synonyms
- Converting Escaped Characters to Unicode
- Minifying the
@import
rule -
The
@keyframes
rule - The
@supports
rule - The
@media
rule
@media screen and (min-width: 767px) {
.icon-next {
height: 20px;
width: 20px
}
}
Can be reduced to:
@media screen and (min-width:767px){.icon-next{height:20px;width:20px}}
/* headings */
h1 /*, h2, h3 */, h4, h5 {
color: cyan; /* TODO change */
/* margin: 0; */
}
h1, h4, h5 {
color: cyan;
}
As specified in §4.1.8 of CSS2.1, and later also in §2 of CSS Syntax Module Level 3, semicolons are separators, not terminators. Hence, semicolons at the end of blocks may be removed. E.g.:
h1 {
font-family: Helvetica;
font-style: normal;
}
h1 {
font-family: Helvetica;
font-style: normal
}
Whenever using absolute units, we may replace them for an equivalent shorter dimension. This also works on media queries:
@media (min-width: 768px) {
.horizontal dt {
width: 160px;
}
@media (min-width: 8in) {
.horizontal dt {
width: 10pc;
}
Since the beginning of CSS (see §6.1), zero <length> values can be written without units. Following a discussion, the specification was updated in 2016 to allow also zero <angle> values to be written without units.
Keep in mind these are the only two dimension types that accept 0
as a value.
In other words, zero values of type <frequency>, <resolution>, or <time>
should not be stripped of their units.
padding: 0px;
transform: skew(0deg);
padding: 0;
transform: skew(0);
To normalize a number, what is done is
-
Remove any leading zeros
-
Remove any trailing zeros
-
Remove the positive number sign (+)
.weirdClass {
/* examples of 1. */
margin: 0250px;
border: 0.8in;
padding: -0.5em;
/* examples of 2. */
border-right: 4.50pt;
border-top: 2.0px;
/* example of 3. */
border-left: +5px;
}
.weirdClass { /* examples of 1. */
margin: 250px;
border: .8in;
padding: -.5em;
/* examples of 2. */
border-right: 4.5pt;
border-top: 2px;
/* example of 3. */
border-left: 5px; }
In the following cases explained below, quotations characters may be removed.
font-family: "Courier New", "serif";
font-family: Courier New, "serif";
"serif"
aren’t removed because serif
is a <generic-family> font. Unquoting "serif"
would make user-agents interpret the value as the serif
keyword instead of the intended homonymous font family.
Values in attribute selectors are either <string>s or identifiers. Thus, when a string can be represented as an identifier, the quotation marks may be removed. E.g.:
a[href="foo"]
a[href~='-bar']
a[href^="http://"]
a[href=foo]
a[href~=-bar]
a[href^="http://"]
"http://"
string, since http://
is not a valid identifier, hence quotes or the appropriate escape sequences must be used.
url("pagelogo.png")
url('//s.ytimg.com/img/vfljun.png')
url("fonts/BebasNeue Bold.eot")
url(pagelogo.png)
url(//s.ytimg.com/img/vfljun.png)
url("fonts/BebasNeue Bold.eot")
url()
because there is a space between BebasNeue
and Bold
.
To remove the quotes, the space could be replaced by %20
following URI percentage
encodings (as specified in 2.1 of RFC 3986), or an equivalent escape sequence such as \20
.
CSS2 prefixed both pseudo-classes and pseudo-elements with a single colon. To distinguish between the two, CSS2.1 changed the convention for pseudo-elements, prefixing them with two colons. However, since most browsers still support the old notation, the following pseudo-elements:
::after
::before
::first-line
::first-letter
:after
:before
:first-line
:first-letter
In several places either a <percentage>, or another data type inhabited by 0
can be used (such as <number>, or <length>). In these cases, it is usually—though
not always—possible to drop the percentage sign, and have it mean the same.
@keyframes a {
0% { margin: 0% }
100% { margin: 20%}
}
@keyframes a {
0% { margin: 0 }
100% { margin: 20%}
}
0%
in margin gets turned into 0
, because keyframe selectors accept only <percentage>s (aside from to
and from
, which are just synonyms for 100%
and 0%
), thus replacing it would be invalid.
Known exceptions to this are height
and max-height
, where 0
and %0
mean
different things, so Hasmin doesn’t apply the replacement in those cases.
There are many different equivalent ways to denote colors, so we look for the shortest. For example, the following values:
rgb(238, 130, 238)
rgba(238, 130, 238, 1)
hsl(200, 100%, 1%)
hsla(200, 100%, 1%, 0.5)
mediumSpringGreen
transparent
#ffccdd
violet
violet
#000305
#00030580
#00fa9a
#0000
#fcd
All the following values are equivalent.
linear-gradient(180deg, #0000 0%, red 6%, blue 50%, aqua 50%, gold 75%, pink 100%)
linear-gradient(#0000 0%, red 6%, blue 50%, aqua 50%, gold 75%, pink 100%)
linear-gradient(#0000, red 6%, blue 50%, aqua 50%, gold 75%, pink)
linear-gradient(#0000, red 6%, blue 50%, aqua 0, gold 75%, pink)
linear-gradient(#0000, red 6%, blue 50%, aqua 0, gold, pink)
linear-gradient()
.
As with linear gradients, hasmin supports reducing radial gradients. For example, the following values:
radial-gradient(farthest-corner ellipse at 50%,red,pink)
radial-gradient(ellipse at center,peru,blue)
radial-gradient(farthest-corner,lime,teal)
radial-gradient(circle 1in,peru,pink)
radial-gradient(circle farthest-corner,peru,pink)
radial-gradient(ellipse 1in 1in,peru,pink)
radial-gradient(#000 0%,#abc 50%,#fff 100%)
radial-gradient(red,pink)
radial-gradient(peru,blue)
radial-gradient(lime,teal)
radial-gradient(1in,peru,pink)
radial-gradient(circle,peru,pink)
radial-gradient(1in 1in,peru,pink)
radial-gradient(#000,#abc,#fff)
Examples of this are converting these:
perspective-origin: 0% 50%;
perspective-origin: 50% 0%;
background-position: left 0% top 0%;
background-position: right 0% center;
perspective-origin: 0;
perspective-origin: top;
background-position: 0 0;
background-position: 100%;
Whenever a <repeat-style> value is used, such as in the background-repeat
and background
properties, the following conversions can be applied:
Two-keyword syntax | Single keyword |
---|---|
no-repeat repeat |
repeat-y |
repeat no-repeat |
repeat-x |
no-repeat no-repeat |
no-repeat |
repeat repeat |
repeat |
space space |
space |
cubic-bezier(0.25, 0.1, 0.25, 1)
cubic-bezier(0, 0, 1, 1)
cubic-bezier(0.42, 0, 1, 1)
cubic-bezier(0, 0, 0.58, 1)
cubic-bezier(0.42, 0, 0.58, 1)
steps(1, start)
steps(1, end)
steps(2, end)
ease
linear
ease-in
ease-out
ease-in-out
step-start
step-end
steps(2)
end
is the default second value, so when it
isn’t present (because it is optional), that’s the value UAs should assume.
The An+B microsyntax is used by some functional pseudo classes, such as :nth-child(). Some examples of changes that may be done with the <an+b\> data type are:
:nth-child(even)
:nth-last-child(2n+1)
:nth-last-of-type(+1n+0)
:nth-of-type(-1n-0)
:nth-child(0n+0)
:nth-child(2n)
:nth-last-child(odd)
:nth-last-of-type(n)
:nth-of-type(-n)
:nth-child(0)
This data type is used in the box-shadow
property and defined in
7.3 of
CSS Backgrounds and Borders Level 3. In it, the 3rd and 4th <length> values
are optional and zero by default. Thus:
box-shadow: 3px 3px 0 3px red;
box-shadow: red 3px 3px 3px 0;
box-shadow: inset 2px 2px 0 0;
box-shadow: 1px 1px 0 inset;
box-shadow: 3px 3px 0 3px red;
box-shadow: red 3px 3px 3px;
box-shadow: inset 2px 2px;
box-shadow: 1px 1px inset;
Every transform function has associated a 4x4 matrix. Hasmin finds the shortests equivalent function able to represent a given matrix. For example, the following functions:
translate(0)
matrix(1,0,0,0,0,0)
matrix(1,0,0,1,35,0)
scale3d(0,1,1)
skew(0)
scaleY(1)
translate(35px)
scaleX(0)
A list of <transform-function\> can be multiplied (that is, their corresponding 4x4 matrices are multiplied), to look for a shorter equivalent representation, e.g.:
translate(10px) skew(45deg) scaleX(5)
skew(45deg) translate(100%) scaleX(5) scaleX(2)
matrix(5,0,1,1,10,0)
skew(45deg) translate(100%) scale(10)
em
) is used with a value other than 0, then there is
no way to know the computed value by only reading the CSS file, so the
optimization can’t be done. That’s why in the second case, translate is left
untouched.
Also, since matrix multiplication isn’t conmutative, skew(45deg) and scale(10) can’t be multiplied, since that would change the original transform.
brightness(0%)
contrast(100%)
grayscale(110%)
invert(111%)
opacity(50%)
sepia(.08)
brightness(0)
contrast(1)
grayscale(1.1)
invert(1.11)
opacity(.5)
sepia(8%)
mask-size: 1em auto;
background-size: auto 1em;
mask-size: 1em;
background-size: auto 1em;
auto
can only be removed when in the second place, that’s why in the example the background-size
declaration remains the same.
Something like:
border-radius: 1px 2px 1px 2px / 1px 1px 1px 1px;
border-radius: 1px 2px / 1px;
Some simple examples of the four functions are:
inset(1px 1px round 4px / 4px)
circle(closest-side at center)
ellipse(10px 10px at left bottom)
polygon(nonzero, 10px 10px)
inset(1px round 4px)
circle()
ellipse(10px 10px at 0 100%)
polygon(10px 10px)
For some more examples, check the test suite.
§14.1 of Selectors Level 4 introduced the >>
combinator, which just like ' '
(whitespace), represents the descendant combinator. Thus, a selector such as h1 >> .class
may be replaced for h1 .class
.
As per Selectors Level 4, Hasmin replaces:
-
:nth-of-type(1)
for:first-of-type
-
:nth-last-of-type(1)
for:last-of-type
-
:nth-child(1)
for:first-child
-
:nth-last-child(1)
for:first-child
Given the following:
.class {
line-height: inherit;
box-sizing: border-box;
border-collapse: separate;
}
.class {
line-height: unset;
box-sizing: unset;
border-collapse: initial;
}
-
The
line-height
property inherits, thereforeunset
equalsinherit
. -
For
box-sizing
, the valueborder-box
is its initial value. It could be replaced forinitial
, butbox-sizing
doesn’t inherit, sounset
is synonymous toinitial
for this property, and shorter. -
Lastly,
separate
is the initial value ofborder-collapse
, so it can just be replaced forinitial
which is shorter. Note that since this property inherits,unset
can’t be used because that would meaninherit
, notinitial
.
Hasmin has information about almost every property (save some of the latest experimental ones), i.e. if it inherits, what its initial value is, etc.
margin: 1px auto 3px auto;
padding: 1px 2px 1px;
border-color: red red;
border-width: thin thin thin thin;
border-style: none solid none solid;
margin: 1px auto 3px;
padding: 1px 2px;
border-color: red;
border-width: thin;
border-style: none solid;
Initial values are removed from shorthands, e.g.:
list-style: disc url(img/x.png) inside;
outline: thin none invert;
text-decoration: #eee solid none;
border-top: medium none currentColor;
list-style: url(img/x.png) inside;
outline: thin;
text-decoration: #eee;
border-top: none;
font-weight: normal;
font: italic small-caps bold 12px/4;
font-weight: 400;
font: italic small-caps 700 12px/4;
word-spacing: normal;
word-spacing: initial;
word-spacing: 0;
word-spacing: 0;
vertical-align: baseline;
vertical-align: initial;
vertical-align: unset;
vertical-align: 0;
vertical-align: 0;
vertical-align: 0;
If the third <length> value is 0
, it may be removed, e.g.:
text-shadow: 0 20px 0 #0f7d7d;
text-shadow: 0 20px #0f7d7d;
Hasmin understands about the different possibilities for this property, and is able to minify it. For example, these:
transform-origin: left top;
transform-origin: bottom center;
transform-origin: center center;
transform-origin: center center 1px;
transform-origin: 0 0;
transform-origin: bottom;
transform-origin: 50%;
transform-origin: 50% 50% 1px;
Hasmin can convert escaped characters into their unicode equivalents, e.g.:
content: "\7121";
content: '\0610F';
content: "\005473";
content: "無";
content: '意';
content: "味";
The @import
rule is followed by either a <url>, or <string> (see §2 of Cascading and Inheritance Level 4). That means that whenever a url()
value is found, the function notation may be removed in favor of the bare <string>, e.g.
@import url("landscape.css");
@import url(narrow.css);
@import "landscape.css";
@import "narrow.css";
Keyframe selectors are percentages, the keyword from
(equivalent to 0%
), or
the keyword to
(equivalent to 100%
). Therefore, if we have something like
this:
@keyframes slide-right {
from {
margin-left: 0px;
}
50% {
margin-left: 110px;
}
100% {
margin-left: 200px;
}
@keyframes slide-right {
0% {
margin-left: 0px;
}
50% {
margin-left: 110px;
}
to {
margin-left: 200px;
}
Double negations are simplified, e.g.:
@supports not (not (display: flex)){/* */}
@supports (display: flex){/* */}
The all
media type is the default one and may be omitted. Thus, the following rules are equivalent:
@media all {/*..*/}
@media {/*..*/}
@media all and (min-width: 500px) {/*..*/}
@media (min-width: 500px) {/*..*/}
@media (min-width:500px),all and (min-device-pixel-ratio:0){/*..*/}
@media (min-width:500px),(min-device-pixel-ratio:0){/*..*/}