A simple, thorough and fast unused-CSS cleaner (MIT Licensed)
DropCSS is an unused CSS cleaner; it takes your HTML and CSS as input and returns only the used CSS as output. The core is simply some minimal glue between these awesome low-level tools:
The entire logic for DropCSS is this ~60 line file.
It is recommended to also run your CSS through an optimizer like clean-css to group selectors, merge and remove redundant rules, purge unused keyframes, etc. Whether this is done before or after DropCSS is up to you, but since clean-css
also minifies, it probably makes sense to run DropCSS first to avoid bouncing [and re-parsing] the output back and forth (optimize & minify -> drop) vs (optimize -> drop -> minify), though this will likely depend on your actual input; profiling is your friend.
A bit more on this project's backstory & discussions in /r/javascript and on Hacker News.
npm install -D dropcss
const dropcss = require('dropcss');
let html = `
<html>
<head></head>
<body>
<p>Hello World!</p>
</body>
</html>
`;
let css = `
.card {
padding: 8px;
}
p:hover a:first-child {
color: red;
}
`;
const whitelist = /#foo|\.bar/;
let dropped = new Set();
// returns { css }
let cleaned = dropcss({
html,
css,
keepText: false,
shouldDrop: (sel) => {
if (whitelist.test(sel))
return false;
else {
dropped.add(sel);
return true;
}
},
});
console.log(cleaned.css);
console.log(dropped);
shouldDrop
is called for every CSS selector that could not be matched in thehtml
. Returnfalse
to retain it ortrue
to drop it. Additionally, this hook can be used to log all removed selectors.keepText
- By default, DropCSS will remove all text nodes from the HTML before processing further since very few CSS selectors can actually target text. Not having to process text nodes is a significant performance boost. However, a few uncommon pseudo-classes like:blank
and:empty
do assert on text nodes. If combined as e.g.:not(:empty)
, this could result wrongful removal, or wrongful retention of selectors. SettingkeepText
totrue
will leave all text nodes in place to allow for this to work properly at the expense of performance.
DropCSS stands on the shoulders of giants.
- Removal of practically all conceivable selectors is achieved thanks to the exhaustive selector support of
css-select
: https://github.com/fb55/css-select#supported-selectors. - CSSTree enables media queries to be transparently processed and removed like all other blocks without special handling.
- Retention of all transient pseudo-class and pseudo-element selectors which cannot be deterministically checked from the parsed HTML.
test.html
- 18.8 KB minified
- 502 dom nodes via
document.querySelectorAll("*").length
styles.min.css
- 27.67 KB combined, optimized and minified via clean-css
- contents: Bootstrap's reboot.css, an in-house flexbox grid, global layout, navbars, colors & page-specific styles. (the grid accounts for ~85% of this starting weight, lots of media queries & repetition)
lib size w/deps | output size | reduction | time elapsed | unused bytes (test.html coverage) | |
---|---|---|---|---|---|
DropCSS |
2.16 MB 251 Files, 51 Folders |
6.60 KB | 76.15% | 138ms | 575 / 8.5% |
UnCSS |
13.7 MB 2,831 Files, 301 Folders |
6.72 KB | 75.71% | 429ms | 638 / 9.3% |
Purgecss |
2.53 MB 513 Files, 110 Folders |
8.01 KB | 71.05% | 78ms | 1,806 / 22.0% |
PurifyCSS |
3.45 MB 791 Files, 207 Folders |
15.4 KB | 44.34% | 186ms | 9,440 / 59.6% |
Notes
- About 400 "unused bytes" are due to an explicit/shared whitelist, not an inability of the tools to detect/remove that CSS.
- About 175 "unused bytes" are due to vendor-prefixed (-moz, -ms) properties & selectors that are inactive in Chrome, which is used for testing coverage.
- Purgecss does not support attribute or complex selectors: Issue #110.
- Not tested against malformed HTML (the underlying Fast HTML Parser claims to support common cases but not all)
- Not tested against malformed CSS (the underlying CSSTree parser claims to be "Tolerant to errors by design")
- There is no processing or execution of
<script>
tags; your HTML must be fully formed (or SSR'd). You should generate and append any additional HTML that you'd want to be considered by DropCSS. If you need JS execution, consider using the larger, slower but still good output,UnCSS
. Alternatively, Puppeteer can now output coverage reports, and there might be tools that utilize this coverage data to clean your CSS, too. DropCSS aims to be minimal, simple and effective.