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-css-modules

v0.1.2

Published

Rollup support for standard CSS modules

Downloads

239

Readme

rollup-plugin-css-modules

Rollup support for standard CSS modules

Standard CSS modules

CSS modules are standard feature of the web platform that allow you to import CSS stylesheets into JavaScript with an import statement, just like other JavaScript modules, and now also JSON modules.

CSS modules must be imported with an import attribute of type: 'css' to tell the browser to interpret the imported source as CSS instead of JavaScript.

CSS modules have a single default export that is a CSSStyleSheet instance containing the imported CSS. This stylesheet can then be applied to the document or shadow roots via the adoptedStyleSheets API, which is available on documents and shadow roots.

Example

styles.css:

.foo {
  color: red;
}

index.js:

import styles from './styles.css' with {type: 'css'};
document.adoptedStyleSheets.push(styles);

Usage

Install

npm i -D rollup-plugin-css-modules

Example Rollup cofig

rollup.config.js:

import {cssModules} from 'rollup-plugin-css-modules';

export default {
  input: 'index.js',
  plugins: [cssModules()],
  output: [{
    file: 'bundle.js',
    format: 'es'
  }]
};

The cssModules() plugin factory takes no options as of now. It may accepts options in the future.

How it works

rollup-plugin-css-modules transforms CSS files that are imported with a {type: 'css'} import attribute into JavaScript modules that export a CSSStyleSheet object created from the CSS source.

The example files above are transformed to:

styles.css:

const stylesheet = new CSSStyleSheet();
stylesheet.replaceSync(`.foo {\n  color: red;\n}\n`);
export default stylesheet;

index.js:

import styles from './styles.css';
document.adoptedStyleSheets.push(styles);

This is a transparent and spec compliant transformation. Like native CSS modules, there are no side-effects from loading the CSS, the CSS is parsed only once, and there's a single CSSStyleSheet instance shared among all importers.

After the transformation, Rollup can bundle the transformed CSS module into the JavaScript bundle(s) as usual.

Potential downsides

While the transform is simple and spec compliant, there are two (small) potential downsides to be aware of:

  1. The CSS file can't be dynamic. This is a natural consequence of bundling. With native CSS modules you could import a CSS file from a remote URL or from a file path who's contents could change. With bundling the file is fixed at build time.
  2. The exact same CSS file can't be shared across JS imports and HTML imports. This is also a consequence of bundling. With native CSS modules you can load the CSS file in many different ways: import into JavaScript, with a <link rel=stylesheet> HTML tag, or some other CSS loader. With bundling you will have to leave a copy of the CSS as a plain .css file in order to load other ways.
  3. The CSS content is parsed as both JavaScript and CSS. This gives the transform a small performance and memory overhead compared to native CSS modules. On projects that use a similar technique of embedding plain CSS strings into JavaScript and creating CSSStyleSheet objects, the overhead was measured to be small since parsing strings in JavaScript is very fast.
  4. Dynamic import isn't supported (yet).

When to use

  1. To use CSS modules in browsers that don't support them
  2. To use CSS modules in Chrome versions that support CSS modules but not import attributes
  3. To bundle CSS modules to reduce network requests

Brower support for CSS modules

As of December 2023, no browser supports both CSS modules and import attributes, so a transform is needed to use them.

Chrome and Chromium browsers

CSS modules are supported in Chrome since version 93, but V8 only supports the older assert import assertions syntax, and not the newer standard-track with import attributes specification.

Safari

Safari supports constructible stylesheets (new CSSStyleSheet()), adoptedStyleSheets, and import attributes, but not CSS modules yet.

Firefox

Firefox supports constructible stylesheets (new CSSStyleSheet()) and adoptedStyleSheets, but not import attributes.

Future work

Async parsing

It's often better to let the browser parse resources in parallel with other work if possible. We can adjust the transform to use CSSStyleSheet.prototype.replace() instead of replaceSync() so that the CSS parsing happens off the main thread.

This is possible with top level await, so that the transformed module would look like:

styles.css:

const stylesheet = new CSSStyleSheet();
await stylesheet.replace(`.foo {\n  color: red;\n}\n`);
export default stylesheet;

First, the performance impact should be measured. The top-level await will block other JavaScript execution in the same module graph, so it may not help that much. On the other hand, other main thread work can resume. This would be an opt-in feature.

Dynamic import support

Dynamic imports can be generically supported by transforming them into a fetch() call:

load-css.js:

const loadCSS = (url) => import(url);

Can be tranformed to: load-css.js:

const loadCSS = (url) => () => {
  // A cache of URL -> Promise<CSSSyleSheet>
  const cache = globalThis._$CSSModuleCache ??= new Map();
  const resolvedURL = new URL(url, import.meta.url).href;
  let stylesheet = cache.get(resolvedURL);
  if (stylesheet !== undefined) {
    return stylesheet;
  }
  stylesheet = new CSSStyleSheet();
  const promise = (async () => {
    const response = await fetch(resolvedURL);
    const text = await response.text();
    return stylesheet.replace(text);
  })();
  cache.set(resolvedURL, promise);
  return promise;
};

This is completely untested code

External CSS support

Specifiers marked as external in the Rollup config are correctly left unmodified, but by transforming the CSS module to use fetch() and top-level await, we can load external CSS files.

Bundling CSS into CSS

We can leave CSS external to the JavaScript bundle, in order to take enable the browser to directly parse the CSS, but still bundle all the CSS together while letting each import site receive just the CSSStyleSheet object it would have pre-transformation by using this hack to bundle multiple CSS sheets into one file.

PostCSS integration

Existing PostCSS Rollup plugins may rely on stylesheets being added to the global document via a <link> tag. We could add a PostCSS pass to this plugin so that developers could write CSS that's not supported by browsers natively, or run transforms like Tailwind.

We need to be careful that we don't encourage packages to publish non-standard CSS that requires the use of the transform, however. PostCSS transformation may belong in a separate plugin.

Minify CSS

CSS minification might be the most common CSS transform, so it could be built in.