121

How to import CSS modules in Typescript with Webpack?

  1. Generate (or auto-generate) .d.ts files for CSS? And use classic Typescript import statement? With ./styles.css.d.ts:

    import * as styles from './styles.css'
    
  2. Import using require Webpack function?

    let styles = require("./styles.css");
    

But for the last approach I must define the require function.

What is the best approach or best option and one that can also handle IntelliSense for the CSS file definitions and classes?

0

12 Answers 12

111

Now in the year 2021, all you need to do is add a src/Globals.d.ts to your project with these lines:

declare module "*.module.css";
declare module "*.module.scss";
// and so on for whatever flavor of css you're using

Then install and add

{
  "compilerOptions": {
    "plugins": [{ "name": "typescript-plugin-css-modules" }]
  }
}

to your tsconfig.

Example of this correctly functioning in VS code after making that simple change (root is a class defined in my stylesheet):

enter image description here

Webpack and tsc also compile correctly on the command line.

Sign up to request clarification or add additional context in comments.

2 Comments

In second to this if you may also checkout on this blog spin.atomicobject.com/2020/06/22/css-module-typescript/…
There is one Important step missing: \ ensure that VS Code will use the project’s version of Typescript instead of the VS Code version spin.atomicobject.com/2020/06/22/css-module-typescript \ -- \ If adding the setting.json file at the Vscode Folder Level Setting doesnt work, try use Workspace Level setting. \ Or, change the setting in TypeScript: Select TypeScript Version... in Vscode \ Or, for instruction with graphs, see puruvj.dev/blog/…
62

A) As you are saying, there is one simplest (not best) option to use require:

const css = require('./component.css')
  • We need to have typings for require as it's not standard feature in typescript.
  • Simplest typing for this specific require may be:

    declare function require(name: string): string;
    
  • Webpack will then compile typescript and use modules properly - BUT without any IDE help and class names checks for build.

B) There is better solution to use standard import:

import * as css from './component.css'
  • enables full class names IntelliSense
  • requires types definition for each css file, otherwise tsc compiler will fail

For proper IntelliSense, Webpack needs to generate types definition for each css file:

  1. Use webpack typings-for-css-modules-loader

    webpackConfig.module.loaders: [
      { test: /\.css$/, loader: 'typings-for-css-modules?modules' }
      { test: /\.scss$/, loader: 'typings-for-css-modules?modules&sass' }
    ];
    
  2. Loader will generate *.css.d.ts files for each of css files in your codebase

  3. Typescript compiler will understand that css import will be module with properties (class names) of type string.

Mentioned typings-for-css-loader contains a bug and because of types file generation delay, it's best to declare global *.css type in case our *.css.d.ts file is not generated yet.

That little bug scenario:

  1. Create css file component.css
  2. Include it in component import * as css from './component.css'
  3. Run webpack
  4. Typescript compiler will try to compile code (ERROR)
  5. Loader will generate Css modules typings file (component.css.d.ts), but it's late for typescript compiler to find new typings file
  6. Run webpack again will fix build error.

Easy fix is to create global definition (eg. file called typings.d.ts in your source root) for importing CSS Modules:

declare module '*.css' {
  interface IClassNames {
    [className: string]: string
  }
  const classNames: IClassNames;
  export = classNames;
}

This definition will be used if there is no css file generated (eg. you have added new css file). Otherwise will be used generated specific (needs to be in same folder and named same as source file + .d.ts extension), eg. component.css.d.ts definition and IntelliSense will work perfectly.

Example of component.css.d.ts:

export const wrapper: string;
export const button: string;
export const link: string;

And if you don't want to see generated css typings you may setup filter in IDE to hide all files with extension .css.d.ts in your sources.

Comments

37

I have added a file named Global.d.ts or typings.d.ts to my ./src folder with some type definitions:

declare module "*.module.css";

Webpack css config:

