DEV Community

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

Posted on • Edited on

7 1

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

I'm waiting for your feedback.

Top comments (0)

nextjs tutorial video

Youtube Tutorial Series 📺

So you built a Next.js app, but you need a clear view of the entire operation flow to be able to identify performance bottlenecks before you launch. But how do you get started? Get the essentials on tracing for Next.js from @nikolovlazar in this video series 👀

Watch the Youtube series