A Rspack and Webpack loader for generating TypeScript declaration files (.d.ts) for CSS Modules.
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.
- Automatic Declaration Generation: Creates
.d.tsfiles alongside your CSS files. - css-loader v7 Compatible: Fully supports css-loader's
exportLocalsConventionoption. - 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.
Install via npm:
npm install --save-dev css-modules-dts-loaderOr with pnpm:
pnpm add -D css-modules-dts-loaderImportant: 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"
}
}
}
]
}
]
}
};| 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". |
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" |
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
keywordPrefixoption
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 exportWhy 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.
// 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>// 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;-
Match css-loader options: Your
namedExportandexportLocalsConventionoptions should match css-loader's configuration to avoid runtime/type mismatches. -
Use verify mode in CI: Set
mode: "verify"in production/CI to ensure.d.tsfiles are always up-to-date.
-
Runs after css-loader: The loader processes the JavaScript output from css-loader, not the raw CSS.
-
Extracts class names: Parses the module exports to find all CSS class names (supports both object and named export formats).
-
Applies transformations: Based on
exportLocalsConvention, transforms class names (e.g., kebab-case → camelCase). -
Generates declarations: Creates TypeScript declaration files with appropriate export format.
-
Writes or verifies: In
emitmode, writes.d.tsfiles. Inverifymode, checks they're up-to-date.
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.
Contributions, bug reports, and feature requests are welcome! To contribute:
- Fork the repository
- Create a feature branch:
git checkout -b my-new-feature - Commit your changes:
git commit -am 'Add some feature' - Push to the branch:
git push origin my-new-feature - Create a new Pull Request
Before contributing, please review the CONTRIBUTING.md guidelines (if available).
This project is licensed under the MIT License. See the LICENSE file for details.
See CHANGELOG.md for version history and recent changes.
If you encounter any issues or have questions, please open an issue in the GitHub repository.