Loading Styles

Kea - High level abstraction between React and Redux - Interview with Marius Andra

Webpack doesn't handle styling out of the box and you will have to use loaders and plugins to allow loading style files. In this chapter, you will set up CSS with the project and see how it works out with automatic browser refreshing. When you make a change to the CSS webpack doesn't have to force a full refresh. Instead, it can patch the CSS without one.

Loading CSS#

To load CSS, you need to use css-loader and style-loader. css-loader goes through possible @import and url() lookups within the matched files and treats them as a regular ES6 import. If an @import points to an external resource, css-loader skips it as only internal resources get processed further by webpack.

style-loader injects the styling through a style element. The way it does this can be customized. It also implements the Hot Module Replacement interface providing good development experience.

The matched files can be processed through loaders like file-loader or url-loader and these possibilities are discussed in the Loading Assets part of the book.

Since inlining CSS isn't a good idea for production usage, it makes sense to use ExtractTextPlugin to generate a separate CSS file. You will do this in the next chapter.

To get started, invoke

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

Now let's make sure webpack is aware of them. Add a new function at the end of the part definition:

webpack.parts.js

exports.loadCSS = ({ include, exclude } = {}) => ({
  module: {
    rules: [
      {
        test: /\.css$/,
        include,
        exclude,

        use: ['style-loader', 'css-loader'],
      },
    ],
  },
});

You also need to connect the fragment with the main configuration:

webpack.config.js

const commonConfig = merge([
  ...
parts.loadCSS(),
]);

The added configuration means that files ending with .css should invoke the given loaders. test matches against a JavaScript-style regular expression.

Loaders are transformations that are applied to source files, and return the new source and can be chained together like a pipe in Unix. They evaluated from right to left. This means that loaders: ['style-loader', 'css-loader'] can be read as styleLoader(cssLoader(input)).

If you want to disable css-loader url parsing, set url: false. The same idea applies to @import as to disable parsing imports you can set import: false through the loader options.

Setting Up the Initial CSS#

You are missing the CSS still:

app/main.css

body {
  background: cornsilk;
}

Also, you need to make webpack aware of it. Without having an entry pointing to it somehow, webpack is not able to find the file:

app/index.js

import './main.css';
...

Execute npm start now. Browse to http://localhost:8080 if you are using the default port and open up main.css and change the background color to something like lime (background: lime). Develop styles as needed to make it look a nicer.

You continue from here in the next chapter. Before that, though, you'll learn about styling-related techniques.

Hello cornsilk world

Understanding CSS Scoping and CSS Modules#

Perhaps the biggest challenge of CSS is that all rules exist within global scope, meaning that two classes with the same name will collide. The limitation is inherent to the CSS specification but projects have workarounds for the issue. CSS Modules introduces local scope for every module by making every class declared within unique by including a hash in their name that is globally unique to the module.

Webpack's css-loader supports CSS Modules. You can enable it through a loader definition as above while enabling the support:

{
  loader: 'css-loader',
options: { modules: true, },
},

After this change, your class definitions remain local to the files. In case you want global class definitions, you need to wrap them within :global(.redButton) { ... } kind of declarations.

In this case, the import statement gives you the local classes you can then bind to elements. Assume you had CSS as below:

app/main.css

body {
  background: cornsilk;
}

.redButton {
  background: red;
}

You could then bind the resulting class to a component:

app/component.js

import styles from './main.css';

...

// Attach the generated class name
element.className = styles.redButton;

body remains as a global declaration still. It's that redButton that makes the difference. You can build component-specific styles that don't leak elsewhere this way.

CSS Modules provides additional features like composition to make it easier to work with your styles. You can also combine it with other loaders as long as you apply them before css-loader.

CSS Modules behavior can be modified as discussed in the official documentation. You have control over the names it generates for instance.
eslint-plugin-css-modules is handy for tracking CSS Modules related problems.

Using CSS Modules with Third Party Libraries and CSS#

If you are using CSS Modules in your project, you should process normal CSS through a separate loader definition without the modules option of css-loader enabled. Otherwise all classes will be scoped to their module. In the case of third party libraries this is almost certainly not what you want.

You can solve the problem by processing third party CSS differently through an include definition against node_modules. Alternately, you could use a file extension (.mcss) to tell files using CSS Modules apart from the rest and then manage this situation in a loader test.

Loading Less#

Less

Less is a CSS processor packed with functionality. Using Less doesn't take a lot of effort through webpack as less-loader deals with the heavy lifting. You should install less as well given it's a peer dependency of less-loader.

Consider the following minimal setup:

{
  test: /\.less$/,
  use: ['style-loader', 'css-loader', 'less-loader'],
},

The loader supports Less plugins, source maps, and so on. To understand how those work you should check out the project itself.

Loading Sass#

Sass

Sass is a widely used CSS preprocessor. You should use sass-loader with it. Remember to install node-sass to your project as it's a peer dependency.

Webpack doesn't take much configuration:

{
  test: /\.scss$/,
  use: ['style-loader', 'css-loader', 'sass-loader'],
},
If you want more performance, especially during development, check out fast-sass-loader.

Loading Stylus and Yeticss#

Stylus

Stylus is yet another example of a CSS processor. It works well through stylus-loader. yeticss is a pattern library that works well with it.

Consider the following configuration:

{
  ...
  module: {
    rules: [
      {
        test: /\.styl$/,
        use: ['style-loader', 'css-loader', 'stylus-loader'],
      },
    ],
  },
  plugins: [
    new webpack.LoaderOptionsPlugin({
      options: {
        // yeticss
        stylus: {
          use: [require('yeticss')],
        },
      },
    }),
  ],
},

