Type-safe use of CSS Modules with TypeScript in React x Vite
Tags: react, vite, typescript, cssmodules
Published: 2021-10-15 00:46:02

## Introduce
You can check the example introduced [here](https://github.com/activeguild/vite-plugin-sass-dts/tree/master/example).


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.

- [vite-plugin-sass-dts] (https://github.com/activeguild/vite-plugin-sass-dts)
  - When dev or build is started, the type definition of css, scss, sass file is automatically created.
- [classnames-generics] (https://github.com/activeguild/classnames-generics)
  - [classnames] (https://github.com/JedWatson/classnames) You can use the library more type-safely.

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

### Install
```bash
npm i classnames-generics
npm i -D viet-plugin-sass-dts
```

##### Set the plugin in the `vite.config.ts` file.
###### vite.config.ts
```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"),
      },
    }),
  ],
});
``` 

##### Start `vite`
```bash
npm run dev
```

##### Implement and save the `scss` file
###### src/App.module.scss
```scss
.header-1 {
  background-color: common.$primary;
  .active {
    background-color: black;
  }
}
```
###### src/assets/styles/_index.scss
```scss
$primary: violet;

.row {
  display: flex;
}
```


##### A type definition file is automatically created in the same directory as the saved `scss` file.
###### src/App.module.scss.d.ts
```ts
import globalClassNames from './style.d'
declare const classNames: typeof globalClassNames & {
  readonly 'header-1': 'header-1';
  readonly 'active': 'active';
};
export = classNames;
```
###### src/style.d.ts
```ts
declare const classNames: {
  readonly 'row': 'row';
};
export = classNames;
```

##### Implement components using type definitions
###### src/App.tsx
```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>
  );
};
```

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
```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 })],
});
``` 

##### Build `vite`
```bash
npm run build
```

I'm waiting for your feedback.