Javascript has evolved a lot over the years, which is why today tools like Weback and Babel are essential in development. In this article we’ll see how to configure Webpack and Babel with Node.js and why they are so important.
Webpack is a module bundler. It takes modules, whether it’s a custom file we’ve created or something that’s been installed through npm, and converts these modules into static resources. Basically Webpack bundles all modules and dependencies into one file which you can then include in your project.
Babel is a transcompiler that is mainly used to convert ECMAScript 2015+ code into backwards-compatible JavaScript code that can be run by older JavaScript engines. Babel therefore allows us to write modern Javascript code, to then be converted into an older version.
Installation
In order to use Webpack first we need to create the basics of the project, so let’s start by looking at what folders to create.
- Create a folder with any name you like for the project.
- Open the terminal and type the command npm init.
Leave the default configuration and press enter.
Loaders
Loaders are third-party extensions that help Webpack handle various file extensions. For example, there are loaders for CSS, for images or for txt files. The goal of a loader is to transform files into modules. Once the file becomes a module, Webpack can use it as a dependency in your project.
Let’s install necessary loaders.
npm i css-loader --save-dev
npm i sass-loader --save-dev
npm i style-loader --save-dev
Plugins
Plugins are third-party extensions that can alter the way Webpack works. For example, there are plugins to extract HTML, CSS or to set environment variables.
Let’s install plugins that we’ll use in our webpack configuration.
npm i html-webpack-plugin --save-dev
npm i mini-css-extract-plugin --save-dev
Babel Configuration
Now it’s time to configure Babel. The packages we need are:
- babel core (il motore di Babel)
- babel preset-env (per compilare il Javascript Moderno)
- babel-loader (il loader per Webpack)
Now let’s install the dependencies.
npm i @babel/core babel-loader @babel/preset-env --save-dev
Once this is done, create a file called babel.babelrc in the project root, and insert the following code to configure Babel to use the preset-env.
{ "presets": [ "@babel/preset-env" ] }
Webpack Configuration
Our Webpack configuration is split into three files :
- webpack.common.js (global configuration)
- webpack.dev.js (development profile)
- webpack.prod.js (production profile)
We use three files for Webpack configuration because later we’re going to set up some scripts in the package.json.
Create a folder called config in your project root and inside it create the above config files, then install the webpack dependencies.
npm i webpack webpack-dev-server webpack-merge webpack-cli --save-dev
After installing the dependencies put the following code inside the webpack.common.js file
var path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const webpack = require('webpack'); module.exports = { module: { rules: [{ test: /\.js$/, exclude: /node_modules/, use: ['babel-loader'], }, { test: /\.(png|svg|jpg|gif|ico)$/, type: 'asset/resource', generator: { filename: './assets/images/[name][ext]' } } ] }, entry: { 'index': path.resolve(__dirname, '../src/assets/js/index.js') }, output: { filename: "assets/js/[name].bundle.js", path: path.resolve(__dirname, '../dist'), publicPath: '/', clean: true }, plugins: [ new webpack.ProvidePlugin({ $: "jquery", jQuery: "jquery" }), new HtmlWebpackPlugin({ template: 'src/views/index.html', inject: true, chunks: ['index'], filename: 'index.html' }) ] };
Let’s see in detail the meaning of all these properties.
Entry: It is the entry point where webpack will start building. We name it index so that we can use it later.
Output: We define the path and filename where Webpack will produce the output. Through the filename property we define the name of the bundle, with path where it will have to be positioned and finally with publicPath we tell the webpack-dev-server or the express server to serve this bundle from the specified virtual location.
Rules: They are a set of rules where we can make sure that for example every time Webpack finds a *.js file it uses babel-loader to convert it.
Plugins:We set the plugins that we will use, in our case ProvidePlugin which supplies us with Jquery externally, and finally HtmlWebpackPlugin to inject the bundles in the same file.
Now we need to create the production profile. Insert the following code inside the webpack.prod.js file.
const { merge } = require('webpack-merge'); const common = require('./webpack.common.js'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); module.exports = merge(common, { mode: 'production', module: { rules: [ { test: /\.(scss|css)$/, use: [ MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader', ], } ], }, plugins: [ new MiniCssExtractPlugin({ filename: 'assets/css/[name].bundle.css', }), ] });
With Webpack merge we have the possibility to merge the two configurations that we have done separately, which turns out to be very useful for separating files and having a cleaner project.
We set up a rule to check if scss or css files are present and respectively load the proper loaders. Finally we use the MiniCssExtractPlugin plugin to extract the css.
Now we need to create the development profile. In particular we need to configure the webpack dev server. The problem with Webpack is that for each change we have to recompile all the code, so if for example you change a js file you will need to run the command to recompile the bundle.
To solve this problem, the Webpack Dev Server helps us, which if properly configured will automatically recompile all the files when a change occurs.
Put the following code in the webpack.dev.js file.
const { merge } = require('webpack-merge'); const common = require('./webpack.common.js'); const path = require('path'); module.exports = merge(common, { mode: 'development', devtool: 'eval-source-map', devServer: { static: [{ directory: path.join(__dirname, '../src/assets/js'), watch: true, }, { directory: path.join(__dirname, '../src/assets/css'), watch: true, }, { directory: path.join(__dirname, '../src/views'), watch: true, }], compress: true, port: 3001, proxy: { '/message': { target: 'http://localhost:3000', secure: false, changeOrigin: true } } }, module: { rules: [ { test: /\.(scss|css)$/, use: [ 'style-loader', 'css-loader', 'sass-loader' ], }, ], } });
Let’s see in detail the meaning of these new properties.
port: The port on which the server will listen.
static: Tells the server where to serve the content from.
watch: If enabled, file changes will trigger a full page reload.
proxy: We define a url to proxy.
Express Configuration
It’s time to install and configure Express.
npm i express
Create a file called index.js in your project root and put the following code in it.
const express = require('express') const app = express() const port = 3000 app.use(express.static(__dirname + '/dist')) app.get('/message', (req, res) => { res.send({ message: "Hello World" }) }) app.listen(port, () => { console.log(`Webpack tutorial app listening at http://localhost:${port}`) })
In the code above, we start the server on port 3000, define the folder where the static files are located, and finally create an endpoint that returns a json with a Hello World message.
Static Files
Before creating our static resources, let’s install jQuery.
npm i jquery
At this point, create a file called index.css inside the src/assets/css folder and insert the following code into it.
.hello-title { color: #4c4cff; }
Create a file called index.js inside the src/assets/js folder and put the following code in it.
import '../css/index.css' $.get(`${window.location.origin}/message`, function (data) { $('.hello-title').text(data.message); });
Finally create a file called index.html inside the src/views folder and put the following code in it.
<html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Webpack Tutorial</title> </head> <body> <h1>Webpack Tutorial App</h1> <h2 class="hello-title"></h2> </body> </html>
Scripts
Now let’s add these custom scripts in the package.json file.
Running npm run dev will start the webpack dev server, while npm run build will compile all the files we specified in the configuration and put the bundles inside the dist folder.
"scripts": { "dev": "webpack serve --config ./config/webpack.dev.js", "build": "webpack --config ./config/webpack.prod.js" }
Application Testing
Now, we just have to try the application.
- Open the terminal
- Type the command npm run build
- Type the command node index.js
- Open the browser and type the following url: http://localhost:3000
Webpack compiled all the files for us successfully, and now inside the dist folder we have our bundles.
If, on the other hand, we want to make changes and start the webpack dev server, just type the command npm run dev, and type the following url: http://localhost:3001.
If you try to modify the index.css file inside the src/assets/css folder, for example by changing the color of the hello-title class you will see that the page will reload by itself.
Even our development server works perfectly, if we make changes to the files that we have configured, the files will be recompiled and the page will be updated automatically.
Although the ports are different, the http call to get the message to our Express server works the same because we set the proxy previously.
Conclusion
In this article we have seen how to configure Webpack and Babel with Node.js, and how powerful all these tools are together. A huge benefit of using Webpack is its customization and features like Hot Module Reloading, however we have only discussed a few ways in which Webpack can be configured as clearly there is so much more to it.
As always the source code is available on GitHub.