Generating TypeScript definitions for CSS Modules using SASS
Spencer Miskoviak
February 10, 2019 • Last updated on November 7, 2020
Photo by ariel sion
CSS Modules can be a great tool for maintaining styles in a large codebase. It works much like vanilla CSS with the primary difference being all class names are local by default. This works well in modern component-based architectures where a single style module can live alongside the component that relies on those styles.
Since a CSS Module is vanilla CSS by default, it doesn't introduce a steep learning curve. But in a large codebase, it's not desirable to repeatedly define specific values (such as color hexes), especially if using a design system. There are approaches specific to CSS Modules for adding support for variables, but SASS is a reliable technology to add support not only for variables but many other useful features.
Beyond styling an application, TypeScript can be a great tool for maintaining a large codebase in general. Providing type definitions for functions or components can make the APIs more discoverable to newcomers or help prevent improper usage.
CSS Modules and TypeScript can each be used on their own. What about integrating CSS Modules, SASS and TypeScript to add type-safety around the usage of the styles?
Tooling
There are many tools that exist in the world of CSS Modules and TypeScript. Before reaching for additional tooling, it's worth evaluating what's possible with only TypeScript.
The easiest way is to not integrate the two together. By using require
instead
of import
the styles object will be typed as any
offering no type-safety.
This is ideal for moving quickly but doesn't leverage TypeScript at all.
const styles = require("./styles.css");
Adding full type-safety requires defining .d.ts
(type definition) files for
the corresponding styles. This can be done by manually creating these files and
defining all of the corresponding class names. It's tedious, duplicates work,
and is error prone which defeats the purpose of type-safety. This is
where additional tooling can bring an advantage.
Instead of defining every individual class name, there is an intermediate solution
that provides some type-safety. This is done by defining a module definition
that applies to all style imports. It won't catch invalid class names or provide
the typeahead but it is an improvement over any
.
For example, a file named css-modules.d.ts
would achieve this with the
following definition:
declare module "*.scss" {
const styles: { [className: string]: string };
export default styles;
}
It prevents invalid usages such as styles.customClass.somethingElse
since
customClass
is a string
but it doesn't validate if customClass
actually
exists.
What about adding full type-safety with additional tooling?
typings-for-css-modules-loader
typings-for-css-modules-loader,
is a drop-in replacement for css-loader
(necessary for CSS Modules) to
automatically generate the type definitions for your CSS Modules in webpack.
This means it can be used with any CSS preprocessor such as SASS.
This post is a good overview to generate type definitions using both CSS Modules and TypeScript with webpack.
This specific package has since been deprecated, but there are now several maintained forks and alternatives available:
- TeamSupercell/typings-for-css-modules-loader
- dropbox/typed-css-modules-webpack-plugin
- Megaputer/dts-css-modules-loader
What if you're not using webpack?
typed-css-modules
typed-css-modules is a command-line interface (CLI) for generating type definitions for CSS Modules. After installing the package, it can be run outside of webpack. This is useful if you're not using webpack, experimenting with adding type definitions to see what it would look like, or want to run it in one-off situations.
But what if you're using SASS?
typed-scss-modules
The typed-scss-modules package is
heavily inspired by typed-css-modules
. It's a CLI that generates type
definitions focused on supporting CSS Modules written using SASS.
Getting started
Install the package as a development dependency since the type definitions should only need to be generated in development:
yarn add -D typed-scss-modules
Now, the types can be generated by providing the directory (or glob pattern):
yarn tsm src/
Listing differences
This option is inspired by Prettier's list-different option which is particularly useful in Continuous Integration to ensure all proposed code changes adhere to the proper formatting. Similarly, in this case, if a type definition doesn't match what would be automatically generated, the command will fail and list the files that are invalid.
yarn tsm src/ --listDifferent
Listing the differences is only one of many options. See the README
for a full list of options for changing the naming convention, the type definition format, handling aliases, included search paths and other options.
📚 Summary
There is a spectrum for how CSS Modules can be integrated with TypeScript, ranging from not at all, to perfect type-safety. Depending on the specific use case, existing codebase, and other technologies being used there are many tools that can be used that fit the different needs.
Tags:
course
Practical Abstract Syntax Trees
Learn the fundamentals of abstract syntax trees, what they are, how they work, and dive into several practical use cases of abstract syntax trees to maintain a JavaScript codebase.
Check out the course