Skip to content

Ch-Valentine/css-modules-dts-loader

Repository files navigation

css-modules-dts-loader

npm npm license

A Rspack and Webpack loader for generating TypeScript declaration files (.d.ts) for CSS Modules.

Overview

css-modules-dts-loader automatically generates (or verifies) .d.ts files from your CSS Modules. By parsing the CSS module output, it extracts class names, applies optional transformations (matching css-loader's behavior), and produces corresponding TypeScript declaration files. This enhances type safety and improves the developer experience when using CSS Modules in TypeScript projects.

Features

  • Automatic Declaration Generation: Creates .d.ts files alongside your CSS files.
  • css-loader v7 Compatible: Fully supports css-loader's exportLocalsConvention option.
  • Named Exports Support: Generates named exports or interface-based exports.
  • JavaScript Keywords Handling: Properly handles reserved keywords as class names using aliased exports for full type safety.
  • Verification Mode: Checks if existing declaration files are up-to-date (perfect for CI/CD).
  • Customizable Formatting: Choose quote style, indentation (tabs or spaces), and sorting.
  • Seamless Integration: Works with Webpack 5+ and Rspack.

Installation

Install via npm:

npm install --save-dev css-modules-dts-loader

Or with pnpm:

pnpm add -D css-modules-dts-loader

Usage

Webpack Configuration

Important: The loader options should match your css-loader configuration for consistent behavior.

const isProduction = process.env.NODE_ENV === "production";
const useNamedExports = true; // Should match css-loader setting

module.exports = {
  module: {
    rules: [
      {
        test: /\.module\.(css|postcss|pcss|scss|sass|less|styl|sss)$/i,
        use: [
          "style-loader",
          {
            loader: "css-modules-dts-loader",
            options: {
              // Export format (should match css-loader)
              namedExport: useNamedExports,
              exportLocalsConvention: "camel-case-only",

              // File generation mode
              mode: isProduction ? "verify" : "emit",

              // Formatting options
              quote: "double",
              indentStyle: "space",
              indentSize: 2,
              sort: true,

              // Custom banner
              banner: "// This file is automatically generated.\n// Please do not change this file!"
            }
          },
          {
            loader: "css-loader",
            options: {
              modules: {
                // These should match the dts-loader options above
                namedExport: useNamedExports,
                exportLocalsConvention: "camel-case-only"
              }
            }
          }
        ]
      }
    ]
  }
};

Loader Options

Option Type Default Description
exportLocalsConvention "as-is" | "camel-case" | "camel-case-only" | "dashes" | "dashes-only" See description How to transform class names. Defaults based on namedExport: "as-is" if true, "camel-case-only" if false. Matches css-loader's option.
namedExport boolean true When true, generates named exports (export const foo: string;). When false, generates an interface with default export. Should match css-loader's setting.
keywordPrefix string "__dts_" Prefix used for aliased exports of JavaScript reserved keywords (e.g., class, export). Must be a valid JavaScript identifier. Only applies when namedExport is true.
quote "single" | "double" "double" Quote style used for interface properties when namedExport is false.
indentStyle "tab" | "space" "space" Indentation style for interface properties.
indentSize number 2 Number of spaces for indentation when indentStyle is "space".
mode "emit" | "verify" "emit" In "emit" mode, writes/overwrites .d.ts files. In "verify" mode, checks files are up-to-date (useful for CI/CD).
sort boolean false When true, sorts class names alphabetically in the generated file.
banner string Auto-generated Custom banner comment at the top of generated files. Default: "// This file is automatically generated.\n// Please do not change this file!"
camelCase boolean undefined Deprecated (will be removed in v2.0.0): Use exportLocalsConvention instead. When true, maps to "camel-case-only". When false, maps to "as-is".

exportLocalsConvention Values

This option matches css-loader's behavior:

Value Description Example: .foo-bar
"as-is" Class names exported exactly as written fooBar → exports "foo-bar"
"camel-case" Exports both original and camelCase versions fooBar → exports both
"camel-case-only" Exports only camelCase version fooBar → exports fooBar only
"dashes" Converts dashes to camelCase, exports both Same as "camel-case"
"dashes-only" Converts dashes to camelCase, exports only camelCase Same as "camel-case-only"

JavaScript Keywords Handling

When using namedExport: true, JavaScript reserved keywords (like class, export, import, etc.) cannot be exported as named exports because they're invalid JavaScript syntax.

Behavior:

  • Keywords are exported using aliased exports to provide full type safety
  • Non-keyword classes are exported normally as named exports
  • Keywords are accessible via namespace import with full type safety
  • The prefix for aliased exports can be customized using the keywordPrefix option

Example:

/* styles.module.css */
.class { color: blue; }
.container { padding: 10px; }

Generated with namedExport: true and default keywordPrefix:

// styles.module.css.d.ts
export const container: string;

declare const __dts_class: string;
export { __dts_class as "class" };

Generated with namedExport: true and keywordPrefix: "dts":

// styles.module.css.d.ts
export const container: string;

declare const dtsclass: string;
export { dtsclass as "class" };

Usage in TypeScript:

import * as styles from './styles.module.css';

// Both are fully type-safe:
styles.container;  // ✅ Type-safe
styles.class;      // ✅ Type-safe via aliased export

Why customize keywordPrefix?

  • Linter compatibility: Some ESLint configurations flag identifiers starting with __ (double underscore)
  • Naming conventions: Match your project's naming standards
  • Readability: Use a prefix that's clearer in your codebase (e.g., dts, css_, module_)

Note: With namedExport: false, all classes (including keywords) are included in the interface, so there's no difference in behavior for keywords.

Example Output

With namedExport: true (default)

// This file is automatically generated.
// Please do not change this file!
export const button: string;
export const container: string;

Usage:

import { button, container } from './styles.module.css';
// or
import * as styles from './styles.module.css';

If your CSS contains keywords:

// This file is automatically generated.
// Please do not change this file!
export const button: string;
export const container: string;

declare const __dts_class: string;
export { __dts_class as "class" };

Usage with keywords:

import * as styles from './styles.module.css';

styles.button;
styles.container;
styles.class; // Type-safe via aliased export

// Or

import {
	button,
	container,
	class as notReservedJsKeyword
} from './styles.module.css';

<div className={notReservedJsKeyword}>
	No conflicts with JS keywords!
</div>

With namedExport: false

// This file is automatically generated.
// Please do not change this file!
interface CssExports {
  "button": string;
  "container": string;
  "class": string;
}

export const cssExports: CssExports;
export default cssExports;

Usage:

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

styles.button;
styles.container;
styles.class;

Configuration Tips

  1. Match css-loader options: Your namedExport and exportLocalsConvention options should match css-loader's configuration to avoid runtime/type mismatches.

  2. Use verify mode in CI: Set mode: "verify" in production/CI to ensure .d.ts files are always up-to-date.

How It Works

  1. Runs after css-loader: The loader processes the JavaScript output from css-loader, not the raw CSS.

  2. Extracts class names: Parses the module exports to find all CSS class names (supports both object and named export formats).

  3. Applies transformations: Based on exportLocalsConvention, transforms class names (e.g., kebab-case → camelCase).

  4. Generates declarations: Creates TypeScript declaration files with appropriate export format.

  5. Writes or verifies: In emit mode, writes .d.ts files. In verify mode, checks they're up-to-date.

Migrating from camelCase to exportLocalsConvention

If you're using the deprecated camelCase option:

// Old (deprecated)
{
  camelCase: true
}

// New (recommended)
{
  exportLocalsConvention: "camel-case-only"
}

The camelCase option still works for backward compatibility but will be removed in v2.0.0.

Contributing

Contributions, bug reports, and feature requests are welcome! To contribute:

  1. Fork the repository
  2. Create a feature branch: git checkout -b my-new-feature
  3. Commit your changes: git commit -am 'Add some feature'
  4. Push to the branch: git push origin my-new-feature
  5. Create a new Pull Request

Before contributing, please review the CONTRIBUTING.md guidelines (if available).

License

This project is licensed under the MIT License. See the LICENSE file for details.

Changelog

See CHANGELOG.md for version history and recent changes.

Support

If you encounter any issues or have questions, please open an issue in the GitHub repository.

About

A Webpack / Rspack loader for generating TypeScript declaration files for CSS Modules.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •