Carl Rippon

Building SPAs

Carl Rippon
BlogBooks / CoursesAbout
This site uses cookies. Click here to find out more

Using CSS in React and TypeScript with Webpack 5

January 27, 2021
reacttypescript

In the last post, we created a React app that used TypeScript and ESLint with Webpack. In this post, we will extend this Webpack configuration so that the app can use CSS.

Referencing an image in the app

Let’s start by trying to reference a CSS class in our App component:

const App = () => <h1 className="app-heading">My React and TypeScript App!</h1>;

We’ll create a CSS file called app.css to hold this CSS class definition:

.app-heading {
  color: red;
}

Let’s start our app in dev mode by running npm start in a terminal.

Of course, this doesn’t work - how does the App component know where the app-heading is defined? We can use an import statement as follows to do this:

import "./app.css"

Webpack gives a “Module parse failed: Unexpected token” error because it doesn’t understand imports for CSS files yet:

CSS import error

Configuring CSS for development

We need to tell Webpack how to handle CSS files. We need to add a couple of loaders as follows in the dev Webpack config (webpack.dev.config in our example):

const config: webpack.Configuration = {
  ...
  module: {
    rules: [
      ...,
      {        test: /\.css$/i,        use: ["style-loader", "css-loader"],      },    ],
  },
  ...
};

So, when Webpack comes across a .css file, it handles it with css-loader and style-loader (loaders in the use array are executed in the reverse order 🤔).

css-loader reads the referenced CSS file in the import statement (app.css in our example). style-loader then put this CSS content into a style element in the bundled html file. While the style element isn’t ideal in production, it is nice in development because the Webpack dev server can make changes to the style element quickly.

For our new Webpack configuration to work, we need to install both the css-loader and style-loader packages as follows:

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

We will need to stop and restart the app to see this in action:

CSS working

Configuring CSS for production

In production, we want the CSS to be in an external CSS file so that our app can take advantage of browser caching. We can do this by using mini-css-extract-plugin instead of style-loader.

Let’s start by installing mini-css-extract-plugin with its TypeScript types:

npm install --save-dev mini-css-extract-plugin @types/mini-css-extract-plugin

We can now add this loader to our production webpack config (webpack.prod.config in our example):

...
import MiniCssExtractPlugin from "mini-css-extract-plugin";
const config: webpack.Configuration = {
  ...,
  module: {
    rules: [
      ...,
      {        test: /\.css$/i,        use: [MiniCssExtractPlugin.loader, "css-loader"],      },    ],
  },
  ...,
  plugins: [
    ...,
    new MiniCssExtractPlugin({      filename: "[name].[contenthash].css",    }),  ],
  ...
};

mini-css-extract-plugin needs to do a little more work than loading content into the existing bundles - it needs to create additional files (.css files). This is why it is a plugin.

We have used the [name] token in the CSS file name to allow Webpack to name the files if our app is code split. We have used the [contenthash] token to change the bundle file name when its content changes, which will bust the browser cache.

Let’s do a build:

npm run build

We’ll see a CSS file generated in the build folder:

CSS bundle

If we look in the HTML file, we’ll see the external CSS file referenced:

CSS reference

Nice. 🙂

Styles from multiple components

Let’s restructure our App component so that it references two child components, which in tern reference different CSS files:

import "./heading.css";
import "./content.css";

const App = () => (
  <>
    <Heading />
    <Content />
  </>
);
const Heading = () => <h1 className="heading">My React and TypeScript App</h1>;

const Content = () => <div className="content">With CSS!</div>;

Our CSS files are as follows:

/* heading.css */
.heading {
  color: red;
}
/* content.css */
.content {
  color: green;
}

If we run the app in dev mode (npm start), we will see the components are styled as we expect by two style elements on the HTML page:

Multiple components in dev

If we do a build (npm run build), we will see that the CSS is bundled into a single CSS file:

Multiple components in prod

Excellent! 🙂

Using CSS modules

The problem with this approach is that the developers need to carefully name the CSS classes so that they don’t clash. For example, if we have called both of our CSS classes “text”, what would the heading and content color be?

CSS clash

The second CSS class takes precedence over the first, which results in both pieces of text being green.

CSS modules solve this problem by allowing us to scope CSS to a particular component. Let’s adjust our components to reference CSS modules:

import heading from "./heading.module.css";
import content from "./content.module.css";
...
const Heading = () => (
  <h1 className={heading.heading}>My React and TypeScript App</h1>
);
const Content = () => <div className={content.content}>With CSS!</div>;

The import statement is slightly different. We are referencing a file with a .module.css suffix and importing a variable from the file which will contain all the CSS class names. Our components reference the class name variables - we will see why this is the case when we see the output.

We will need to rename content.css to content.module.css and heading.css to heading.module.css.

TypeScript raises a “Cannot find module ‘.module.css’ or its corresponding type declaration” error because it doesn’t know how to handle CSS modules:

CSS module error

To resolve this error, we can add the following content into a file called custom.d.ts in the src folder:

declare module "*.module.css";

This resolves the TypeScript error.

If we restart the app in dev mode, we see the styles are applied as we expected:

CSS modules in dev

We see that the CSS class names have been given a random-looking name. This ensures that styles in different components don’t clash.

If we remember back to how we referenced the class name in a component, it was a little strange:

className={fileName.cssClassName}

This allows the Webpack process to give the class name a unique name.

If we produce a production build (npm run build), we get unique class names in the production CSS file too:

CSS modules in production

Nice! 🙂

That’s it! Our React and TypeScript project is now set up to use CSS.

This code is available in GitHub at https://github.com/carlrip/react-typescript-css-webpack.

Did you find this post useful?

Let me know by sharing it on Twitter.
Click here to share this post on Twitter

If you to learn more about using TypeScript with React, you may find my course useful:

Using TypeScript with React

Find out more