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 🙏

© 2024 – Pkg Stats / Ryan Hefner

rollup-plugin-postcss-treeshakeable

v1.0.1

Published

Enables treeshaking of modular CSS produced by PostCSS

Downloads

36

Readme

rollup-plugin-postcss-treeshakeable

Enables treeshaking of modular CSS produced by postcss

Installation

Install the package

npm install rollup-plugin-postcss-treeshakeable --save-dev

Add it to your rollup.config.js

import postcss from "rollup-plugin-postcss";
import postcssTreeshakeable from "rollup-plugin-postcss-treeshakeable";

export default {
  // ...
  plugins: [
    postcss({
      modules: true,
      plugins: []
    }),
    postcssTreeshakeable()
  ]
};

The postcssTreeshakeable must appear after the postcss plugin.

Disclaimer

This package is experimental. A first test showed that the technique works, but it has not been tested on any big project yet.

Gist

This plugins transforms this code (which gets produced by postcss when importing a css module):

var css = ".foo { background: red; }";
export default { foo: "foo__classname_1 " };
import styleInject from "<some-folder>/style-inject/dist/style-inject.es.js";
styleInject(css);

into this code

import styleInject from "<some-folder>/style-inject/dist/style-inject.es.js";
var cssMap = { foo: "foo__classname_1 " };
var hasRun;
var styles = function styles() {
  if (!hasRun) {
    hasRun = true;
    styleInject(".foo { background: red; }");
  }
  return cssMap;
};

export default styles;

🎉 This allows consumers to get rid of CSS of unused modules when treeshaking.

Long explanation

Situation

You are writing a cool library which lets people render a Butler on screen. The Butler is provided as a React component and uses CSS modules to have some styles applied. The library also comes with a bunch of other components.

Whoever uses your library should only bundle the code and styles of the components they are actually using. The unused component code and styles should be excluded from the bundle.

A technique called Treeshaking lets us do this. However, it was so far only able to omit the code of unused components. The styles of unused components would have still been lingering around in your bundle, even though they were never used!

This plugin enables library authors which use PostCSS and bundle with Rollup to create components whose styles can be omitted through treeshaking. This creates smaller bundles and thus makes the application the library consumers are building slimmer.

What you've been doing

So far you've probably been importing CSS modules like this:

import styles from "./Butler.mod.css";

const Butler = props => <div className={styles.alfred}>I am Butler</div>;

Given that Butler.mod.css has this content:

.alfred {
  background: red;
}

The Butler.mod.css gets turned into this by postcss:

var css = ".Butler-mod_alfred__3zrhW {\n  background: red;\n}\n";
export default { alfred: "Butler-mod_alfred__3zrhW" };
import styleInject from "<some-folder>/style-inject/dist/style-inject.es.js";
styleInject(css);

When you bundle this with with rollup, you end up with the following in your generated bundle:

function styleInject(css, ref) {
  /* redacted */
}

var css = ".Butler-mod_alfred__3zrhW {\n  background: red;\n}\n";
var styles = { alfred: "Butler-mod_alfred__3zrhW" };
styleInject(css);

var Butler = function Butler(props) {
  return React.createElement(
    "div",
    {
      className: styles.alfred
    },
    "I am Butler"
  );
};

export { Butler /* other components */ };

Treeshaking components

If the consumer of your library doesn't import Butler, but only imports the other components, then we don't need to keep Butler around.

Treeshaking does exactly this and marks the Butler export as unused. Once consumers of the library you're building run dead code elimination (for example by running Uglify), Butler won't end up in their application bundle anymore.

This is pretty great, however the styles of Butler would currently still end up in the produced bundle.

Treeshaking styles

This plugin transforms the contents of Butler.mod.css into a representation which can be removed through treeshaking.

It transforms the contents of modular css files generated by postcss from

var css = ".Butler-mod_alfred__3zrhW {\n  background: red;\n}\n";
var styles = { alfred: "Butler-mod_alfred__3zrhW" };
styleInject(css);

to this

var cssMap = { alfred: "Butler-mod_alfred__3zrhW" };
var hasRun;
var styles = function styles() {
  if (!hasRun) {
    hasRun = true;
    styleInject(".Butler-mod_alfred__3zrhW {\n  background: red;\n}\n");
  }
  return cssMap;
};

What you'll be doing now

We are not exporting an object of classnames anymore, but we're rather exporting a function returning that object. The function will add inject the styles the first time it gets called.

The consumers of the CSS modules have to be adapted, as we're now exporting a function instead of an object:

import styles from "./Butler.mod.css";

-const Butler = props => <div className={styles.alfred}>I am Butler</div>;
+const Butler = props => <div className={styles().alfred}>I am Butler</div>;

After applying this plugin and after rewriting the usage of imported CSS (by turning styles into styles()), your library consumers will benefit from treeshaking it!

Why it works

Notice that we're not calling styleInject directly, but we're rather waiting until the styles are actually being used. At that point, we add the styles to the document.

When treeshaking now removes unused components, it can also remove their unused CSS, as the styles are not referred to anymore after the components have been omitted.

And thus, no more CSS of unused components in the bundles of your library users.

Caveats

All styles of one modular CSS file get injected the first time any of its styles are used. So, if you want to apply the styles of some files right away, importing them is no longer good enough.

You have to call the function as well:

import someStyles from "./somewhere.css";

// This call injects them into the document and thus applies them.
// It further returns the classnames contained in `somewhere.css`
someStyles();