Building a Modern JavaScript Application using Gulp and Babel

Disclaimer: this article is outdated. I would not choose to use gulp
in any projects today.
Whether you’ve just begun your journey with JavaScript or you’re a seasoned developer, you’re probably feeling some pressure to jump on the ES6 bandwagon. But with all the different tools, transpilers, and task runners available to us, it can often seem like a daunting task. If you’re like most developers you’ll opt to skip the configuration phase and jump into development using a pre-configured starter project. This is perfect for getting started right away, but there’s also a lot of value in building and tuning your tooling. In this article we’ll configure a modern JavaScript workflow using a few popular tools and attempt to explain how they work along the way.
Getting Started
Let’s start off with a fresh project. First, we’ll create a folder and initialize a Node project using the command line:
~ mkdir modern-javascript-build ~ cd modern-javascript-build ~ npm init
You’ll be prompted for some information including your project’s name, version, and license type. Once complete, a new file will be created in your current directory with the name package.json
. This file holds your project’s metadata and dependency requirements.
Now that our project’s been initialized, let’s go ahead and add some development dependencies:
~ npm i -g gulp-cli
This will install Gulp so that it's accessible globally and you can use it in your terminal. Next, let's install the first plugins we'll need to create our Gulp tasks.
~ npm i --save-dev gulp browser-sync
Serving the Application
In order to run the application, we’ll need to serve it to our user’s browser through some kind of server. BrowserSync provides a great development environment that takes advantage of live-reloading and many other benefits. For this walkthrough we’ll use the BrowserSync static server, but if you have an existing API or server that you’d like to integrate with, you still have the option to use BrowserSync with a proxy.
BrowserSync
BrowserSync gives us a temporary development server that reloads client browsers on file-change and synchronizes events across many devices. It is not an alternative for a production back-end.
First, we’ll need a simple HTML file.
/app/index.html
<DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Hello ES6</title> </head> <body> <h3 id="message">Hello</h3> </body> <script src="build/scripts.js"></script> </html>
Now we need to create a Gulp task to run a BrowserSync server using the <tt>app</tt> directory as the root. Let’s start by creating our Gulp configuration file.
~ touch gulpfile.js
Gulp
Gulp is a simple task automation toolkit. It can automate repetitive tasks and help you structure out a build process using code instead of configuration files.
Next, we’ll need to actually register the task. We haven’t created the build
or watch
tasks yet, but let’s go ahead and add them to our configuration since we know we’ll need them later on.
gulpfile.js
var gulp = require('gulp') var browserSync = require('browser-sync').create() /** * Create a static server using the `/app` directory as root */ gulp.task('serve', ['build'], () => { browserSync.init({ server: { baseDir: "./app" } }) }) gulp.task('develop', ['build', 'serve', 'watch']) gulp.task('default', ['develop'])</code></pre>
Compilation
Because the majority of browsers don’t support ES6 natively yet, we need some way to take our source code and compile it into code that’s compatible with them. For this task, we’ll use a combination of two different plugins. First we’ll need Browserify in order to support modules in the browser. Then, we’ll use Babel to transform our code into browser-compatible code.
Babel
If Gulp is our task runner, then Babel is our compiler. We'll use it to transform our modern ES6 code to the more compatible ES5. We'll use Babel and Browserify in combination to automate a build task for our project.
Now that we have a task for serving up assets, let’s create a build task that will take our ES6 application source as input and output a compiled ES5 script that can run inside a browser environment. Let’s start by installing some dependencies.
~ npm i --save-dev browserify babelify gulp-sourcemaps gulp-concat babel-preset-env vinyl-buffer vinyl-source-stream
Next, let’s add the following build
task and its dependencies to our gulpfile.
gulpfile.js
// ... var babelify = require('babelify') var browserify = require('browserify') var sourcemaps = require('gulp-sourcemaps') var source = require('vinyl-source-stream') var buffer = require('vinyl-buffer') /** * Compile `app/scripts/` ES6 scripts into `app/build/scripts.js` using * browserify and babelify */ gulp.task('build', () => { browserify( { entries: ['app/scripts/application.js'], cache: {}, packageCache: {}, debug: true } ) .transform(babelify, { presets: ['env'] }) .bundle() .on('error', function (error) { console.error(`\n${error}\n`.red) this.emit('end') }) .pipe(source('scripts.js')) .pipe(buffer()) .pipe(sourcemaps.init({ loadMaps: true })) .pipe(sourcemaps.write('./')) .pipe(gulp.dest('app/build')) .pipe(browserSync.stream()) }) // ...
This babel configuration uses the env
preset in order to inherit the latest stable ES6 feature proposals. This doesn’t include early stage feature proposals such as decorators and optional chaining, but you can still add them to your configuration as optional presets if you wish to use them.
Our Application
We’ll need a test case to prove that our build actually works. Let’s write a simple script to cycle through a list of greetings and render them to the DOM.
/src/scripts/application.js
const element = document.getElementById('message') const greetings = ['Hola', 'Bonjour', 'Aloha' ,'Hallo', 'Ciao', 'Hello'] let currentIndex = 0 setInterval(() => { element.innerHTML = greetings[currentIndex] currentIndex = (currentIndex === greetings.length - 1) ? 0 : currentIndex + 1 }, 1000)
Now that we finally have some code to compile, we can run the following command.
~ gulp build
You should see a couple of generated files in your app/build/
directory. This means Babel successfully compiled our source code and that we’re ready to launch our application.
Now that our build is ready, it’d be great if we could edit our code without having to manually run the build
and serve
tasks manually.
Live Reloading
BrowserSync makes great use of streams for live reloading. As files related to the page are updated in the file-system, they’ll be reloaded from inside the page without having to trigger a manual refresh. In order to take advantage of this, we’ll need to make sure that the app/build/scripts.js
file is updated automatically when we make a changes to our application code. We’ll also watch for changes to our HTML files and force a reload on all clients when they change.
Let’s register another Gulp task.
gulpfile.js
// ... require('colors') /** * Watch paths for file-changes and execute tasks */ gulp.task('watch', () => { gulp.watch("app/*.html").on('change', browserSync.reload) gulp.watch('app/scripts/**/*.js', ['build']) .on('change', (event) => { var timestamp = new Date().toTimeString().split(' ')[0] console.info('[' + timestamp.grey + ']' + ' ✨ File-change detected...'.cyan) }) }) // ...
Now that all our tasks are defined, let’s run the develop
task and see what happens.
~ gulp develop
This task runs build
, serve
, and watch
simultaneously so we can make changes to our code while gulp automatically builds and serve our application to the browser.
Summary
Boilerplate projects are a great way to get started without writing repetitive code, but at some point in your project, you’ll inevitably need to change your workflow. Without a solid understanding of how your application’s build system works, altering it can be intimidating.
Now that you have a basic working application, try adding some of your own tasks to customize your build process. What are some of your favorite tools? What build steps can’t you go without? Let me know in the comments below!
Links
Source code: https://github.com/iamvfl/gulp-babel-starter