To start using yeticss with Stylus, you must import it to one of your app's .styl files:

@import 'yeticss'

//or
@import 'yeticss/components/type'

PostCSS#

PostCSS

PostCSS allows you to perform transformations over CSS through JavaScript plugins. You can even find plugins that provide you Sass-like features. PostCSS is the equivalent of Babel for styling. postcss-loader allows using it with webpack.

The example below illustrates how to set up autoprefixing using PostCSS. It also sets up precss, a PostCSS plugin that allows you to use Sass-like markup in your CSS. You can mix this technique with other loaders to allow autoprefixing there.

{
  test: /\.css$/,
  use: [
    'style-loader',
    'css-loader',
    {
      loader: 'postcss-loader',
      options: {
        plugins: () => ([
          require('autoprefixer'),
          require('precss'),
        ]),
      },
    },
  ],
},

You have to remember to include autoprefixer and precss to your project for this to work. The technique is discussed in detail in the Autoprefixing chapter.

PostCSS supports postcss.config.js based configuration. It relies on cosmiconfig internally for other formats.

cssnext#

cssnext is a PostCSS plugin that allows to experience the future now with certain restrictions. You can use it through postcss-cssnext and enable it as follows:

{
  loader: 'postcss-loader',
  options: {
    plugins: () => ([
      require('postcss-cssnext')(),
    ]),
  },
},

See the usage documentation for available options.

cssnext includes autoprefixer! You don't have to configure autoprefixing separately for it to work in this case.

Understanding Lookups#

To get most out of css-loader, you should understand how it performs its lookups. Even though css-loader handles relative imports by default, it doesn't touch absolute imports (url("/static/img/demo.png")). If you rely on these kind of imports, you have to copy the files to your project.

copy-webpack-plugin works for this purpose, but you can also copy the files outside of webpack. The benefit of the former approach is that webpack-dev-server can pick that up.

resolve-url-loader comes in handy if you use Sass or Less. It adds support for relative imports to the environments.

Processing css-loader Imports#

If you want to process css-loader imports in a specific way, you should set up importLoaders option to a number that tells the loader how many loaders after the css-loader should be executed against the imports found. If you import other CSS files from your CSS through the @import statement and want to process the imports through specific loaders, this technique is essential.

Consider the following import from a CSS file:

@import "./variables.sass";

To process the Sass file, you would have to write configuration:

{
  test: /\.css$/,
  use: [
    'style-loader',
    {
      loader: 'css-loader',
      options: {
        importLoaders: 1,
      },
    },
    'sass-loader',
  ],
},

If you added more loaders, such as postcss-loader, to the chain, you would have to adjust the importLoaders option accordingly.

Loading from node_modules Directory#

You can load files directly from your node_modules directory. Consider Bootstrap and its usage for example:

@import "~bootstrap/less/bootstrap";

The tilde character (~) tells webpack that it's not a relative import as by default. If tilde is included, it performs a lookup against node_modules (default setting) although this is configurable through the resolve.modules field.

If you are using postcss-loader, you can skip using ~ as discussed in postcss-loader issue tracker. postcss-loader can resolve the imports without a tilde.

Enabling Source Maps#

If you want to enable source maps for CSS, you should enable sourceMap option for css-loader and set output.publicPath to an absolute url pointing to your development server. If you have multiple loaders in a chain, you have to enable source maps separately for each. css-loader issue 29 discusses this problem further.

Converting CSS to Strings#

Especially with Angular 2, it can be convenient if you can get CSS in a string format that can be pushed to components. css-to-string-loader achieves exactly this.

Using Bootstrap#

There are a couple of ways to use Bootstrap through webpack. One option is to point to the npm version and perform loader configuration as above.

The Sass version is another option. In this case, you should set precision option of sass-loader to at least 8. This is a known issue explained at bootstrap-sass.

The third option is to go through bootstrap-loader. It does a lot more but allows customization.

Conclusion#

Webpack can load a variety of style formats. It even supports advanced specifications like CSS Modules. The approaches covered here inline the styling by default.

To recap:

  • css-loader evaluates the @import and url() definitions of your styling. style-loader converts it to JavaScript and implements webpack's Hot Module Replacement interface.
  • css-loader supports the CSS Modules specification. CSS Modules allow you maintain CSS in a local scope by default solving the biggest issue of CSS.
  • Webpack supports a large variety of formats compiling to CSS through loaders. These include Sass, Less, and Stylus.
  • PostCSS allows you to inject functionality to CSS in through its plugin system. cssnext is an example of a collection of plugins for PostCSS that implements future features of CSS.
  • css-loader doesn't touch absolute imports by default. It allows customization of loading behavior through the importLoaders option. You can perform lookups against node_modules by prefixing your imports with a tilde (~) character.
  • To use source maps, you have to enable sourceMap boolean through each style loader you are using except for style-loader. You should also set output.publicPath to an absolute url that points to your development server.
  • Using Bootstrap with webpack requires special care. You can either go through generic loaders or a bootstrap specific loader for more customization options.

Although the loading approach covered here is enough for development purposes, it's not ideal for production as it inlines the styling to the JavaScript bundles. You'll learn to solve this problem in the next chapter by separating CSS from the source.

Previous chapterStyling
Next chapterSeparating CSS

This book is available through Leanpub (digital), Amazon (paperback), and Kindle (digital). By purchasing the book you support the development of further content. A part of profit (~30%) goes to Tobias Koppers, the author of webpack.

Need help?