npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

dedupe.css

v1.1.0

Published

A CSS from JS tool.

Readme

dedupe.css

Simply put this project's purpose is to allow you to author CSS with repeating declarations, and transform that to CSS where as much as possible declarations are not repeated, à la atomic CSS. For now it is entirely a JS to CSS solution, meaning you author your CSS in JS.

the cli

The CLI takes a single JS module input file, and then outputs a single CSS file, plus a generated JS module and JSON file that both have a map of keys to the generated class names in the CSS file.

an example

the entry file

In this contrived example we have four classes: .box-1, .box-2, .box-3, and .box-4. postcss-nesting is used to allow nesting.

// Please note that "css" only outputs a string. It is used to gain syntax highlighting, and is not strictly necessary.
import {css} from 'dedupe.css';

//  the export doesn't have to be "classes"
export const classes = css`
  .box-1 {
    margin-right: 1em;
    color: white;
    background-color: gray;

    @media (min-width: 700px) {
      margin-right: 2em;
    }
  }

  .box-2 {
    margin: 2em;
    margin-right: 1em;
    color: black;
    background-color: white;

    @media (min-width: 700px) {
      margin: 3em;
      margin-right: 2em;
    }
  }

  .box-3 {
    margin-top: 1em;
    margin-bottom: 1em;
    color: white;
    background-color: gray;

    @media (min-width: 700px) {
      margin-top: 2em;
      margin-bottom: 2em;
    }
  }

  .box-4 {
    margin-left: 1em;
    margin: 2em;
    margin-right: 1em;
    color: black;
    background-color: white;

    @media (min-width: 700px) {
      margin-left: 2em;
      margin: 3em;
      margin-right: 2em;
    }
  }
`;

// _start gets prepended to the output css. There is also _end which would get appended.
export const _start = css`
  * {
    margin: 0;
    font: inherit;
  }
`;

the structure of the output

This is the output from running dedup.css against the input above.

css

First any CSS outside an at-rule is output, then at-rules are output, sorted with postcss-sort-media-queries. Outside of any at-rule, and within each, the CSS that's generated is output in three parts. First any shorthand properties are output. Then any properties that are not shared by two or more classes are output, like margin-top: 1em and margin-bottom: 1em in the example. Last are all the properties that are not shorthands that are shared. Dedupe.css does this by putting everything into a sqlite database and then querying it to find which properties are shared and not.

* {
  margin: 0;
  font: inherit;
}
.a {
  margin: 2em;
}
.b {
  margin-top: 1em;
  margin-bottom: 1em;
}
.c {
  margin-right: 1em;
}
.d {
  background-color: gray;
  color: white;
}
.a {
  background-color: white;
  color: black;
}
@media (min-width: 700px) {
  .a {
    margin: 3em;
  }
  .b {
    margin-top: 2em;
    margin-bottom: 2em;
  }
  .c {
    margin-right: 2em;
  }
}

This also demonstrates that shorthands negate any constituent properties that they succeed, but not ones they precede. For instance margin-left is gone from the output, when it occurs in the input.

js

The output JS has the same exports as the input, but instead of CSS tagged template literals it has objects with keys corresponding to the original classes and values corresponding to the generated ones.

export const classes = {
  'box-2': 'a c',
  'box-4': 'a c',
  'box-3': 'b d',
  'box-1': 'c d',
};

json

The JSON is just like the JS. It's provided for situations where you aren't using JS. For instance it could be used with a templating language.

{
  "classes": {
    "box-2": "a c",
    "box-4": "a c",
    "box-3": "b d",
    "box-1": "c d"
  }
}

--dev

When you use --dev the JS output is different. Instead of a plain object it outputs a proxy that wraps an object with a bunch of getters. This is done so that you can see which keys are not used in dev tools using the coverage tool. Also the proxy has a get trap that will throw an error when accessing missing keys.

export const classes = new Proxy(
  {
    get ['box-2']() {
      return 'a c';
    },
    get ['box-4']() {
      return 'a c';
    },
    get ['box-3']() {
      return 'b d';
    },
    get ['box-1']() {
      return 'c d';
    },
  },
  {
    get(target, prop) {
      if ({}.hasOwnProperty.call(target, prop)) {
        return 'classes:' + prop + ' ' + Reflect.get(target, prop);
      }

      throw Error('classes:' + prop + ' is undefined');
    },
  }
);

additional selectors

You can use pseudo-elements, pseudo-classes, and other modifiers, so long as they target an element with a class, and the selector itself starts with that class. No complex selectors are allowed essentially. This restriction exists because otherwise dedupe.css couldn't guarantee that the styles would actually be applied as intended, since it changes the order of declarations. Please note that pseudo-classes and other modifiers also can't always be optimized like classes alone can, though pseudo-elements always can.

.box-1 {
  /* ✅ allowed */
  &:before {
    content: '';
  }

  /* ✅ allowed */
  &.red {
    color: red;
  }

  /* ✅ allowed */
  &:not(a) {
    color: black;
  }

  /* ✅ allowed */
  &:nth-child(2) {
    font-weight: normal;
  }

  /* ❌ not allowed */
  & + & {
    margin-top: 1em;
  }

  /* ❌ not allowed */
  & span {
    color: gray;
  }

  /* ❌ not allowed */
  & span:nth-child(1) {
    font-weight: bold;
  }
}

mixins

A neat trick is that you can have mixins that are made possible by the input being run through postcss-nesting.

const boldMixin = css`
  & {
    font-weight: bold;
  }
`;

const classes = css`
  .heading {
    ${boldMixin}
  }
`;

to do

  • programmatic api
  • support CSS input file
  • allow stdin