mrkeyoor.com_
Thu 06 Aug 07:40 UTC
npmWeb Frontendupdated 06 Aug 2026

clean-css

clean-css is a CSS minifier written in plain JavaScript. You construct it with an options hash, call minify() with a CSS string or a list of file paths, and get back an object holding the optimized styles plus stats, warnings, and errors. Optimizations are grouped into three levels: level 0 does nothing beyond reading and inlining, level 1 (the default) rewrites single properties such as shortening colors and dropping units from zeros, and level 2 works across rules, merging duplicates, folding longhands into shorthands, and optionally restructuring rulesets so they take fewer bytes. It also inlines local @import rules, rebases relative URLs, and produces source maps. It is the minifier behind grunt-contrib-cssmin, gulp-clean-css, and a large slice of older webpack setups, which is where most of those weekly downloads come from.

Verdict

clean-css still minifies ordinary CSS correctly and it is deeply embedded in legacy build tooling, which is the only good reason left to pick it. For anything new, use lightningcss or whatever your bundler already includes; a CSS parser that has not shipped since 2023 is a bad bet as the language keeps growing.

API stability5/5The 5.x API has not changed since 5.3.3 in November 2023 and the 4.x to 5.x jump only altered defaults such as rebase and IE compatibility. Stability here is partly a side effect of the project having stopped.
Docs4/5Everything lives in one long README, but it is genuinely thorough: every constructor option is listed with its default, all three optimization levels are enumerated, and the FAQ answers the real questions about source maps, batch mode, and preserving fragments. There is no separate docs site or searchable API reference.
Maintenance1/5The README declares maintenance mode and links a discussion saying so. Last release November 2023, last push October 2024, 39 open issues (46 counting PRs). PRs are accepted but releases are described by the author as occasional bugfixes only.
Ecosystem4/5Around 22M weekly downloads through grunt-contrib-cssmin, gulp-clean-css, optimize-css-assets-webpack-plugin, and similar wrappers. That is inherited traffic from older toolchains rather than new adoption, and the plugin API added in v5 has few third-party plugins.

Use it if

  • You already run a Grunt, Gulp, or older webpack build whose CSS plugin wraps clean-css, and swapping minifiers would mean rewriting the asset pipeline for a few kilobytes of difference
  • You want cross-rule optimizations that most minifiers skip: level 2 merges non-adjacent rules with the same selector, removes properties that are redefined later, and can restructure rules so declarations move to shorter homes
  • You need @import inlining and URL rebasing done by the minifier itself, because your CSS is assembled from files on disk rather than by a bundler that already resolved them
  • You are minifying CSS at runtime inside Node with no build step, and want a single dependency-light package with a synchronous call rather than a bundler or a native binary
Skip it if

Setup reality

npm install clean-css pulls one dependency (source-map 0.6) and no native code, and the API is CommonJS with no ESM export map, so ESM consumers get the default-import interop shuffle. The parts that catch people out are behavioral. minify() is synchronous by default and returns the result object directly, but it becomes asynchronous the moment you pass a callback or set returnPromise: true, and remote @import rules are only inlined when you pass a callback because fetching is async; without one they are left untouched. If you pass a string it is treated as CSS source, and if you pass an array of strings they are treated as file paths, which is a surprising overload the first time you hit it. Since 5.0, rebase defaults to false, so relative url() paths are left alone unless you set rebaseTo. Errors do not throw: invalid CSS is dropped and reported in output.warnings and output.errors, which nobody reads until a rule goes missing in production.

Patterns

Minify a CSS stringminify-a-string

const CleanCSS = require('clean-css');

const output = new CleanCSS().minify('a { color: blue; }\ndiv { margin: 5px }');

console.log(output.styles); // a{color:#00f}div{margin:5px}
console.log(output.stats.efficiency); // fraction of bytes saved

Without a callback, minify() is synchronous and returns the result object directly. Nothing throws on bad CSS, so check output.errors and output.warnings yourself.

Turn on cross-rule optimizationsoptimization-level-2

const CleanCSS = require('clean-css');

const output = new CleanCSS({ level: 2 }).minify(source);

Level 2 merges duplicate and non-adjacent rules and folds longhands into shorthands. Level 1 still runs underneath unless you explicitly disable it. restructureRules stays off even at level 2; enable it separately if you want rules moved around.

Mix level 1 and level 2 optionsfine-grained-levels

const CleanCSS = require('clean-css');

const output = new CleanCSS({
  level: {
    1: { all: true, normalizeUrls: false },
    2: { all: false, removeDuplicateRules: true, restructureRules: true }
  }
}).minify(source);

The all flag sets every option at that level, so list it first and then re-enable the ones you want. This is the escape hatch when one specific optimization breaks your stylesheet.

Minify files without reading them firstminify-files-from-paths

const CleanCSS = require('clean-css');

