DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 967,611 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Maximo Martinez Soria
Maximo Martinez Soria

Posted on • Updated on

Let's create a Webpack configuration from scratch

Even though It's not that difficult, webpack could be so intimidating.

We just need to understand a couple of things.

At the end of the post you will be able to fully understand how to write a webpack.config.js file.

This is the full repo: https://github.com/maximomartinezsoria/webpack-boilerplate

Feel free to check it out if you feel lost at any point.

Let's get started.

Setup

First of all, we'll need to create a new folder and initialize git and npm.

We'll also create a src folder with a javascript file where our code is going to live.

# create a new folder and move into it
$ mkdir webpack-boilerplate
$ cd webpack-boilerplate

# Initialize git and npm
$ git init
$ npm init

# create src folder, move into and create a file
$ mkdir src
$ cd src
$ touch index.js
Enter fullscreen mode Exit fullscreen mode

Then, we'll install webpack and webpack-cli. The first one, is the core library and the other will help us interact with webpack.

# i: install
# -D: development dependencies
$ npm i -D webpack webpack-cli
Enter fullscreen mode Exit fullscreen mode

Finally, let's create a configuration file at the same level of package.json.

$ touch webpack.config.js
Enter fullscreen mode Exit fullscreen mode

You should finish with somthing like this:

- webpack-boilerplate
  - node_modules
    - ....
  - src
    - index.js
  - package.json
  - package.lock.json
  - webpack.config.js
Enter fullscreen mode Exit fullscreen mode

Getting into Webpack

Let's work a little bit in webpack.config.js file.

// webpack.config.js
const path = require('path')

module.exports = {
    entry: path.resolve(__dirname, 'src/index.js'),
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'js/[name].js',
    },
}
Enter fullscreen mode Exit fullscreen mode

This is a node file, so we are using commonjs instead of es6 import / export syntax.

Entry, is where Webpack is going to look for the code.

From now on, if you want a code to pass through Webpack, you'll need to import it in that file.

CSS, preprocessors, and other sort of files could be imported as well. Just need the proper loader.

Output, is where Webpack is going to drop the resultant files.

Notice that we are using a placeholder. This one will be replaced with the name of the file by Webpack.

Giving it a shot

We need to create some commands in order to start using webpack.

// package.json

{
  ...
    "scripts": {
        "dev": "webpack"
    }
  ...
}
Enter fullscreen mode Exit fullscreen mode

As simple as that.

$ npm run dev
Enter fullscreen mode Exit fullscreen mode

The command will throw you a warning message saying that you should specify the mode option.

Go ahead and add a flag to the command specifying either development or production.

We are going to create specific configurations for both modes later, so it's not important right now.

Loaders and Plugins

Loaders allow us to load and pre-process different kinds of files, and plugins extends their possibilities.

I know that this doesn't make much sense right now, but you'll understand soon. Let's go to the code.

Handle HTML

As you now know, the entry point and main file of the app is index.js.

As you also know, browsers need html files. At least one.

The first thing that we are going to do, is to export a html file.

// webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    entry: path.resolve(__dirname, 'src/index.js'),
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'js/[name].js',
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, 'public/index.html'),
        }),
    ],
}
Enter fullscreen mode Exit fullscreen mode

Remember to install the plugin.

$ npm i -D html-webpack-plugin
Enter fullscreen mode Exit fullscreen mode

Babel for modern JavaScript

Not all people use modern browsers. That's why we need Babel to transpile our modern JavaScript into ES5.

First, we need to install some dependencies:

$ npm i -D babel-loader @babel/core @babel/preset-env
Enter fullscreen mode Exit fullscreen mode

As I told you, loaders allow us to load different kinds of files.

This time we have babel-loader, which loads JavaScript files into @babel-core, which transpiles our modern JavaScript into ES5 using the rules defined in @babel/preset-env.

After that little explanation, let's go to the code.

In order to use a loader, we need to use the key module and set an array of rules inside it.

Each rule has some configurations. In this case we are using the followings:

  • test: tells Webpack which kind of files is this rule for.
  • use: which loader will be applied in this rule.
  • exclude: we are excluding the node_modules folder to improve performance.
// webpack.config.js
module.exports = {
    ...
  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'babel-loader',
        exclude: /node_modules/,
      },
    ]
  },
  plugins: [
    ...
  ],
}
Enter fullscreen mode Exit fullscreen mode

Ok. We are asking Webpack to use babel. Now we need to tell babel what to do.

All babel configurations need to be set in a specific file called .babelrc in the root folder.

In addition to presets, we can use plugins to increase presets functionality.

In this case, lets install @babel-plugin-transform-runtime, which allows us to use async/await.

