DEV Community

Cover image for Type-safe use of CSS Modules with TypeScript in React x Vite
j1ngzoue
j1ngzoue

Posted on • Updated on

Type-safe use of CSS Modules with TypeScript in React x Vite

Introduce

You can check the example introduced here.

When developing with React and CSS Modules, you may find that the styling part is not type-safe. Introducing a more type-safe styling method.
For that purpose, I created two libraries, so I will introduce them.

It is assumed that vite, React and TypeScript are installed.

Install

npm i classnames-generics
npm i -D viet-plugin-sass-dts
Enter fullscreen mode Exit fullscreen mode
Set the plugin in the vite.config.ts file.
vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import sassDts from "vite-plugin-sass-dts";
import path from "path";

export default defineConfig({
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@use "@/styles" as common;`,
        importer(...args) {
          if (args[0] !== "@/styles") {
            return;
          }

          return {
            file: `${path.resolve(
              __dirname,
              "./src/assets/styles"
            )}`,
          };
        },
      },
    },
  },
  plugins: [
    react(),
    sassDts({
      allGenerate: true,
      global: {
        generate: true,
        outFile: path.resolve(__dirname, "./src/style.d.ts"),
      },
    }),
  ],
});
Enter fullscreen mode Exit fullscreen mode
Start vite
npm run dev
Enter fullscreen mode Exit fullscreen mode
Implement and save the scss file
src/App.module.scss
.header-1 {
  background-color: common.$primary;
  .active {
    background-color: black;
  }
}
Enter fullscreen mode Exit fullscreen mode
src/assets/styles/_index.scss
$primary: violet;

.row {
  display: flex;
}
Enter fullscreen mode Exit fullscreen mode
A type definition file is automatically created in the same directory as the saved scss file.
src/App.module.scss.d.ts
import globalClassNames from './style.d'
declare const classNames: typeof globalClassNames & {
  readonly 'header-1': 'header-1';
  readonly 'active': 'active';
};
export = classNames;
Enter fullscreen mode Exit fullscreen mode
src/style.d.ts
declare const classNames: {
  readonly 'row': 'row';
};
export = classNames;
Enter fullscreen mode Exit fullscreen mode
Implement components using type definitions
src/App.tsx
import { VFC } from "react";
import styles from "./App.module.scss";
import { classNamesFunc } from "classnames-generics";

const classNames = classNamesFunc<keyof typeof styles>();
type Props = {
  active: boolean;
};

export const App: VFC<Props> = (props) => {
  return (
    <header
      className={classNames(
        styles["header-1"],
        { [styles.active]: props.active },
        styles.row
      )}
    >
      vite-plugin-sass-dts-example
    </header>
  );
};
Enter fullscreen mode Exit fullscreen mode

Complementation is also effective, so you can expect an improvement in development speed.

Tips

You can create a type definition for an already created scss file by passing options at build time.

vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import sassDts from "vite-plugin-sass-dts";

export default defineConfig({
  plugins: [react(), sassDts({ allGenerate: true })],
});
Enter fullscreen mode Exit fullscreen mode
Build vite
npm run build
Enter fullscreen mode Exit fullscreen mode

Discussion (0)