{
  test: /\.css$/,
  use: [
    isProductionBuild ? MiniCssExtractPlugin.loader : "style-loader",
    {
      loader: "css-loader",
      options: {
        modules: true,
        importLoaders: 1,
        localIdentName: "[name]_[local]_[hash:base64]",
        sourceMap: true,
        minimize: true
      }
    }
  ]
},

Then I simply import the module like: import styles from "./App.module.css";

1 Comment

Just to add to this - if you're using create-react-app with the --typescript flag, their webpack configuration expects CSS modules to be named *.module.css. So all you need to do to make the "import a from b" syntax work is rename all .css files to File.module.css and import them like import styles from "./File.module.css".
29

In 2022, all I needed to do it to add Globals.d.ts file under the src folder
with

declare module "*.module.css";
declare module "*.module.scss";

Then I can import CSS modules in my typescript files as usual for css or scss files:

import styles from "./Team.module.scss";

1 Comment

Error: not found module
7

"css-loader": "^7.1.1" has a bug!?!?!

1. if after installing css-loader

2. added a flag

  {
      test: /\.css$/i,
      loader: "css-loader",
      options: {
       modules: true,
      },
    }
  1. created a file global.d.ts

  2. made an entry declare module "*.module.css";

but this entry doesn't work

import s from "./Main.module.css";

and this is the only one that works

import * as s from "./Main.module.css";

in my case, installing css-loader of an earlier version helped, for example npm i -D [email protected]

Edited

 {
        loader: "css-loader",
        options: {
          modules: {
            ...,
            namedExport: false,
            exportLocalsConvention: 'as-is',
            ...
          },
        },
      }

2 Comments

Thank you so much for posting this, @LibertyCode. This was driving me nuts for the last few hours! Looks like this behavior was changed in v7.1.1 (see here). I downgraded to v7.1.0 and it works with import styles from ... again (see here). 🎉
This got me as well, but this is not a bug, but a breaking change mentioned in their release docs: github.com/webpack-contrib/css-loader/blob/master/…
6

This is the easiest way in 2024:

  1. Create the file globals.d.ts file under the src
  2. Put this code there
declare module '*.scss'
  1. Now you can import like this
import styles from './App.module.scss'
  1. Then install and add in your package.json
npm i mini-css-extract-plugin
  1. Then add in webpack.config.ts
   {
      test: /\.(sa|sc|c)ss$/i,
      use: [
        devMode 
        ? "style-loader" 
        : MiniCssExtractPlugin.loader,
        "css-loader",
        options: {
                  loader: 'css-loader',
                  modules: true,
        },
        "sass-loader",
      ],
    }

Comments

4

I use create-react-app, but for some reason other solutions didn't work, something was missing; I think it's because VS Code uses its own TypeScript vesrion. So here is all I did until it finally worked; if you use create-react-app & VS Code it should work for you as well:

  1. run npm i typescript-plugin-css-modules --save-dev
  2. in the project's root folder, create a file named styles.d.ts (or something like this) and paste the following content:

styles.d.ts

// if you use css
declare module "*.module.css" {
  const classes: { [key: string]: string };
  export default classes;
}

// if you use scss
declare module "*.module.scss" {
  const classes: { [key: string]: string };
  export default classes;
}