We are also installing @babel/runtime. It's needed by the plugin and it's highly recommended to install it as a production dependency.

$ npm i -D @babel/plugin-transform-runtime
$ npm i @babel/runtime
Enter fullscreen mode Exit fullscreen mode
// .babelrc
{
    "plugins": [
        "@babel/plugin-transform-runtime"
    ],
    "presets": [
        "@babel/preset-env"
    ]
}
Enter fullscreen mode Exit fullscreen mode

Styles

Every website or app needs styles.

As you already know, loaders allow us to import different kinds of files into webpack.

Does that mean that we can import css into JavaScript? Yes, it does.

It sounds weird and a little bit crazy, but importing css in JS is how we include our styles into the bundle.

$ npm i -D style-loader css-loader
Enter fullscreen mode Exit fullscreen mode

Importing css into JavaScript is actually weird. That's why we need a couple of things.

css-loader is the one who handle the importing, and style-loader injects css in the html file that we've already generated with HtmlWebpackPlugin.

If you want to use a preprocessor, you need one more loader to handle that file and the specific preprocessor library itself.

SASS / SCSS

$ npm i -D sass-loader node-sass
Enter fullscreen mode Exit fullscreen mode

STYLUS

$ npm i -D stylus-loader stylus
Enter fullscreen mode Exit fullscreen mode

LESS

$ npm i -D less-loader less
Enter fullscreen mode Exit fullscreen mode

The code is quite similar than the last time. We are just adding more rules.

// webpack.config.js
...
module: {
    rules: [
        {
          test: /\.css$/,
          use: [
            'style-loader',
            'css-loader',
          ],
        },
        {
          test: /\.scss$/,
          use: [
            'style-loader',
            'css-loader',
            'sass-loader'
          ],
        },
        {
        test: /\.less$/,
        use: [
          'style-loader',
          'css-loader',
          'less-loader'
        ],
      },
      {
        test: /\.styl$/,
        use: [
          'style-loader',
          'css-loader',
          'stylus-loader'
        ],
      },
        ...
    ]
}
...
Enter fullscreen mode Exit fullscreen mode

Let's recap.

First, we've used a specific loader for a preprocessor, which turns the code into css. Then, css-loader handles that code and finally style-loader injects css into html.

Remember to import these files into your main JS file.

// index.js
import './index.css'
import './index.scss'
import './index.less'
import './index.styl'
Enter fullscreen mode Exit fullscreen mode

Images, videos and fonts

In order to use images, videos or fonts in our css, we need to use a specific loader.

In this case, we are using an object in the use key. This allows us to use additional configurations like the output path.

$ npm i -D file-loader
Enter fullscreen mode Exit fullscreen mode
// webpack.config.js
...
module: {
    rules: [
        ...
        {
            // '?' means that 'e' is optional. So we can use jpg or jpeg
      test: /\.jpe?g|png|gif|woff|eot|ttf|svg|mp4|webm$/,
      use: {
        loader: 'file-loader',
        options: {
          outputPath: 'assets/'
        }
      },
    },
        ...
    ]
}
...
Enter fullscreen mode Exit fullscreen mode

Development vs Production

We've already learned a lot about Webpack. We are able to use lots of files as well as modern Javascript and CSS preprocessors.

But the real power of Webpack is to make a better developer experience in development mode and a better user experience in production mode. That's why we are going to make two different configurations from now on.

Development

Let's create a new configuration file for development purposes.

The standard name is usually webpack.dev.config.js, but you can use anyone.

We need all the things that we've done before. So I'm going to rename the file that I was using, as oposed to create a new one.

Since we are working in development mode, let's get Webpack to know that, using the mode key.

//webpack.config.js
module.exports = {
  entry: path.resolve(__dirname, 'src/index.js'),
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'js/[name].js',
  },
  mode: 'development',
    ...
}
Enter fullscreen mode Exit fullscreen mode

It's also time to change our package.json script.

// package.json
...
"scripts": {
  "dev": "webpack-dev-server --config ./webpack.dev.config.js"
},
...
Enter fullscreen mode Exit fullscreen mode

The config option defines the path to the config file.

Let's talk about webpack-dev-server.

Development server

The first and one of the most important things that a developer needs is a local server.

That's what we use webpack-dev-server for.

$ npm i -D webpack-dev-server
Enter fullscreen mode Exit fullscreen mode

We also need to write the configuration for the server.

  //webpack.config.js
module.exports = {
  ...
  mode: 'development',
  devServer: {
    contentBase: path.resolve(__dirname, 'dist'),
    open: true,
  },
    ...
}
Enter fullscreen mode Exit fullscreen mode

contentBase defines the directory where is going to look for the files and open in true, means that when we run the command Webpack is going to open a browser automatically.

