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

vite-css-modules

v1.12.0

Published

Vite plugin for correct CSS Modules behavior

Readme

vite-css-modules

Vite plugin to fix broken CSS Modules handling.

→ Play with a demo on StackBlitz

Note: We're working to integrate this fix directly into Vite (PR #16018). Until then, use this plugin to benefit from these improvements now.

Why use this plugin?

Have you encountered any of these Vite CSS Module bugs? They're happening because Vite's CSS Modules implementation delegates everything to postcss-modules, creating a black box that Vite can't see into.

This plugin fixes these issues by properly integrating CSS Modules into Vite's build pipeline.

The bugs this fixes

When you use composes to import classes from another CSS file, Vite's PostCSS plugins never process the imported file. This means your PostCSS transformations, auto-prefixing, or custom plugins are silently skipped for dependencies.

What happens in Vite:

  • style.module.css gets processed by PostCSS ✓
  • utils.css does NOT get processed by PostCSS ✗
  • Your PostCSS plugin never sees utils.css because postcss-modules bundles it internally

With this plugin:

  • Both files go through your PostCSS pipeline correctly

Vite issue #10079, #10340 | Test case

When multiple CSS Modules compose from the same utility file, the utility's styles get bundled multiple times. This increases bundle size and can cause style conflicts.

Example:

/* utils.css */
.button { padding: 10px; }

/* header.module.css */
.title { composes: button from './utils.css'; }

/* footer.module.css */
.link { composes: button from './utils.css'; }

What happens in Vite:

/* Final bundle contains .button styles TWICE */
.button { padding: 10px; }  /* from header.module.css */
.button { padding: 10px; }  /* from footer.module.css - duplicated! */

With this plugin:

/* Final bundle contains .button styles ONCE */
.button { padding: 10px; }  /* deduplicated */

Vite issue #7504, #15683 | Test case

If you typo a class name in composes, Vite doesn't error. Instead, it outputs undefined in your class names, breaking your UI with no warning.

Example:

.button {
  composes: nonexistant from './utils.css';  /* Typo! */
}

What happens in Vite:

import styles from './style.module.css'

console.log(styles.button) // "_button_abc123 undefined" - no error!

With this plugin:

Error: Cannot find class 'nonexistent' in './utils.css'

Vite issue #16075 | Test case

Trying to compose from SCSS/Sass files causes syntax errors because postcss-modules tries to parse SCSS as plain CSS.

Example:

/* base.module.scss */
.container { display: flex; }
/* style.module.css */
.wrapper { composes: container from './base.module.scss'; }

What happens in Vite:

CssSyntaxError: Unexpected '/'

With this plugin:

  • Works correctly because each file goes through its proper preprocessor first

Vite issue #10340 | Test case

Changing a CSS Module file causes a full page reload instead of a hot update, losing component state.

What happens in Vite:

  • Full page reload on CSS Module changes

With this plugin:

  • CSS Module changes update instantly without losing component state

Vite issue #16074 | Test case

Using JavaScript reserved keywords as class names (like .import, .export) generates invalid JavaScript code.

Example:

.import { color: red; }
.export { color: blue; }

What happens in Vite:

// Tries to generate invalid JavaScript:
export const import = "...";  // Syntax error "import" is reserved!
export const export = "...";  // Syntax error "export" is reserved!

With this plugin:

  • Properly handles reserved keywords in class names

Vite issue #14050 | Test case

Install

npm install -D vite-css-modules

Setup

In your Vite config file, add the patchCssModules() plugin to patch Vite's CSS Modules behavior:

// vite.config.js
import { patchCssModules } from 'vite-css-modules'

export default {
    plugins: [
        patchCssModules() // ← This is all you need to add!

        // Other plugins...
    ],
    css: {
        // Your existing CSS Modules configuration
        modules: {
            // ...
        },
        // Or if using LightningCSS
        lightningcss: {
            cssModules: {
                // ...
            }
        }
    },
    build: {
        // Recommended minimum target (See FAQ for more details)
        target: 'es2022'
    }
}

This patches your Vite to handle CSS Modules in a more predictable way.

Configuration

Configuring the CSS Modules behavior remains the same as before.

Read the Vite docs to learn more.

Strongly typed CSS Modules (Optional)

As a bonus feature, this plugin can generate type definitions (.d.ts files) for CSS Modules. For example, if style.module.css is imported, it will create a style.module.css.d.ts file next to it with the type definitions for the exported class names:

patchCssModules({
    generateSourceTypes: true
})

API

patchCssModules(options)

exportMode

  • Type: 'both' | 'named' | 'default'
  • Default: 'both'

Specifies how class names are exported from the CSS Module:

  • both: Exports class names as both named and default exports.
  • named: Exports class names as named exports only.
  • default: Exports class names as a default export only (an object where keys are class names).

generateSourceTypes

  • Type: boolean
  • Default: false

This option generates a .d.ts file next to each CSS module file.

FAQ

What issues does this plugin address?

Vite delegates bundling each CSS Module to postcss-modules, leading to significant problems:

  1. CSS Modules not integrated into Vite's build

    Since postcss-modules is a black box that only returns the final bundled output, Vite plugins can't hook into the CSS Modules build or process their internal dependencies. This prevents post-processing by plugins like SCSS, PostCSS, or LightningCSS. (#10079, #10340)

  2. Duplicated CSS Module dependencies

    Bundling CSS Modules separately duplicates shared dependencies, increasing bundle size and causing style overrides. (#7504, #15683)

  3. Silent failures on unresolved dependencies

    postcss-modules fails silently when it can't resolve a composes dependency—missing exports don't throw errors, making CSS bugs harder to catch. (#16075)

The vite-css-modules plugin fixes these issues by seamlessly integrating CSS Modules into Vite's build process.

How does this work?

The plugin treats CSS Modules as JavaScript modules, fully integrating them into Vite's build pipeline. Here's how:

  • Transforms CSS into JS modules

    CSS Modules are compiled into JS files that load the CSS. composes rules become JS imports, and class names are exported as named JS exports.

  • Integrates with Vite's module graph

    Because they're now JS modules, CSS Modules join Vite's module graph. This enables proper dependency resolution, bundling, and de-duplication.

  • Unlocks plugin compatibility

    Other Vite plugins can now access and process CSS Modules—fixing the prior limitation where dependencies inside them were invisible.

This model is similar to Webpack's css-loader, making it familiar to devs transitioning from Webpack. It also reduces overhead and improves performance in larger projects.

Does it export class names as named exports?

Yes, but there are a few things to keep in mind:

  • JavaScript naming restrictions

    Older JavaScript versions don't allow special characters (like -) in variable names. So a class like .foo-bar couldn't be imported as foo-bar and had to be accessed via the default export.

  • Using localsConvention

    To work around this, set css.modules.localsConvention: 'camelCase' in your Vite config. This converts foo-barfooBar, making it a valid named export.

  • ES2022 support for arbitrary names

    With ES2022, you can now export/import names with any characters using quotes. This means .foo-bar can be used as a named export directly.

To enable this, set your build target to es2022:

// vite.config.js
export default {
    build: {
        target: 'es2022'
    }
}

Then import using:

import { 'foo-bar' as fooBar } from './styles.module.css'

// Use it
console.log(fooBar)

This gives you full named export access—even for class names with previously invalid characters.

Sponsors