// array of strings means "these are paths"
const output = new CleanCSS().minify(['src/base.css', 'src/theme.css']);
console.log(output.styles); // both files concatenated and optimized

A bare string is treated as CSS source; the same string inside an array is treated as a file path. Passing paths is also what lets clean-css resolve local @import rules and rebase URLs correctly.

Optimize many files without concatenating thembatch-multiple-files

const CleanCSS = require('clean-css');

const output = new CleanCSS({ batch: true }).minify(['src/a.css', 'src/b.css']);

console.log(output['src/a.css'].styles);
console.log(output['src/b.css'].stats.minifiedSize);

Added in 5.0. With batch: true the return value is keyed by input path instead of being one merged result, and each entry carries its own styles, stats, errors, and warnings.

Get a promise or use a callbackasync-and-promises

const CleanCSS = require('clean-css');

new CleanCSS({ returnPromise: true })
  .minify(source)
  .then((output) => console.log(output.styles))
  .catch((err) => console.error(err));

// or the callback form
new CleanCSS().minify(source, (error, output) => {
  if (error) return console.error(error);
  console.log(output.styles);
});

returnPromise must be asked for explicitly; the default is synchronous. Mixing the two (returnPromise plus a callback) is not supported, pick one.

Inline remote @import rulesinline-remote-imports

const CleanCSS = require('clean-css');

const source = '@import url(https://example.com/base.css);';

new CleanCSS({ inline: ['local', 'remote'] }).minify(source, (error, output) => {
  console.log(output.styles);
  console.log(output.inlinedStylesheets);
});

Remote inlining only happens in the callback or promise form, because the fetch is async. Call it synchronously and the @import is silently left in the output. Default is inline: 'local'.

Keep a block of CSS untouchedpreserve-css-fragments

/* clean-css ignore:start */
.critical-hack {
  color: transparent;
}
/* clean-css ignore:end */

/*! this bang comment survives minification */

The ignore markers are a source-level feature (4.2+), not an option. Comments starting with /*! are kept by default via the level 1 specialComments option; set it to 0 to strip licence headers too.

Produce a source mapgenerate-source-maps

const CleanCSS = require('clean-css');

new CleanCSS({ sourceMap: true, rebaseTo: 'dist' })
  .minify(['src/app.css'], (error, output) => {
    // output.sourceMap is a SourceMapGenerator instance
    require('fs').writeFileSync('dist/app.min.css.map', output.sourceMap.toString());
  });

sourceMap must be a boolean; an incoming map is passed as the second argument to minify() instead. Set rebaseTo to the output directory or the paths inside the map point at the wrong place.

Emit readable CSS instead of one lineformat-readable-output

const CleanCSS = require('clean-css');

const pretty = new CleanCSS({ format: 'beautify' }).minify(source);
const breaks = new CleanCSS({ format: 'keep-breaks' }).minify(source);

const custom = new CleanCSS({
  format: { breaks: { afterRuleEnds: 2 }, indentBy: 2, indentWith: 'tab' }
}).minify(source);

Since 5.0 break options accept numbers, so afterRuleEnds: 2 means two newlines. Useful when you want optimization without an unreadable single-line diff in a committed file.

Loosen or tighten browser compatibilitycompatibility-mode

const CleanCSS = require('clean-css');

new CleanCSS({ compatibility: 'ie9' }).minify(source);

// or one flag at a time
new CleanCSS({ compatibility: 'ie9,-properties.merging' }).minify(source);

new CleanCSS({
  compatibility: { customUnits: { rpx: true } }
}).minify(source);

The default is still ie10+, which is why some modern-looking output choices seem conservative. Non-standard units such as rpx are dropped as invalid unless you register them under customUnits.

Write a plugin to rewrite propertiescustom-plugin

const CleanCSS = require('clean-css');

const dropRepeatedRepeat = {
  level1: {
    property: function (_rule, property) {
      if (property.name === 'background-repeat' && property.value.length === 2
        && property.value[0][1] === property.value[1][1]) {
        property.value.pop();
        property.dirty = true;
      }
    }
  }
};

new CleanCSS({ plugins: [dropRepeatedRepeat] }).minify(source);

Plugins replaced the old transform callback in 5.0. You mutate the internal property representation and must set dirty = true or the change is not serialized. The AST shape is barely documented; read lib/optimizer/level-1/property-optimizers for working examples.

Alternatives

PackageRegistryPick it when
lightningcssnpmYou want a maintained minifier that parses current CSS syntax and runs an order of magnitude faster because it is Rust
cssnanonpmYour build is already PostCSS-based and you want the minifier to share one AST pass with autoprefixer and friends
esbuildnpmYou are bundling anyway and would rather have CSS minification come free with the bundler than as a separate step
cssonpmYou want structural CSS optimization on a csstree parser with a smaller, more inspectable codebase