privatenumber / vite-css-modules

Vite plugin for correct CSS Modules behavior
MIT License
50 stars 2 forks source link
css css-modules patch plugin postcss vite

vite-css-modules

CSS Modules is currently broken in Vite. This Vite plugin fixes them by correctly handling CSS Modules.

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

→ Play with a demo on StackBlitz



Why use this plugin?

Currently, CSS Modules is implemented incorrectly in Vite, leading to critical issues that makes it unusable. This plugin corrects the implementation, fixing many known bugs and making CSS Modules work expectedly in your projects.

Here are the issues this plugin addresses:

Dependency duplication

Prevents duplicated CSS Modules, preventing style conflicts from duplication and minimizing bundle size

Hot Module Replacement (HMR) issues

Enables HMR in CSS Module dependencies, improving development efficiency

Plugin compatibility

Allows other Vite plugins (e.g. PostCSS/SCSS) to process CSS Modules dependencies

Improved composition handling

This plugin raises errors for missing composes dependencies and supports CSS class names that collide with JavaScript reserved keywords.

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.

Bonus feature: strongly typed CSS Modules

This plugin can conveniently generate type definitions for CSS Modules by creating .d.ts files alongside the source files. For example, if style.module.css is imported, it will create a style.module.css.d.ts file next to it containing type definitions for the exported class names.

This improves the developer experience by providing type-safe class name imports, better autocompletion, and enhanced error checking directly in your editor when working with CSS Modules.

To enable this feature, pass generateSourceTypes to the patchCssModules plugin:

patchCssModules({
    generateSourceTypes: true
})

API

patchCssModules(options)

The patchCssModules function is the main method of the plugin and accepts an options object. Here are the options you can configure:

options.exportMode

Specifies how class names are exported from the CSS Module:

options.generateSourceTypes

If enabled, this option generates TypeScript .d.ts files for each CSS module, providing type definitions for all exported class names. This feature enhances developer experience by enabling autocompletion and type safety for imported CSS classes.

FAQ

What issues does this plugin address?

Vite currently processes CSS Modules by bundling each entry point separately using postcss-modules. This approach leads to several significant problems:

  1. CSS Modules are not integrated into Vite's build process

    Since each CSS Module is bundled in isolation, Vite plugins cannot access the dependencies resolved within them. This limitation prevents further CSS post-processing by Vite plugins, such as those handling SCSS, PostCSS, or LightningCSS transformations. Even though postcss-modules attempts to apply other PostCSS plugins to dependencies, it encounters issues, as reported in Issue #10079 and Issue #10340.

  2. Duplicated CSS Module dependencies

    Because each CSS Module is bundled separately, shared dependencies across modules are duplicated in the final Vite build. This duplication results in larger bundle sizes and can disrupt your styles by overriding previously declared classes. This problem is documented in Issue #7504 and Issue #15683.

  3. Silent failures on unresolved dependencies

    Vite (specifically, postcss-modules) fails silently when it cannot resolve a composes dependency. This means missing exports do not trigger errors, making it harder to catch CSS bugs early. This issue is highlighted in Issue #16075.

By addressing these issues, the vite-css-modules plugin enhances the way Vite handles CSS Modules, integrating them seamlessly into the build process and resolving these critical problems.

How does this work?

The plugin changes Vite's handling of CSS Modules by treating them as JavaScript modules. Here's how it achieves this:

This approach mirrors how Webpack’s css-loader works, making it familiar to developers transitioning from Webpack. Additionally, because this method reduces the overhead in loading CSS Modules, it can offer performance improvements in larger applications.

Does it export class names as named exports?

Yes, the plugin allows class names to be exported as named exports, but there are some considerations:

To use this feature, set your Vite build target to es2022 or above in your vite.config.js:

{
    build: {
        target: 'es2022'
    }
}

You can then import class names with special characters using the following syntax:

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

This approach lets you access all class names as named exports, even those with characters that were previously invalid in JavaScript variable names.

Sponsors