How to configure CSS and CSS modules in webpack
One of the first thing you need to configure in your webpack project is CSS. CSS is so fundamental to a web app — almost all web apps needs it. But configuring webpack is tricky. And if you want CSS modules and CSS it can be even more confusing. In this article, I sort it all out for you.
You’ll learn the following in this post
- How to configure basic CSS in a webpack project using style-loader and css-loader
- How to extract the CSS to its own style.css file
- How to configure CSS modules with webpack
- How to configure both CSS and CSS modules
How to configure CSS with webpack
Let’s start configuring CSS. I assume you already have a webpack project set up. If you don’t, check out createapp.dev to create your own custom webpack boilerplate.
Create the CSS file and reference it
Before we configure CSS support in the webpack setup, let’s first add a CSS file and use it.
The first thing we are going to do is to add a styles.css file in the project. Let’s put it in the src folder.
body color: white; background-color: black; >
This CSS file creates a black background and white text color. This makes it clear to see if it works or not — if it works the whole page will be black!
Next thing to do is to import it. We’ll do this from a JavaScript file because by default webpack puts the CSS inside the bundle which means we have to reference it from another JavaScript file (we’ll look into how to extract the CSS to its own file later in this tutorial).
Put this in your index.js file:
Configure webpack css-loader and style-loader
To be able to use CSS in your webpack app, you need to set up a new loader. Out-of-the-box, webpack only understands Javascript and JSON. With a loader, you can translate another type of file to a format that webpack understands and can work with.
There are many webpack loaders and each loader has a specific purpose. You need two loaders to support CSS in your app: css-loader and style-loader . Let’s look at how we can configure css-loader and style-loader in webpack.
You set up a loader with the module keyword in your webpack.config.js .
This is how you configure CSS in your webpack.config.js :
module: rules: [ test: /\.css$/, use: [ 'style-loader', 'css-loader' ] > ] >
The test keyword tells webpack what kind of files should use this loader. The use keyword tells webpack which loaders should be run for these files.
As you can see in the config, you need to use two loaders, style-loader and css-loader . You also need to install them as NPM dependencies:
npm install --save-dev style-loader css-loader
What does css-loader and style-loader do?
Loaders are just pure JavaScript functions: they take some data as input and do something to that data and returns a transformed version of the data. When you use two loaders in webpack then it takes the output of the first and sends it as input to the second. In our example it takes the CSS file and runs it through css-loader then it takes the output and runs it as input to the style-loader
Let’s look at what the loaders do.
css-loader reads the CSS from the CSS file and returns the CSS with the import and url(. ) resolved correctly. What does that mean? Let’s look at an example. Let’s say you have a url in the CSS referencing another resource, like an image:
.topbanner background: url("topbanner.svg") #00d no-repeat fixed; >
In this example, we reference topbanner.svg from the CSS. When css-loader sees this line, it tells webpack to load this file using an appropriate loader. For this to work you also need to configure a SVG loader, because the file is a SVG file:
module: rules: [ // CSS loader here test: /\.svg$/, use: "file-loader", >, ] >
If you wouldn’t have configured css-loader you would get an cryptic error like this:
ERROR in ./src/styles.css 1:0 Module parse failed: Unexpected token (1:0) You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders > .topbanner < | background: url("zip.svg") #00D no-repeat fixed; | >@ ./src/styles.css 1:14-39 @ ./src/index.js
You would get this error even though you had configured a loader for SVG files. Avoid weird erros like that by always using css-loader for your CSS configuration.
The next loader you configured was the style-loader . This loader adds the CSS to the DOM so that the styles are active and visible on the page. This is needed because the CSS is put into the bundle.js file — there is no separate styles.css file.
How to extract the CSS to its own styles.css file
It’s possible to output a separate styles.css file by using the mini-css-extract-plugin instead of using style-loader like we previously did.
The first thing you need to do is to install the dependency
npm install --save-dev mini-css-extract-plugin
Then you need to import the plugin at the top of the webpack.config.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
Next thing is to enable the plugin in the plugin section of your webpack.config.js file:
plugins: [ new MiniCssExtractPlugin(), ],
And last but not least, you replace the style-loader with MiniCssExtractPlugin.loader :
test: /\.css$/, use: [ MiniCssExtractPlugin.loader, // instead of style-loader 'css-loader' ] >
Now when you run webpack, it will output main.css file in the dist folder that you can reference from your index.html file. (remove the import «./styles.css»; line from index.js if you added that)
How to configure CSS modules in webpack
Now that you know how to configure pure CSS, let’s look into how to configure CSS modules in webpack.
But first what is CSS modules? From the github repo:
«A CSS Module is a CSS file in which all class names and animation names are scoped locally by default.»
CSS is global — the classes you define can be used anywhere. But CSS modules are scoped to the component where they are used.
CSS-tricks.com has a good introduction to CSS Modules that is useful if you want to learn more. In this post, we’ll be focusing on learning how to configure it.
Configuring CSS modules is quite similar to configuring CSS.
test: /\.css$/, use: [ 'style-loader', loader: 'css-loader', options: importLoaders: 1, modules: true > > ] >
you still use the css-loader and the style-loader . No extra dependencies are needed. But the difference to configuring CSS is that you add two options to css-loader .
It’s the modules: true that tells css-loader to enable CSS modules.
importLoaders: 1 means that it also applies CSS modules on @import ed resources.
Using both CSS Modules and global CSS at the same time
So the way to enable CSS modules is to pass some options to the css-loader . But what if we want to use both CSS and CSS modules. Let’s say you have a layout.css file which is truely global CSS. Or you use a third-party component that is dependant on global CSS. It’s possible to do it!
We need a convention to use for deciding which files are global CSS and which are CSS modules. The way I like to do it is to use CSS modules only for files ending in *.module.css , for example modal.module.css , and all other *.css files are global.
To configure this we’ll add two loaders to our webpack config: one for CSS and one for CSS modules and we’ll use the include and exclude keywords for separating the two. include and exclude takes a regexp that decided if the file should be used or not. The complete setup looks like this:
module: rules: [ test: /\.css$/, use: [ "style-loader", loader: "css-loader", options: importLoaders: 1, modules: true, >, >, ], include: /\.module\.css$/, >, test: /\.css$/, use: ["style-loader", "css-loader"], exclude: /\.module\.css$/, >, ] >
Now you can use both CSS and CSS modules in your project!
Follow me on Twitter to get real time updates with tips, insights, and things I build in the frontend ecosystem.
Loaders
Loaders are transformations that are applied to the source code of a module. They allow you to pre-process files as you import or “load” them. Thus, loaders are kind of like “tasks” in other build tools and provide a powerful way to handle front-end build steps. Loaders can transform files from a different language (like TypeScript) to JavaScript or load inline images as data URLs. Loaders even allow you to do things like import CSS files directly from your JavaScript modules!
Example
For example, you can use loaders to tell webpack to load a CSS file or to convert TypeScript to JavaScript. To do this, you would start by installing the loaders you need:
npm install --save-dev css-loader ts-loader
And then instruct webpack to use the css-loader for every .css file and the ts-loader for all .ts files:
webpack.config.js
module.exports = module: rules: [ test: /\.css$/, use: 'css-loader' >, test: /\.ts$/, use: 'ts-loader' >, ], >, >;
Using Loaders
There are two ways to use loaders in your application:
- Configuration (recommended): Specify them in your webpack.config.js file.
- Inline: Specify them explicitly in each import statement.
Note that loaders can be used from CLI under webpack v4, but the feature was deprecated in webpack v5.
Configuration
module.rules allows you to specify several loaders within your webpack configuration. This is a concise way to display loaders, and helps to maintain clean code. It also offers you a full overview of each respective loader.
Loaders are evaluated/executed from right to left (or from bottom to top). In the example below execution starts with sass-loader, continues with css-loader and finally ends with style-loader. See «Loader Features» for more information about loaders order.
module.exports = module: rules: [ test: /\.css$/, use: [ loader: 'style-loader' >, loader: 'css-loader', options: modules: true, >, >, loader: 'sass-loader' >, ], >, ], >, >;
Inline
It’s possible to specify loaders in an import statement, or any equivalent «importing» method. Separate loaders from the resource with ! . Each part is resolved relative to the current directory.
import Styles from 'style-loader!css-loader?modules!./styles.css';
It’s possible to override any loaders, preLoaders and postLoaders from the configuration by prefixing the inline import statement:
- Prefixing with ! will disable all configured normal loaders
import Styles from '!style-loader!css-loader?modules!./styles.css';
import Styles from '!!style-loader!css-loader?modules!./styles.css';
import Styles from '-!style-loader!css-loader?modules!./styles.css';
Options can be passed with a query parameter, e.g. ?key=value&foo=bar , or a JSON object, e.g. ? .
tip
Use module.rules whenever possible, as this will reduce boilerplate in your source code and allow you to debug or locate a loader faster if something goes south.
Loader Features
- Loaders can be chained. Each loader in the chain applies transformations to the processed resource. A chain is executed in reverse order. The first loader passes its result (resource with applied transformations) to the next one, and so forth. Finally, webpack expects JavaScript to be returned by the last loader in the chain.
- Loaders can be synchronous or asynchronous.
- Loaders run in Node.js and can do everything that’s possible there.
- Loaders can be configured with an options object (using query parameters to set options is still supported but has been deprecated).
- Normal modules can export a loader in addition to the normal main via package.json with the loader field.
- Plugins can give loaders more features.
- Loaders can emit additional arbitrary files.
Loaders provide a way to customize the output through their preprocessing functions. Users now have more flexibility to include fine-grained logic such as compression, packaging, language translations and more.
Resolving Loaders
Loaders follow the standard module resolution. In most cases it will be loaded from the module path (think npm install , node_modules ).
A loader module is expected to export a function and be written in Node.js compatible JavaScript. They are most commonly managed with npm, but you can also have custom loaders as files within your application. By convention, loaders are usually named xxx-loader (e.g. json-loader ). See «Writing a Loader» for more information.