Use webpack to bundle styling files

As a popular bundling tool, webpack has a lot of configurations to learn. It's impossible to know everything about the loaders/plugins and their configurations, but we need to know some general configs for normal files, like .css or .scss.

1. Clone repo and init

First, you need clone the demo project for this article from git, and run npm i in the root directory. This will install necessary packages: webpack, webpack-cli, file-loader and url-loader.

Let's have a look this project from top to bottom:

dist folder: this is the bundle output place, right now there is only one index.html inside.

Dive in this index.html, you can see a <div> with root id and a ./bundle.js imported.

src/index.js: the logic inside is pretty straight forward. Just import an image named avatar.jpg and append it into the <div> with root id in dist/index.html.

webpack.config.js: this file is the configuration file for webpack and uses commonJS module management convention to export configuration object. The naming webpack.config.js is the default value. You can change it to any other names, but then when you run weboack you need to add a suffix: --config <your-config-file-name>.

In this project, I will use an image to display how to add loaders for .css and .sass. That means I need use a appropriate loader to handle .jpg file. I use url-loader here.

url-loader: this loader will encode image file content to base64 url and insert it to the src of <img>. However, this will cause a unfriendly UX when the image is too big. I used limit to handle this case: if the size is bigger than 10240 bytes, url-loader will handle the image like file-loader, which moves the image file to output folder directly.

Right now you can in the cli to run npm run bundle, then image and bundle.js will appear in the distfolder. You can open index.html in browser, and you should see the avatar image there.

2. Bundle .css and .scss file

First, create index.css file under src folder, and write some styling code:

.avatar {
  width: 150px;
  height: 150px;
}

Now, we have the .css file, we need to use it in index.js. Import the css file and add a class name to the image DOM:

...
import './index.css';
...
img.classList.add('avatar');
...

The logic part is already finished. We need tell webpack how to bundle .css file. Install two loaders: npm i style-loader css-loader -D and add a new rule in weboack.config.js:

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

The rule is pretty simple: if the file has .css extension, webpack will use css-loader to handle the importing relationship between all .css files first, then use style-loader to insert these styling code to <style> tag of index.html.

When use multiple loaders in one rule, webpack will use them from bottom to top, from right to left.

You can run npm run bundle and check index.html in browser right now. The image should be smaller and inspect the html in dev tool of browser, the css styling code should be placed in a <style> in <head>.

Next, we can just change a little bit and bundle the .scss file.

Modify the index.css to index.scss, then replace the code with following content:

body {
  .avatar {
    width: 150px;
    height: 150px;
  }
}

Don't forget to change importing index.css to index.scss in index.js top.

Then install sass-laoder : npm i sass-loader node-sass -D. Modify the rule in webpack.config.js like this:

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

You can run bundle command and test index.html in your browser.

3. PostCSS and Autoprefixer

Sometimes we need add the prefix for different browser engines, like -webkit-, -ms- and -o-, for some css3 properties. We can use postcss-loader and its plugin Autoprefixer to ignore this kind of inconvenience.

PostCSS plugin to parse CSS and add vendor prefixes to CSS rules using values from Can I Use. It is recommended by Google and used in Twitter and Alibaba.

First, install npm i postcss-loader autoprefixer -D. Then add postcss-loader in .scss rule of webpack.config.js.

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

Add a config file postcss.config.js in root directory, and write following code:

module.exports = {
  plugins: [
  	require('autoprefixer')
  ]
}

Add a css3 property for .avatar in index.scss:

transform: rotate(20deg);

Don't forget tell Autoprefixer what the target browsers of this project, add this in your package.json file:

"browserslist": [
   "last 2 versions"
]

At last, run npm run bundle and inspect in browser, you will find for .avatar image, the styling will be like this:

width: 150px;
height: 150px;
-webkit-transform: rotate(20deg);
transform: rotate(20deg);

4. CSS module

Let's create a new file createAvatar.js in src folder, then copy exactly same code from index.js. Paste it into a function ``createAvatar` and export this function:

import avatar from './avatar.jpg';

export default () => {
  var img = new Image();
  img.src = avatar;
  img.classList.add('avatar');

  var root = document.getElementById('root');
  root.append(img);
};

Then we import and call this function in index.js:

import avatar from './avatar.jpg';
import './index.scss';
import createAvatar from './createAvatar';

createAvatar();

var img = new Image();
img.src = avatar;
img.classList.add('avatar');

var root = document.getElementById('root');
root.append(img);

Let's bundle and have a look at the index.html in browser.

Of course you will get 2 exactly same images.

Find any issue with this? You imported createAvatar in index.js and only import index.scss once in index.js but the styling will apply to global! This is not good as when you modify the style of one module, may modify other modules as well.

That's why we need css module.

With webpack, it's really easy to implement css module.

First, add an option for css-loader:

{
  test: /\.scss$/,
  use: [
     'style-loader',
     {
       loader: 'css-loader',
       options: {
           modules: true,
       },
      },
      'postcss-loader',
      'sass-loader',
  ],
}

Then, modify the importing and logic code in index.js like this:

...
import style from './index.scss';
...
img.classList.add(style.avatar);

Re-bundle and you will see the first image has no any style but the second one gets styles as we expected.