// if you use less
declare module "*.module.less" {
  const classes: { [key: string]: string };
  export default classes;
}
  1. in .vscode/settings.json (create the file if you don't already have it) - add the below fields:

.vscode/settings.json

"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true

So, the file might look like so:

.vscode/settings.json

{
   "typescript.tsdk": "node_modules/typescript/lib",
   "typescript.enablePromptUseWorkspaceTsdk": true
}

1 Comment

For some reason I only needed to install the plugin, add it in tsconfig.json. Didn't need to create a .d.ts file or set typescript.enablePromptUseWorkspaceTsdk. Setting typescript.tsdk in VS Code Settings was what finally got it working for me!
3

Or import using require webpack function

This is just what I used to do and still have that code in a few of my projects out there.


Now : I wrote typestyle : http://typestyle.github.io/#/ but you don't have to use it. Just stick with const styles = require('./styles.css') if it makes you happy. FWIW you can also use raw css with typestyle if you want http://typestyle.github.io/#/raw

Comments

2

I think now typescript can import css file by simply doing import 'fileTobeImportedPath.css'

Comments

2

This case is related to Typescript. You can add typings.d.ts in your project with this content:

declare module "*.module.css";
declare module "*.module.scss";

It is good practice to use file name with *.module.* format if you want to enable CSS Module.

css-loader will enable CSS Module automatically for files with name that satisfy this RegEx: /\.module\.\w+$/i. This feature is customable by setting options.modules property as an object.

For example:

import style from './App.module.css'; // CSS Module enabled
import './index.css'; // Plain CSS loaded

For recent configuration, you can add this rule to webpack.config.js:

{
  test: /\.css$/,
  use: [
    'style-loader',
    {
      loader: "css-loader",
      options: {
        modules: {
          localIdentName: "[hash:base64]", // default
          auto: true // default
        },
        sourceMap: true
      }
    },
  ]
},

A custom configuration example:

{
  loader: "css-loader",
  options: {
    modules: {
      localIdentName: "[name]_[local]_[hash:base64]", // custom class name format
      auto: /\.cmod\.\w+$/i, // custom auto enable CSS Module for "*.cmod.css" format
    },
  }
},

Complete documentation is HERE.

2 Comments

Is it good practice to use .module.css or is it required? Because if it's not required then it's not good practice to make people use that rediculous naming convention.
It's already commonly used by popular libraries such as CSS Loader or Angular to detect files that apply CSS modules. So, it is good practice to align with communities. The name is also acceptable and easily differentiated.
0

npm install --save-dev style-loader css-loader css-modules-dts-loader

and

const isNamedExport = true;
// webpack.config.js
module.exports = {
  // ... other webpack config settings
   module: {
      rules: [
         {
         test: /\.module\.(css|postcss|pcss|scss|sass|less|styl|sss)$/i,
         use: [
               "style-loader",
               {
                  loader: "css-modules-dts-loader",
                  options: {
                     // Convert CSS class names to camelCase (default: false)
                     camelCase: true,
                     // Quote style: "single" or "double" (default: "double")
                     quote: "single",
                     // Indentation style: "tab" or "space" (default: "space")
                     indentStyle: "space",
                     // Number of spaces if indentStyle is "space" (default: 2)
                     indentSize: 2,
                     // Mode: "emit" to generate or "verify" to check the file (default: "emit")
                     mode: isProduction ? "verify" : "emit",
                     // Sort the exported class names alphabetically (default: false)
                     sort: true,
                     // Use named exports instead of interface (default: true)
                     namedExport: isNamedExport,
                     // Custom banner comment at the top of the file
                     banner: "// This file is automatically generated.\n// Please do not change this file!"
                  }
               },
               {
                  loader: "css-loader",
                  options: {
                     sourceMap: true,
                     modules: {
                        namedExport: isNamedExport,
                        exportLocalsConvention: "as-is",
                        localIdentName: "[name]__[local]___[hash:base64]"
                     },
                     importLoaders: 1
                  }
               }
            ]
         }
      ]
   }
};

and import styles by the next way

if isNamedExport is true

import * as styles from "$componentName.module.css"

if isNamedExport is false

import styles from "$componentName.module.css"

Comments

-12

let styles = require("./styles.css"); is the best approch because it is es5 version of javascript which is compatible with every feature of react.import * as styles from './styles.css' is es6 version of javascript.

2 Comments

its neither es5 nor es6, it is typescript and as such will be transpiled.
I agree with @Meirion Hughes. thanks to all for the responses !

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.