Getting started with ES6, Babel and Webpack

Javascript is weird. We know this. By now, you’ve probably heard of something called ES6, or maybe ES2015 and above. Maybe you’ve heard it’s better, or more modern, but that it can’t be used in every browser yet. So what’s the point? How are people using it? There’s a lot of jargon, and a lot of technology behind that jargon — transpilation, polyfill, whatever.
Fear not! We’re all in this together. I decided to compile a guide since I spent a solid 2 weeks panicking trying to figure this out for freelance work.
Why do I need these things?
If your current workflow works for you, then great! But if you’ve ever dreamed of let and const declarations, class and import syntax, arrow functions, more modular code, or other syntactic sugar for JavaScript, you’ll want to look into ES6+ and Babel.
ES(some-number) just defines a specification of the JavaScript language. ES5 is older than ES6, which is the same as ES2015, which is older than ES2017 (they’re trying to do yearly releases, hence the switch in naming conventions). Not too bad.
Here’s a list of features in ES2015 aka ES6 — there are even more in ES2016 and ES2017!
Babel is a transpiler. Whereas a traditional compiler produces bytecode output, a transpiler just translates one program into an equivalent program. For example, you can transpile between Python and JavaScript, and from ES2015 to ES5. Babel does the latter.
If you want to bundle your assets into a single file, you’ll need a tool like Webpack. Webpack also includes a very nice dev server that will automatically reload your project as you update it.
Webpack can bundle all sorts of things: JavaScript, CSS and JSON files, even images and SVG files, letting you just import or require them in your code. If you need more convincing, here’s a detailed breakdown on when/why you might want to use Webpack.
If you’re still interested, let’s go through the steps required to set up a basic web app with Babel and Webpack. We’ll start from the beginning by setting up a package manager. I will, however, assume you’re comfortable with a terminal.
Install a package manager
If you’re using any sort of external module or framework (like Angular or React) and want to do dev outside the browser, you’ll need a package manager. A package manager lets you install packages as well as create a clean environment for each project, with only the versions of those packages you need.
npm, the Node Package Manager, comes bundled with Node.js, so go ahead and install Node.js, a desktop runtime for JavaScript.
If you’re on a Mac and use Homebrew, you can use it install Node:
brew install nodeIt’s a similar process with the package manager Chocolatey for Windows, and likely most other package managers. If you already have Node installed, you might need to upgrade it to the latest version.
To follow along, point your terminal to a new directory and initiate a package with:
npm initAnswer the relevant questions (you can skip them) and you’ll be good to go.
Install + Configure Babel
The good news: as of Mac OS Sierra and iOS 10, all modern browsers support ES6 syntax. The bad news: a lot of browsers or devices aren’t up to speed.
Our particular transpiler, Babel, takes code written in ES6 (or ES2016, or ES2017) and turns it into more-compatible Javascript. So let’s use our fancy npm to install Babel, which does just that.
npm i babel-cli babel-preset-env --save-devNote: babel-cli is the actual babel command we’ll execute. babel-preset-env is a bundle of transpilation rules that let us use all the features in ES2015 up to the latest ES spec, and lets us target specific environments. The --save-dev flag lets npm know that these dependencies are only required for developing the project, and not running it.
We need to tell Babel to use the babel-preset-env we installed, so create a new file called .babelrc in your package directory with the following content:
{
"presets": ["env"]
}This is our very basic preference file for Babel. To show you that it works, create a new folder called src, and add a new file src/index.js with the following ES6 body:
let greeting = () => 'Hello, world!';Now, we can transpile the file by telling npm to execute the babel runtime we downloaded, using the npx command (which stands for “npm execute”):
npx babel src/index.js -o src/transpiled.jsNote: this takes an input, src/index.js and transpiles it to an output -o, src/transpiled.js
This will create a new file, src/transpiled.js with the following content:
'use strict';var greeting = function greeting() {
return 'Hello, world!';
};
This is the ES5 equivalent of the fancy ES2015 syntax we used! It achieves the same effect, but will work on any browser since 2009. Next, we’ll see how to use Webpack to bundle all of our .js files so we don’t have to transpile them manually.
Note: we won’t go over more complicated concepts like polyfills, but the process is pretty similar.
Install + Configure Webpack
As mentioned before, Webpack is our bundler and our dev server. It will condense all of our assets into a single file as well as handle our Babel transpilation for us.
Let’s get started:
npm i webpack webpack-dev-server babel-loader --save-devNote: This installs Webpack, webpack-dev-server, and the babel-loader that automatically transpiles our files before Webpack bundles them.
Since our source material lives in the src folder, let’s create a new folder for our output, called dist. We’ll tell Webpack to output our bundle to that folder. Create a webpack.config.js file in the root project directory with the following content:
var path = require('path');module.exports = {
entry: {
app: './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'), // relative path to dist filename: 'bundle.js'
},
devServer: {
contentBase: './dist/'
},
module: {
rules: [{
test: /\.js$/,
use: ['babel-loader']
}]
}
}
This tells Webpack that the entry point to our app lives in src/index.js, that we want the output to live in dist/bundle.js, that webpack-dev-server should serve content from the dist folder, and that we want to load all of our .js files through the babel-loader before bundling them.
To see if everything works, run:
npx webpackThis should create a bundle in your dist folder! How exciting. If we want a production-ready bundle, minified and uglified, we can run:
npx webpack -p # the p stands for "production"Now, let’s set up webpack-dev-server. Add a boilerplate index.html file in your dist folder that points to your bundle.js. Here’s an example:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>Now, add console.log(greeting()); to the end of your src/index.js, so it’s two lines:
let greeting = () => 'Hello, world!';
console.log(greeting());Next, launch webpack-dev-server:
npx webpack-dev-serverThis will spin up a local server, by default at port 8080. So point your browser tolocalhost:8080, and check the console output (on Chrome on Mac, command-option-j). You should see Hello, world!.
Now you can write your app as normal, but take advantage of all of the ES2015–2017 features without having to worry about transpiling or reloading on your own.
Importing Packages
Webpack-dev-server now bundles and reloads our code — great! But what about other packages, you know, the ones npm lets us install? Let’s go through a couple examples of how the import syntax works with npm packages.
First off, let’s install jQuery, a popular DOM manipulation library, and d3, a popular data visualization framework.
npm i jquery d3In our src/index.js file, add the following lines:
import * as d3 from 'd3';
let greeting = () => 'Hello, world!';
d3.select('body').append('div').text(greeting());When importing files, Webpack knows to look up a package by its npm name. That lets us just type 'd3' or 'jquery' instead of linking directly to a file.
JavaScript modules define exports — which can be variables or functions, and can be accessed by other modules that import them. Above, we took everything (*) in the d3 package’s namespace, and stored it in a new namespace within our file, the variable d3. Not too complicated! We could also import specific members from d3’s namespace:
import { select } from 'd3';
let greeting = () => 'Hello, world!';
select('body').append('div').text(greeting());Here, we only import the select function from d3, and could add any others we wanted, separated with commas between the braces. Note that it no longer lives in the d3 namespace, we can just call it by its name.
JavaScript modules can also define default exports, which is what a module provides when no particular import is specified. jQuery is one such library that defines a default export:
import $ from 'jquery';
let greeting = () => 'Hello, world!';
$('body').append(greeting());Notice that we aren’t importing anything specifically from jQuery, just assigning its default export to the variable $. Different packages define their exports differently, so make sure you’re importing what you need!
Note: We could add a final line export greeting; to turn our index.js into a module of its own, or even export default greeting;. We could import { greeting } from './index'; from a different file in the same folder.
Final Thoughts
This is a very basic example, but it should give you enough of a foundation to get going building larger apps. You can break your app into modules and import them using ES6 syntax. It also introduces the concept of Webpack loaders, which allow us to bundle anything imaginable into our app, like CSS, JSON, SVG, and even regular images. Loaders can be chained together, so you can transform Sass into CSS into inline Styles using a chain of style-loader, css-loader, sass-loader, for example. You can find Webpack’s documentation on rules and loaders here.
Webpack and Babel are powerful tools, but getting started can be frustrating. Hopefully this guide clears some things up!
In the future, I hope to do a couple more tutorials on importing CSS and CSS modules, and getting a similar environment set up with TypeScript.
