DEV Community

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

Discussion (9)

pic
Editor guide
Collapse
robekay1 profile image
Robekay

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? :/

Collapse
lyrod profile image
Lyrod

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.

Collapse
pavlee profile image
pavlee

Great tutorial, many thanks!

Collapse
krankypanky profile image
Krankypanky

Wow thanks, that tutorial explained me a lot!!

Collapse
aurelkurtula profile image
Collapse
markadell profile image
Mark Adel

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

Collapse
aurelkurtula profile image
aurel kurtula Author

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

Collapse
onexerogames profile image
OneXero

Some really useful pointers in this article. Thanks :D

Collapse
rhn94 profile image
rhn94

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

npm install --save-dev babel-cli