From now on, Webpack is going to create a new bundle every time you save a file.

Production

Our development configuration is done.

But for a better user experience we can change some things for production.

As we are going to change lots of things, let's create another file and make a new script. I'll call it webpack.config.js.

// package.json
...
"scripts": {
  "dev": "webpack-dev-server --config ./webpack.dev.config.js",
  "build": "webpack"
},
...
Enter fullscreen mode Exit fullscreen mode

As long as you use the default name (webpack.config.js), config option is not required.

We can reuse some options that we've already written, so my new file it's just a duplication from webpack.dev.config.js.

Some important changes that we need before getting started are:

  • mode: set to production.
  • devServer: remove the entire object. We don't need a local server in production.
  • output: is a good practice to use hash in production files in order to avoid cache problems.
...
entry: path.resolve(__dirname, 'src/index.js'),
output: {
  path: path.resolve(__dirname, 'dist'),
  filename: 'js/[name].[hash].js',
},
mode: 'production',
..
Enter fullscreen mode Exit fullscreen mode

Now we are ready to start.

Extracting styles

The first difference between development and production is how styles are handled.

In development, we inject the css in order to accelerate things. But in production we need a css file. That's why, we are using a new loader that will extract the css into a new file.

$ npm i -D mini-css-extract-plugin
Enter fullscreen mode Exit fullscreen mode

So, is it a plugin or a loader? Actually, both. You'll see.

We need to pass it as a plugin to define both the output filename and the chunkFilename.

The hash placeholder, allows us to avoid cache problems.

We need to pass it as a loader also. And, as you can see, we are not using style-loader any more.

// webpack.config.js
const MiniCSSExtractPlugin = require('mini-css-extract-plugin')
...

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          {
            loader: MiniCSSExtractPlugin.loader
          },
          'css-loader',
        ],
      },
      {
        test: /\.scss$/,
        use: [
          {
            loader: MiniCSSExtractPlugin.loader
          },
          'css-loader',
          'sass-loader'
        ],
      },
      {
        test: /\.less$/,
        use: [
          {
            loader: MiniCSSExtractPlugin.loader
          },
          'css-loader',
          'less-loader'
        ],
      },
      {
        test: /\.styl$/,
        use: [
          {
            loader: MiniCSSExtractPlugin.loader
          },
          'css-loader',
          'stylus-loader'
        ],
      },
      ...
    ]
  },
  plugins: [
    ...
    new MiniCSSExtractPlugin({
      filename: 'css/[name].[hash].css',
      chunkFilename: 'css/[id].[hash].css'
    })
  ],
}
Enter fullscreen mode Exit fullscreen mode

So, let's recall.

We are using css-loader or the specific loaders for preprocessors to allow importing those files into JavaScript. Then, we use MiniCSSExtractPlugin to extract css and create a file with it. Finally, that new file will be linked to the html automatically.

Cleaning

We are almost done. Let's add two more fixes.

The first one is related with hashes. As you know, we are using hashes everywhere to avoid cache problems. But now we have a problem with hashes. It's a bug or a feature?

The problem is that we are creating new files every time we run Webpack, but we are just using the last one. So, if you run a couple of times the command and see the css or js folder, you are going to find lots of garbage files.

It would be great to erase all of those files before each run of Webpack. Let's do it.

$ npm i -D clean-webpack-plugin
Enter fullscreen mode Exit fullscreen mode
// webpack.config.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

...
    plugins: [
        ...
        new CleanWebpackPlugin({
          cleanOnceBeforeBuildPatterns: ['**/*']
      })
    ]
...
Enter fullscreen mode Exit fullscreen mode

That does exactly what we want. It cleans the whole dist folder before each bundle creation.

Note that you can modify the pattern (**/*) to match just the things you want. This might be useful when you are using dll for example.

Minify CSS

The second fix is that css is not being minified.

Minification process removes all unnecessary spaces in the file so it becomes smaller.

$ npm i -D optimize-css-assets-webpack-plugin
Enter fullscreen mode Exit fullscreen mode
// webpack.config.js
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')

...
    output: {
    ...
  },
    optimization: {
    minimizer: [ new OptimizeCSSAssetsPlugin() ]
  },
...
Enter fullscreen mode Exit fullscreen mode

optimization is a new key that we didn't use before. I recommend you to take a look at the docs to know more about this key.

The perfect configuration

since this is a very general configuration, we're done.

But this isn't the perfect Webpack configuration. Actually, that doesn't exists.

Each project has its own configuration so you can do whatever you need.

I'm preparing another example of Webpack configuration for React. Stay tuned.

Top comments (0)

πŸ‘‹ Have You Posted on DEV Yet?

Head over to our Welcome Thread and tell us a bit about yourself!