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 🙏

© 2026 – Pkg Stats / Ryan Hefner

rolldown-plugin-css

v0.1.3

Published

Rolldown CSS plugin: Sass/Scss/Less compilation, CSS Modules & compatibility via Lightning CSS, auto-injecting CSS imports into JS.

Readme

rolldown-plugin-css

A CSS plugin for Rolldown that handles the full CSS pipeline in a single plugin — preprocessing, transformation, asset output, and automatic import injection.

npm version license ​​Rolldown兼容性 ​​Vite兼容性


Features

  • 🎨 Sass / SCSS / Less — auto-detects installed preprocessors, no manual configuration needed
  • LightningCSS — syntax lowering, vendor prefixing, minification, and all other LightningCSS transforms
  • 📦 CSS Modules — scoped class names for *.module.* files, exported as a JS object
  • 🔗 Auto import injection — automatically prepends import './xxx.css' (or require) to each JS chunk that contains styles
  • 🗂 Per-chunk CSS output — each JS chunk gets its own CSS file, placed in a configurable subdirectory
  • 🔍 Zero config — works out of the box with sensible defaults

Installation

# Required
npm add -D rolldown-plugin-css lightningcss

# Sass support (pick one, sass-embedded is faster)
npm add -D sass-embedded
# or
npm add -D sass

# Less support
npm add -D less

lightningcss is a required peer dependency. Preprocessors (sass-embedded, sass, less) are optional — only install what your project uses.


Usage

Rolldown

// rolldown.config.ts
import { defineConfig } from "rolldown";
import { cssRolldown } from "rolldown-plugin-css";

export default defineConfig({
  input: {
    index: "src/index.ts",
    components: "src/components/index.ts",
  },
  output: {
    dir: "dist",
    format: "esm",
  },
  plugins: [cssRolldown()],
});

Vite

// vite.config.ts
import { defineConfig } from "vite";
import { cssVite } from "rolldown-plugin-css";

export default defineConfig({
  plugins: [cssVite()],
});

Output structure

dist/
  css/
    components.css              ← styles owned by the components entry chunk
    components.Dzqt_Fdc.css    ← styles owned by a shared chunk
  index.esm.js
  components.esm.js
  js/
    components.Dzqt_Fdc.js     ← automatically has `import '../css/components.Dzqt_Fdc.css'` prepended

Options

The plugin options extend LightningCSS’s TransformOptions (excluding filename, code, and sourceMap which are managed internally), with two additional fields:

export interface CSSPluginOptions<C extends CustomAtRules> extends Omit<
  TransformOptions<C>,
  "filename" | "code" | "sourceMap"
> {
  include?: number; // default: Features.Nesting | Features.CustomMediaQueries
  cssDir?: string; // default: 'css'
}

This means every option supported by LightningCSS transform() is available directly — targets, minify, cssModules, drafts, pseudoClasses, unusedSymbols, and more.

targets

Type: Targets
Default: undefined

Browser targets for syntax lowering and vendor prefixing. Use browserslistToTargets from lightningcss to convert a Browserslist query.

import { browserslistToTargets } from "lightningcss";
import browserslist from "browserslist";
import { cssRolldown } from "rolldown-plugin-css";

cssRolldown({
  targets: browserslistToTargets(browserslist(">= 0.5%, not dead")),
});

include

Type: number (LightningCSS Features bitmask)
Default: Features.Nesting | Features.CustomMediaQueries

Controls which CSS draft features LightningCSS should transform/lower. Features is re-exported from this plugin for convenience.

import { cssRolldown, Features } from "rolldown-plugin-css";

cssRolldown({
  // Lower CSS Nesting and Custom Media Queries (default)
  include: Features.Nesting | Features.CustomMediaQueries,
});

See the LightningCSS Features documentation for all available flags.


minify

Type: boolean
Default: false

Minify the CSS output using LightningCSS.

import { cssRolldown } from "rolldown-plugin-css";

cssRolldown({
  minify: process.env.NODE_ENV === "production",
});

cssModules

Type: CSSModulesConfig | boolean
Default: undefined

LightningCSS CSS Modules configuration. When set, applies to all CSS Module files (*.module.*). The plugin detects CSS Module files by filename pattern — you don’t need to enable this manually for the detection to work, but you can use this option to customize the generated class name pattern and other CSS Modules behavior.

import { cssRolldown } from "rolldown-plugin-css";

cssRolldown({
  cssModules: {
    pattern: "[hash]_[local]", // default scoped class name pattern
  },
});

See the LightningCSS CSS Modules documentation for all available options.


cssDir

Type: string
Default: 'css'

The subdirectory (relative to output.dir) where CSS asset files are written. The injected import path is automatically computed relative to each JS chunk’s location.

