Setting up a minimal Node environment with Webpack and Babel

aurelkurtula

aurel kurtula

Posted on February 15, 2018

Setting up a minimal Node environment with Webpack and Babel

Today I am going to explain how I use babel to quickly enable ES6 when working in node, and how webpack can be used when working with react.

Since this is for node, we would obviously need to have node and npm (or yarn) installed - the installation for those two is beyond the scope of this tutorial.

Next, we should install nodemon and babel-node globally.

npm install -g nodemon babel-node
Enter fullscreen mode Exit fullscreen mode

This means that those two packages are installed on your computer and will work for any future projects and any set up independent on your local computer.

Getting started

As per every node project, the best way to start is by creating a directory and running npm init -y into it from the terminal (-y automatically answers yes to all the questions that you'd otherwise need to answer or manually skip). This would create the package.json file which keeps track of the packages required.

Now create another file, you can do this through the terminal touch .babelrc. This is the babel configuration file. This is where we'll let babel know what we need it to look out for. In it add the following code:

{"presets": ['env']}
Enter fullscreen mode Exit fullscreen mode

Up to the point of writing this tutorial I had used es2015-node5 (which I can't remember why it worked better than es2015) but according to the documentation we just need to use the env preset.

As per the documentation:

babel-preset-env is a new preset, first released over a year ago that replaces many presets that were previously used including: babel-preset-es2015, babel-preset-es2016, babel-preset-es2017, babel-preset-latest [and] other community plugins involving es20xx: babel-preset-node5, babel-preset-es2015-node, etc

With .babelrc configured, we just need to install the babel-preset-env

npm install babel-preset-env --save-dev
Enter fullscreen mode Exit fullscreen mode

Testing what we have so far

To the setup we have so far, let's make a server.js file (it can be called anything you like) and write the boilerplate for an express application

import express from 'express'; 
const app = express();
app.get('/', (req, res) => {
    res.send('Hello World')
})
app.listen(4000, () => {
  console.log('Listening');
});
Enter fullscreen mode Exit fullscreen mode

That's just to test whether the ES6 code will work. With that in place, lets use the two globally installed modules to compile and run the above file:

nodemon --exec babel-node server.js
Enter fullscreen mode Exit fullscreen mode

Running nodemon is like running node but with the first the script reruns when ever we make changes to server.js whereas babel-node compiles the code in server.js based on the settings we specified in .babelrc

Using webpack to configure react

On top of the above setup, we are able to add support for react but this time we need to make use of webpack (and express).

Lets visualise the file structure that our boilerplate is going to end up with

root/
    .babelrc
    package.json
    server.js
    webpack.config.js
    client/
        style/
            style.css
        index.html 
        index.js
Enter fullscreen mode Exit fullscreen mode

We already created the first three files. The client folder is going to have the react project files. A very basic setup would be the following:

In client/index.js lets write the basics of a react app:

import React from 'react';
import ReactDOM from 'react-dom';
import './style/style.css';
const App = () => {
  return <div>Hello World</div>
}
ReactDOM.render(
  <App />,
  document.querySelector('#root')
);
Enter fullscreen mode Exit fullscreen mode

(Remember you'd need to install the react and react-dom packages)

In client/index.html we have the most basic html code:

<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
    <div id="root" />
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

(Clearly you'd want more in there, viewport settings and so forth)

Note how even though index.js should be connected to index.html at the moment we aren't connecting them. We'd do that with webpack.

First let's tell babel to watch for the react syntax as well - we do that in .babelrc:

{"presets": ['env', 'react']}
Enter fullscreen mode Exit fullscreen mode

Of course we'd need to install the preset: npm i --save-dev babel-preset-react

Configuring webpack

Lets create webpack.config.js and write the basic structure.

import webpack from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import LiveReloadPlugin from 'webpack-livereload-plugin'

export default  {
  entry: './client/index.js',
  output: {
    path: '/',
    filename: 'bundle.js'
  },
  module: {
    rules: [... ]
  },
  plugins: [..]
};
Enter fullscreen mode Exit fullscreen mode

First we import all the packages that need: webpack of course, and two plugins which we'll cover when we use then.

The object that we're exporting contains all the webpack configuration. Again, since we are using webpack to manage our react code, we're specifying the entry point to be the main react code, webpack will take that, compile it and output it as es5 code at bundle.js (it never appears as a raw file in your directory but it can be accessed in the browser /bundle.js)

Before we move on, lets install the packages we imported above

npm install --save-dev webpack html-webpack-plugin webpack-livereload-plugin 
Enter fullscreen mode Exit fullscreen mode

Setting up webpack rules

Inside module.rules we are able to get webpack to perform all sorts of operations based on the rules we specify.

The first rule of course will be for webpack to compile all our javascript code to ES5, and the second rule is to treat all our css code as css!

export default  {
  ...
  module: {
    rules: [
      {
        use: 'babel-loader',
        test: /\.js$/,
        exclude: /node_modules/
      },
      {
        use: ['style-loader', 'css-loader'],
        test: /\.css$/
      }
    ]
  },
  ...
};
Enter fullscreen mode Exit fullscreen mode

Very self explanatory, we are basically making sure that if the file being processed is with a .js extension, run it through babel-loader package (excluding the node modules).

If the file has a .css extension, run it through the style-loader and css-loader package.

Whilst we do not import these packages, we do need to have them installed

npm i --save-dev babel-loader style-loader css-loader babel-core
Enter fullscreen mode Exit fullscreen mode

Note that using babel-loader seems to require babel-core as well.

There are so many other rules you can add, rules concerning images, fonts, svg, minifications and lots more.

I love SASS so let's write another rule to handle files with .scss extensions. Still within the rules array:

{
    test: /\.scss$/,
  use: [{
      loader: "style-loader"
  }, {
      loader: "css-loader", options: {
          sourceMap: true
      }
  }, {
      loader: "sass-loader", options: {
          sourceMap: true
      }
  }]
}
Enter fullscreen mode Exit fullscreen mode

I took the above setup straight from the documentation. It's similar to the other tests, however because we needed to add the options the values of the use array are objects. We are simply ensuring that when our SASS compiles to CSS, source maps are generated (very useful for debugging SASS in the browser).

We know that we need to install the sass-loader just as we did with other loaders.

npm i --save-dev sass-loader node-sass
Enter fullscreen mode Exit fullscreen mode

(sass-loader requires the use of node-sass)

With that setup, in ./client/index.js we would be able to import SASS files in our react code and webpack would handle the conversion.

Setting up webpack plugins

So far we configured the output and the rules. Webpack knows exactly what to do when it encounters our code. Now we want to merge all our code (from the entry point) and bundle it all together

import webpack from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import LiveReloadPlugin from 'webpack-livereload-plugin'

export default  {
  entry: './client/index.js',
  ....
  plugins: [
    new HtmlWebpackPlugin({
      template: 'client/index.html'
    }),
    new LiveReloadPlugin()
  ]
};
Enter fullscreen mode Exit fullscreen mode

The first plugin HtmlWebpackPlugin takes care to put everything together, read to be shipped. Note the entry point, and the template, webpack links the two, hence we didn't need to manually add any script tags in the client/index.html

Using the bundle

We've already decided to use express to send content to the browser. It makes sense that we need to get the bundle from webpack and serve it through express. Let's do that in server.js:

import express from 'express'; 
import webpack from 'webpack';
import webpackMiddleware from 'webpack-dev-middleware';
import webpackConfig from './webpack.config.js';

const app = express();
app.use(webpackMiddleware(webpack(webpackConfig)));

app.get('/api', (req, res) =>  )

app.listen(4000, () => {
  console.log('Listening');
});
Enter fullscreen mode Exit fullscreen mode

Within our express code, we import our webpack file and let webpack create the bundle (webpack(webpackConfig)), then we convert it to a middleware which express can understand (webpackMiddleware(webpack(webpackConfig))) and finally let express use it as it's middleware.

That middleware takes the bundled react application and serves it to the home route. We can still create react routes (the /api is an example) but the home route is taken over by the express application.

All that's left to do is to install the middleware package we used above

npm i --save-dev webpack-dev-middleware
Enter fullscreen mode Exit fullscreen mode

Runnig the server

Inside package.json lets add an npm start script.

  "scripts": {
    "start": "nodemon --exec babel-node server.js  --ignore client"
  }
Enter fullscreen mode Exit fullscreen mode

Then, in the terminal we just need to run npm start which in turn runs the above line. What we are doing there is; we're running server.js with nodemon and babel-node but we are telling them to ignore the /client folder. That's because, that particular folder is going to be handled by webpack instead.

Conclusion

You can clone the project from github

I've hesitated to write this tutorial as I rarely need to set up my environment from scratch. However I feel I've learned a lot more about how babel, webpack and express work together by writing this. I hope you learned something too. (If you have anything to add, please comment :))

💖 💪 🙅 🚩
aurelkurtula
aurel kurtula

Posted on February 15, 2018

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related