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 :))
Top comments (9)
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!!
Glad you liked it
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