Css Module Type + Nextjs 14!
Tags: css, typescript, webpack, nextjs
Published: 2024-01-19 18:19:54


## In this article
- [In this article](#in-this-article)
- [Introduction](#introduction)
- [Time-saving Webpack plugin](#time-saving-Webpack-plugin) 
- [Steps](#steps)
- [Start Create __Webpack plugin__](#Start-Create-Webpack-plugin)
- [Conclusion](#conclusion)
- [Source Code](https://github.com/rezamoosavidweb/next-css-module-type)




## Introduction
In this upcoming article, we're diving into using CSS Modules with Webpack5, Nextjs, and TypeScript. Following my exploration of __Vite__ in [__this link__](https://dev.to/rezamoosavidweb/css-module-type-vite-5hlm), our goal is to assist developers in handling TypeScript errors tied to CSS Modules and share practical tips on styling with style.module.css.

We aim to demystify the process, making it easy for developers to ensure type safety in their styles and write more robust, maintainable code.

By the end of this article, you'll have a clear understanding of managing TypeScript errors in the context of CSS Modules, empowering you to create clean, error-free code in your __Nextjs__ projects.

Whether you're a TypeScript enthusiast or a React developer seeking better styling practices, join us to simplify TypeScript errors and highlight best practices when working with CSS Modules in the __webpack__ environment.


## Time-saving Webpack plugin
Automating the generation of module types for CSS Modules is a game-changer. This tool simplifies the entire process, minimizing manual work and decreasing the likelihood of errors. Specifically designed for Next.js with Webpack, it seamlessly integrates into your projects, effortlessly managing CSS Module type definitions. Whether you're an experienced developer or just diving into Next.js and Webpack, this plugin is your go-to for streamlining the workflow related to CSS Modules in TypeScript. It becomes an indispensable asset for enhancing the development experience in your React projects within the Next.js and Webpack environment.

## Steps
1)Identify all CSS modules.

2)Extract classes defined within them./

3)Create a file with a .d.ts extension alongside each module.css, 
placing type definitions for classes based on the found classNames.

4)Format the file content using Prettier ,finally save the file.

5)All the above steps should be performed for each change in CSS module files.

__Note:__ As noted in [__this article__](https://dev.to/rezamoosavidweb/css-module-type-vite-5hlm) for __Vite__ These steps are performed during development so that types are generated once when the app is run. Afterward, when the app is running, each modified __css module__ file is checked, and its type is generated accordingly.

## Start Create __Webpack plugin__
Before you start, you need to have a React app with Vite and TypeScript, which you can download from [__this link__](https://stackblitz.com/edit/vitejs-vite-qhjhjj?file=index.html&terminal=dev). 
Then, create a file named __watching-css-modules.js__ in the root of your project.

now add blew to watching-css-modules.ts file:
```bash
//watching-css-modules.ts

import { Plugin } from "vite";

class CssModuleTypes {
  constructor(rootDir = "./src/") {
    this.rootDir = rootDir ?? "";
  }
  apply(compiler) {
    compiler.hooks.emit.tapAsync("TranslatePlugin", (compilation, callback) => {
      compilation.contextDependencies.add(
        path.resolve(__dirname, this.rootDir)
      );
      callback();
    });
    compiler.hooks.invalid.tap("Invalid", (fileName, changeTime) => {
      //code will be added later
    });
  }
}

function withCssTypes(nextConfig = {}) {
  return Object.assign({}, nextConfig, {
    webpack: (config) => {
      config.plugins.push(new CssModuleTypes());
      return config;
    },
  });
}

module.exports = withCssTypes;

```
### about above code:
The __webpack__ Compiler supports watching which monitors the file system and recompiles as files change. When in watch mode, the compiler will emit the additional events such as __watchRun__, __watchClose__, and __invalid__.
I used __invalid__ here because two parameters are passed to it: one is __fileName__, and the other is __changeTime__. The __fileName__ is necessary for checking whether the __module.css__ file has changed.


Now, before proceeding with the development of our plugin, it's necessary to add it to webpack. Therefore, add the css-modules-types plugin to next.config.js:
```bash
//next.config.js

const withCssType = require("./watching-css-modules");

/** @type {import('next').NextConfig} */
const nextConfig = {};

module.exports = withCssType(nextConfig);


```


To locate all module.css files during app runtime and track changes in development, two packages, "path" and "fs," are employed. After filtering and finding module.css files, their content is extracted. Utilizing postCss, we then identify all classes used within these files. Subsequently, corresponding types for the found classes are generated. Finally, employing the "prettier" package, we format their content and save them in files with the same names as the source module.css files, appended with the .d.ts extension.

```bash
const fs = require("fs");
const postcss = require("postcss");
const selectorParse = require("postcss-selector-parser");
const prettier = require("prettier");
const path = require("path");
const srcDir = path.join(process.cwd(), "src");

const removeDupStrFromArray = (arr) => {
  const uniqueArray = [];

  for (const str of arr) {
    if (!uniqueArray.includes(str)) {
      uniqueArray.push(str);
    }
  }
  return uniqueArray;
};
function isCSSSelectorValid(selector) {
  try {
    selectorParse().processSync(selector);
    return true; // If no errors occurred, the selector is valid
  } catch (error) {
    console.error(`Invalid CSS selector: ${selector}`);
    return false; // If an error occurred, the selector is not valid
  }
}
const typeDeceleration = async (classArray) => {
  const data = `declare const styles: {${classArray
    ?.map((el, index) => `readonly '${el}': string;\n`)
    .join("")}};export default styles;`;
  const formattedData = await prettier.format(data, {
    parser: "typescript",
  });
  return formattedData;
};

function isDir(dir) {
  try {
    return fs.statSync(dir).isDirectory();
  } catch {
    return false;
  }
}
function createUniquesClassName(fullPath) {
  return new Promise((resolve, reject) => {
    let css = fs.readFileSync(fullPath);
    const classNames = [];
    postcss()
      .process(css, { from: fullPath, to: fullPath?.replace(".css", ".d.css") })
      .then(async (result) => {
        result.root.walkRules((rule) => {
          if (!isCSSSelectorValid(rule.selector)) return;
          selectorParse((selectors) => {
            selectors.walkClasses((selector) => {
              classNames.push(selector.value);
            });
          }).process(rule.selector);
        });

        const uniquesClassName = await removeDupStrFromArray(classNames);
        resolve(uniquesClassName);
      })
      .catch(reject);
  });
}

async function createDecelerationFile(fullPath) {
  const uniquesClassName = await createUniquesClassName(fullPath);

  if (uniquesClassName?.length > 0) {
    const decelerationPath = fullPath?.replace(
      ".module.css",
      ".module.css.d.ts"
    );
    const formattedDeceleration = await typeDeceleration(uniquesClassName);

    try {
      fs.writeFileSync(decelerationPath, formattedDeceleration, (error) =>
        console.log("error in writeFileSync:", error)
      );
    } catch (err) {
      console.log("error in writing file:", err);
    }
  }
}

function getCssModulesFiles(pathDir) {
  let directory = pathDir ?? srcDir;

  if (isDir(directory)) {
    fs.readdirSync(directory).forEach(async (dir) => {
      const fullPath = path.join(directory, dir);
      if (isDir(fullPath)) return getCssModulesFiles(fullPath);
      if (!fullPath.endsWith(".module.css")) return;

      try {
        createDecelerationFile(fullPath);
      } catch (e) {
        console.log(e);
      }
    });
  } else {
    if (!directory.endsWith(".module.css")) return;
    createDecelerationFile(directory);
  }
}

class CssModuleTypes {
  constructor(rootDir = "./src/") {
    this.rootDir = rootDir ?? "";
  }
  apply(compiler) {
    compiler.hooks.emit.tapAsync("TranslatePlugin", (compilation, callback) => {
      compilation.contextDependencies.add(
        path.resolve(__dirname, this.rootDir)
      );
      callback();
    });
    compiler.hooks.invalid.tap("Invalid", (fileName, changeTime) => {
      getCssModulesFiles(fileName);
    });
  }
}

function withCssTypes(nextConfig = {}) {
  return Object.assign({}, nextConfig, {
    webpack: (config) => {
      config.plugins.push(new CssModuleTypes());
      return config;
    },
  });
}

module.exports = withCssTypes;

```
## Conclusion
With this plugin, you can safely leverage the benefits of CSS Modules and TypeScript. Initially planned as a standalone package, I chose to share it through an article format, providing the complete [__source code__](https://github.com/rezamoosavidweb/next-css-module-type) on GitHub. Dive into the article and explore the code to spark ideas for your own plugins. 
In another article, discover this plugin adapted for __Vite__ and __React__. Check out [__this link__](https://dev.to/rezamoosavidweb/css-module-type-vite-5hlm) where we discuss __Vite__, __React__, and __CSS Modules__.