pcssmdts v0.2.0
pcssmdts
A powerful TypeScript definition generator for PostCSS-powered CSS Modules, ensuring type safety and providing IDE intellisense for your CSS class names.
Why pcssmdts?
The Problem
When working with CSS Modules in TypeScript projects, especially in large design systems or component libraries, you often face these challenges:
- Type Safety Gaps: CSS class names are essentially strings, making them prone to typos and runtime errors
- Poor IDE Support: No autocomplete for CSS class names means developers need to constantly reference CSS files
- Refactoring Difficulties: Renaming CSS classes becomes risky without TypeScript's refactoring support
- Design System Maintenance: Ensuring consistent class usage across a large codebase is challenging
The Solution
pcssmdts bridges these gaps by:
- Type Safety: Automatically generates TypeScript definitions for your CSS Modules
- IDE Integration: Provides full IntelliSense support for class names
- Refactoring Support: Makes class names refactorable through TypeScript's tooling
- PostCSS Integration: Works seamlessly with your existing PostCSS setup
Comparison with IDE Solutions
While IDE plugins like typescript-plugin-css-modules
can provide type support during development, pcssmdts offers several advantages:
CI/CD Integration:
- Generate type definitions as part of your build process
- Catch type errors in CI before they reach production
- No reliance on developer IDE configuration
Team Consistency:
- Ensures all team members have the same type definitions
- Works regardless of IDE or editor preferences
- No need to maintain plugin configurations across the team
Build-time Validation:
- Validates CSS Modules during build time
- Integrates with your existing TypeScript compilation
- Prevents deployment of mismatched class names
Source Control:
- Generated types can be committed (optional)
- Enables type checking in environments without PostCSS setup
- Perfect for consuming libraries in other projects
// With IDE plugin only:
// - Types only available during development
// - Requires IDE configuration
// - May vary between team members
// With pcssmdts:
// - Types available everywhere
// - Part of your build process
// - Consistent across team and CI
import styles from './Button.module.css';
// TypeScript knows exactly what's available
const className = styles.button; // ā
Type-safe
PostCSS Configuration Flexibility
One of the key advantages of pcssmdts is its seamless integration with PostCSS, allowing you to:
Use Your Existing Setup:
// postcss.config.js module.exports = { plugins: [ require('postcss-nested'), require('tailwindcss'), require('autoprefixer'), require('postcss-modules')({ // your custom options generateScopedName: '[name]__[local]___[hash:base64:5]', }), ], };
Support Modern CSS Features:
/* Your CSS Module with modern syntax */ .button { /* Nesting (via postcss-nested) */ &:hover { background: theme('colors.blue.600'); } /* Custom Media Queries */ @media (--dark-mode) { background: theme('colors.gray.800'); } /* CSS Custom Properties */ --button-padding: 1rem; padding: var(--button-padding); }
Framework Integration:
- Works with Tailwind CSS for utility-first styling
- Compatible with CSS preprocessors (SASS/LESS) through PostCSS plugins
Integrates with popular frameworks:
// Next.js import styles from './Button.module.css'; // React import styles from './Button.module.scss'; // With SASS // Vue with TypeScript import styles from './Button.module.css';
Note: CSS Modules are different from CSS-in-JS solutions. While CSS-in-JS libraries like styled-components or emotion write styles in JavaScript, CSS Modules let you write traditional CSS files with local scope and static analysis. pcssmdts is specifically designed for CSS Modules, providing type safety without runtime overhead.
- Custom Naming Conventions:
// postcss.config.js with custom class naming module.exports = { plugins: [ require('postcss-modules')({ generateScopedName: (name, filename, css) => { // Your custom naming logic return `myApp_${name}_${hashString(css)}`; }, }), ], };
This flexibility means you can:
- Use modern CSS features while maintaining type safety
- Integrate with your existing build pipeline
- Customize class name generation to match your needs
- Support complex CSS transformations while keeping type definitions accurate
Real-World Use Cases
1. Design System Development
// Before: No type safety or autocomplete
const Button = ({ variant }) => (
<button className={`btn btn--${variant}`}>Click me</button>
);
// After: Full type safety and autocomplete
import styles from './Button.module.css';
type ButtonVariant = 'primary' | 'secondary';
const Button = ({ variant }: { variant: ButtonVariant }) => (
<button
className={`${styles.btn} ${
variant === 'primary' ? styles.btnPrimary : styles.btnSecondary
}`}
>
Click me
</button>
);
2. Large-Scale Application Migration
When migrating a large application to use CSS Modules:
// Before: Global CSS with potential naming conflicts
const Header = () => (
<header className="app-header">
<nav className="navigation">
<a className="nav-link active">Home</a>
</nav>
</header>
);
// After: Scoped CSS Modules with type safety
import styles from './Header.module.css';
const Header = () => (
<header className={styles.header}>
<nav className={styles.navigation}>
<a className={`${styles.link} ${styles.linkActive}`}>Home</a>
</nav>
</header>
);
3. Component Library Development
When building a shared component library:
// components/Card/Card.module.css
.card {
/* styles */
}
.cardHeader {
/* styles */
}
.cardContent {
/* styles */
}
.cardFooter {
/* styles */
}
// components/Card/Card.tsx
import styles from './Card.module.css';
export const Card = ({
header,
children,
footer
}: CardProps) => (
<div className={styles.card}>
{header && <div className={styles.cardHeader}>{header}</div>}
<div className={styles.cardContent}>{children}</div>
{footer && <div className={styles.cardFooter}>{footer}</div>}
</div>
);
Benefits in Practice
Development Speed:
- Immediate feedback on invalid class names
- Autocomplete reduces need to reference CSS files
- Faster onboarding for new team members
Code Quality:
- Catch CSS class typos at compile time
- Ensure consistent class usage across components
- Make refactoring CSS class names safe and easy
Maintenance:
- Track CSS class usage across the codebase
- Safely remove unused classes
- Easier code reviews with type checking
Features
- š Fast and efficient d.ts generation
- š Full PostCSS support with custom plugins and configurations
- šÆ Automatic camelCase class name conversion
- š” IDE intellisense support
- ā TypeScript compilation-time type checking
- š Configurable output formats
- š¦ Zero runtime overhead
Requirements
- Node.js:
>=16.19.1 <21
- PostCSS configuration file (optional, will use defaults if not provided)
Installation
Quick Start with npx (Recommended)
The fastest way to use this utility is through npx
without installation:
npx pcssmdts "src/**/*.module.css"
Local Installation
Add it as a development dependency to your project:
# npm
npm install pcssmdts --save-dev
# yarn
yarn add pcssmdts -D
# pnpm
pnpm add -D pcssmdts
Add to your package.json scripts:
{
"scripts": {
"generate:style-defs": "pcssmdts \"src/**/*.module.css\"",
"generate:style-defs:watch": "pcssmdts \"src/**/*.module.css\" --watch"
}
}
How It Works
pcssmdts operates in three main steps:
- CSS Module Detection: Scans your project for CSS module files based on the provided glob pattern
- PostCSS Processing: Processes your CSS files using your PostCSS configuration
- TypeScript Definition Generation: Creates corresponding .d.ts files with type definitions
Example Project Structure
src/
āāā components/
ā āāā Button/
ā āāā styles.module.css
ā āāā styles.module.css.d.ts (generated)
ā āāā Button.tsx
CSS Module Example
/* styles.module.css */
.button {
display: flex;
&--primary {
background: blue;
}
&--secondary {
background: gray;
}
}
.icon-wrapper {
margin-right: 8px;
}
Generated TypeScript Definition
// styles.module.css.d.ts
declare const styles: {
readonly button: string;
readonly buttonPrimary: string;
readonly buttonSecondary: string;
readonly iconWrapper: string;
};
export = styles;
React Component Usage
import styles from './styles.module.css';
export const Button: React.FC<{
variant?: 'primary' | 'secondary';
}> = ({ variant = 'primary' }) => {
return (
<button
className={`${styles.button} ${
variant === 'primary' ? styles.buttonPrimary : styles.buttonSecondary
}`}
>
<span className={styles.iconWrapper}>{/* Your icon here */}</span>
Click me
</button>
);
};
CLI Options
The CLI is powered by typed-css-modules under the hood, providing robust TypeScript definition generation.
pcssmdts <source> [options]
Arguments:
source Source pattern for CSS module files (e.g., "src/**/*.module.css")
Basic options:
-v, --verbose Enable verbose logging [boolean]
-c, --config Custom PostCSS config path [string]
-k, --keep Keep compiled CSS files [boolean]
-w, --watch Watch mode for file changes [boolean]
typed-css-modules options:
-n, --namedExports Use named exports in .d.ts files [boolean] [default: false]
--camelCase Convert CSS class names to camelCase [choices: "true", "false", "dashes"]
--outDir Output directory for generated d.ts files. When specified, all generated .d.ts files will be placed in this directory, regardless of their original location. [string]
--eol End of line character [string]
General:
--help Show help information [boolean]
--version Show version number [boolean]
Common Use Cases
Basic Usage By default, .d.ts files are generated next to their corresponding CSS modules:
pcssmdts "src/**/*.module.css"
Result:
src/ āāā components/ ā āāā Button/ ā āāā styles.module.css ā āāā styles.module.css.d.ts (generated here)
Using outDir Place all generated .d.ts files in a specific directory:
pcssmdts "src/**/*.module.css" --outDir types
Result:
src/ āāā components/ ā āāā Button/ ā āāā styles.module.css āāā types/ āāā styles.module.css.d.ts (generated here)
Note: When using
outDir
, all generated .d.ts files will be placed flat in the specified directory, regardless of their original location in the source tree.Keep Compiled Files for Debugging Preserve PostCSS-processed files for debugging or inspection:
pcssmdts "src/**/*.module.css" -k
Result:
src/ āāā components/ ā āāā Button/ ā āāā styles.module.css (original) ā āāā _compiled.styles.module.css (processed CSS) ā āāā styles.module.css.d.ts (generated types)
The
_compiled.*.css
files contain the processed CSS after all PostCSS transformations. This is useful for:- Debugging CSS transformations
- Verifying PostCSS plugin output
- Inspecting final class names after scoping
Watch Mode
pcssmdts "src/**/*.module.css" -w
Custom PostCSS Config
pcssmdts "src/**/*.module.css" -c ./config/postcss.config.js
Named Exports with Custom Output Directory
pcssmdts "src/**/*.module.css" -n --outDir types
CamelCase with Dashes
pcssmdts "src/**/*.module.css" --camelCase dashes
Keep Compiled Files with Custom EOL
pcssmdts "src/**/*.module.css" -k --eol "\n"
Watch Mode
Watch mode (-w
or --watch
) automatically regenerates TypeScript definitions when your CSS modules change. Here's how to verify it's working:
Start Watch Mode
pcssmdts "src/**/*.module.css" -w
Verify Initial Generation
- Check that
.d.ts
files are generated for all your CSS modules - You should see console output indicating the initial generation
- Check that
Test File Changes
# 1. Open your CSS module in an editor # 2. Add a new class .new-class { color: red; } # 3. Save the file
The watcher should automatically:
- Detect the file change
- Regenerate the
.d.ts
file - Show console output about the regeneration
Verify Type Updates
// Your component file import styles from './styles.module.css'; // The new class should be available with TypeScript intellisense <div className={styles.newClass} />; // If using camelCase
Common Watch Mode Issues
- If changes aren't detected, ensure you're watching the correct directory
- Some IDEs may need to refresh the TypeScript server to pick up new definitions
- Use
-v
flag for verbose logging to debug watch mode issues:pcssmdts "src/**/*.module.css" -w -v
PostCSS Configuration
pcssmdts uses postcss-load-config to load your PostCSS configuration. It supports all standard PostCSS config files:
postcss.config.js
.postcssrc
package.json
withpostcss
field
Example PostCSS config:
// postcss.config.js
module.exports = {
plugins: [
require('postcss-nested'),
require('postcss-modules')({
// your options here
}),
// other plugins...
],
};
Best Practices
Git Ignore Add generated files to your .gitignore:
**/*.module.css.d.ts **/_compiled.*.css
CI Integration Run type generation before TypeScript compilation:
{ "scripts": { "build": "pcssmdts \"src/**/*.module.css\" && tsc" } }
Development Workflow Use watch mode during development:
{ "scripts": { "dev": "concurrently \"pcssmdts 'src/**/*.module.css' -w\" \"your-dev-server\"" } }
Contributing
Contributions are welcome! Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Support
- š Documentation
- š Issue Tracker
- š¬ Discussions