loading...

Setting up a minimal Node environment with Webpack and Babel

aurelkurtula profile image aurel kurtula ・6 min read

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

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']}

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

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');
});

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

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

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')
);

(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>

(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']}

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: [..]
};

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 

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$/
      }
    ]
  },
  ...
};

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

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
      }
  }]
}

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

(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()
  ]
};

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');
});

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

Runnig the server

Inside package.json lets add an npm start script.

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

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 :))

Posted on by:

aurelkurtula profile

aurel kurtula

@aurelkurtula

I love JavaScript, reading books, drinking coffee and taking notes.

Discussion

markdown guide
 

First of all I wanna mention that this tutorial helped me a lot to understand the basics of webpack, babel and so on ...

The only thing I do not appreciate with this implementation is that when loading the html-site there is a clear delay visible when adding some style to the stylesheets.

For example, everytime I reload the site all the buttons and textfields which I have added to the html-file are first loaded in the "standard html look" and a split second later my css styling is added and they appear in the way they should.

This is definetly not a huge issue, but it is not very pleasant.

Does anyone maybe know how to fix that issue? :/

 

You'll need extract-text-webpack-plugin (Webpack 3) or mini-css-extract-plugin (Webpack 4) so the scss files are bundled into a css file.

html-webpack-plugin should add it to the head of the index.html and browsers will load it before rendering the application.

 

Great tutorial, many thanks!

 

Wow thanks, that tutorial explained me a lot!!

 
 

Why would we use babel with NodeJS, doesn't NodeJS already cover most of ES6/ES7 features?

 

Yes but doesn't cover the import/export which I totally forgot to mention

 

Some really useful pointers in this article. Thanks :D

 

you might want to update this; you can't install babel-node anymore globally; you have to

npm install --save-dev babel-cli