import { cssRolldown } from "rolldown-plugin-css";

cssRolldown({ cssDir: "css" }); // → dist/css/components.css  (default)
cssRolldown({ cssDir: "assets/styles" }); // → dist/assets/styles/components.css
cssRolldown({ cssDir: "" }); // → dist/components.css

CSS Modules

Any file matching *.module.* is treated as a CSS Module. The plugin extracts scoped class names and exports them as a plain JS object.

/* Button.module.scss */
.button {
  background: royalblue;
  color: white;

  &:hover {
    background: darkblue; /* CSS Nesting — lowered by LightningCSS */
  }
}
// Button.tsx
import styles from "./Button.module.scss";

export function Button({ label }: { label: string }) {
  return <button className={styles.button}>{label}</button>;
}

The compiled output exports a class name map:

// compiled output (simplified)
const classes = { button: "a1b2c_button" };
export default classes;

The corresponding CSS (.a1b2c_button { ... }) is extracted and written to the chunk’s CSS asset file.


Advanced usage

Full LightningCSS configuration

Because the plugin options extend TransformOptions directly, you have access to all LightningCSS features:

import { cssRolldown, Features } from "rolldown-plugin-css";
import { browserslistToTargets } from "lightningcss";
import browserslist from "browserslist";

cssRolldown({
  // Browser targets
  targets: browserslistToTargets(browserslist(">= 0.5%, not dead")),

  // Features to lower
  include:
    Features.Nesting |
    Features.CustomMediaQueries |
    Features.MediaRangeSyntax |
    Features.OklabColors,

  // Minify in production
  minify: process.env.NODE_ENV === "production",

  // Custom CSS Modules class name pattern
  cssModules: {
    pattern: "[name]__[local]--[hash]",
  },

  // CSS output directory
  cssDir: "assets",
});

Multiple outputs (ESM + CJS)

The plugin automatically reads output.format from Rolldown’s output options and injects import or require accordingly. No extra configuration needed.

import { cssRolldown } from "rolldown-plugin-css";

export default defineConfig({
  input: "src/index.ts",
  output: [
    { dir: "dist/esm", format: "esm" }, // → import './css/index.css'
    { dir: "dist/cjs", format: "cjs" }, // → require('./css/index.css')
  ],
  plugins: [cssRolldown()],
});

Disable CSS injection (library / SSR)

If you want CSS files to be emitted without injecting import statements into JS (e.g. for a component library where consumers control style loading), you can fork the plugin or disable injection via a future option. For now, the recommended approach is to use the plugin as-is and document that consumers should import the CSS separately.


How it works

Preprocessor auto-detection

The plugin detects which preprocessor to use based on file extension:

  • .scss / .sass → tries sass-embedded first (native binary, ~10× faster), falls back to sass
  • .less → uses less
  • .css → skips preprocessing, goes directly to LightningCSS

The loaded module is cached at the module level, so the dynamic import() only runs once per build process.

transform hook

Rolldown only understands JavaScript modules. The transform hook converts each CSS file into a JS module that Rolldown can include in the module graph:

  • Preprocessor (Sass/Less): compiles to plain CSS, captures the source map
  • LightningCSS: transforms the CSS (nesting, vendor prefix, syntax lowering, etc.), with the preprocessor source map passed as inputSourceMap so the final source map traces back to the original .scss/.less file
  • CSS Module files (*.module.*): returns a JS module exporting the scoped class name map — export default { button: 'a1b2c_button' }
  • Plain CSS files: returns an empty JS comment — /* css-plugin: path/to/file.css */ — as a placeholder to keep the module in the graph

Both return moduleSideEffects: true to prevent tree-shaking from removing the module before generateBundle can see it.

generateBundle hook

Once all chunks are sealed, the plugin iterates over every chunk and:

  1. Finds CSS module IDs owned by this chunk via Object.keys(chunk.modules) — this includes placeholder modules that chunk.moduleIds (the tree-shaken list) might omit
  2. Concatenates the CSS strings in import order
  3. Emits a CSS asset file via this.emitFile
  4. Prepends an import './xxx.css' (or require) statement to chunk.code, using a path relative to the JS chunk’s output location

The inject step happens in the same loop iteration where cssFileName is already known — no separate plugin, no naming convention guesswork, no shared state.


Peer dependencies

| Package | Required | Notes | | --------------- | -------- | --------------------------------- | | rolldown | ✅ | ^1.0.0-rc.8 or later | | lightningcss | ✅ | Any recent version | | sass-embedded | optional | For .scss/.sass (recommended) | | sass | optional | For .scss/.sass (fallback) | | less | optional | For .less |


License

MIT