Article Image
Article Image
read

UPDATED 2017/02/04: There is now a webpack version of this post.

In this post, I’m going to walk through creating a very simple Javascript project. The project itself is not going to do anything much, but the point (as always, with javascript) is to figure out how to get the build and test pipelines set up correctly. I’m going to try to use a minimum of components, and in the end I want the following;

  • A static javascript application, deliverable as three files;

    • index.html
    • application.js
    • stylesheet.css

The application should work in all modern browsers.

  • When working on the site, I want to;

    • write my javascript using ES2015
    • keep my javascript classes in separate files
    • write and run unit tests for my classes

For the purposes of this exercise, all the application needs to do is write a message into a web page. The point is to use ES2015 to do that.

Getting started

Let’s start by setting up a fresh project (FYI, I’m using npm version 3.5.3);

$ mkdir hello-es2015
$ cd hello-es2015
$ git init
$ npm init

For now, just press return in response to all the prompts.

Let’s add some directories

$ mkdir src dist
$ touch src/.gitkeep dist/.gitkeep

We’re going to be adding some npm packages, but we don’t want them in our git repo.

$ echo node_modules/ > .gitignore

Now let’s add some source files;

src/app.js

console.log("Hello from app.js");

src/stylesheet.css

body {
  background-color:  black;
  font-family:       "Roboto","Helvetica Neue",Helvetica,Arial,sans-serif;
  color:             white;
  font-weight:       bold;
  margin:            20px 20px auto;
}

main {
  width: 95%;
  margin: auto;
}

src/index.html

<html>
  <head>
    <meta name="viewport" content="width=500,initial-scale=1.0,user-scalable=yes">
    <title>Hello ES2015</title>
    <link rel="stylesheet" href="stylesheet.css" />
  </head>
  <body>
    <main>
      <p>This is not very interesting</p>
    </main>
    <script src="application.js"></script>
  </body>
</html>

And let’s commit what we’ve got, so far

$ git add .
$ git commit -m "Initial commit"

All very simple. If you open src/index.html in your browser, you should see an uninspiring web page.

Note that you won’t see anything logged to the console, because we called our script application.js in the html tag, but our javascript source file is called app.js We’ll fix that in the next step.

Gulp

We’re going to need a build tool to take our separate javascript classes and combine them. We’re also going to need to transpile the ES2015 javascript into the ES5 code supported by the majority of browsers. So, let’s create a build system to generate our dist/ files. For the files we’ve got right now, a simple cp src/* dist/ would work. But, we’re going to need something a bit more complicated pretty soon, so we’ll use a proper build tool.

We’re going to use gulp.js, installing it globally so that we can call gulp from the command-line, without having to type ./node_modules/gulp/bin/gulp.js every time. We’re also going to install it as a project dependecy, along with a couple of plugins.

$ npm install gulp-cli --global
$ npm install gulp gulp-concat del --save-dev

(For a proper introduction to gulp, check out this article)

In order to use gulp, we need to tell it what to do via a gulpfile.js so create one with the following content;


var gulp   = require('gulp'),
    concat = require('gulp-concat'),
    del    = require('del');

gulp.task('default', ['clean'], function() {
  gulp.start('build-js', 'copy-css', 'copy-html');
});

gulp.task('clean', function() {
  return del('dist/*');
});

gulp.task('build-js', function() {
  return gulp.src('src/**/*.js')
    .pipe(concat('application.js'))
    .pipe(gulp.dest('dist/'));
});

gulp.task('copy-css', function() {
  return gulp.src('src/**/*.css')
    .pipe(gulp.dest('dist/'));
});

gulp.task('copy-html', function() {
  return gulp.src('src/**/*.html')
    .pipe(gulp.dest('dist/'));
});

This loads our gulp plugins, then defines tasks to build our application.js file (build-js), and copy our CSS and HTML files. The default task is what will happen if we just type gulp at the command-line, and the clean task is listed as a dependency of the default task, so we’ll always be building into a clean dist/ directory.

Run gulp and you should see something like this;

$ gulp
[17:40:59] Using gulpfile ~/hello-es2015/gulpfile.js
[17:40:59] Starting 'clean'...
[17:40:59] Finished 'clean' after 5.78 ms
[17:40:59] Starting 'default'...
[17:40:59] Starting 'build-js'...
[17:40:59] Starting 'copy-css'...
[17:40:59] Starting 'copy-html'...
[17:40:59] Finished 'default' after 12 ms
[17:40:59] Finished 'build-js' after 30 ms
[17:40:59] Finished 'copy-html' after 20 ms
[17:40:59] Finished 'copy-css' after 22 ms

Now if you open dist/index.html in your browser, you should see the same web page, and this time you should also see a log message Hello from app.js in the console.

ES2015

We haven’t done anything using ES2015 javascript features yet. Let’s fix that now.

Edit your src/index.html file, and remove the contents of the main tag, so that it’s just <main></main>

Then replace src/app.js with this;

import Hello from './hello'

(new Hello({
  target: document.getElementsByTagName('main')[0]
})).run();

…and create a src/hello.js file containing this;

class Hello {

  constructor(config) {
    this.target = config.target;
  }

  run() {
    this.target.innerHTML = `
      <p>
        Hello from ES2015
      </p>
    `;
  }
}

export default Hello;

When you run gulp again and reload your browser, you should see nothing but a black page, with a javascript error in the console like this;

application.js:1 Uncaught SyntaxError: Unexpected token import

We need to modify our build pipeline to transpile our new ES2015 code to ES5. To do that, we’ll use babel

$ npm install babel-cli babel-preset-es2015 babelify browserify vinyl-source-stream

create a .babelrc file containing this;

{
  presets: ["es2015"]
}

Now that we have babel, we can convert our gulpfile to ES2015 as well. Rename gulpfile.js to gulpfile.babel.js and edit it to look like this;

(() => {
  'use strict';
})();

import gulp from "gulp";
import browserify from "browserify";
import source from "vinyl-source-stream";
import del from "del";

gulp.task('default', ['clean'], () => {
  gulp.start('build-js', 'copy-css', 'copy-html');
});

gulp.task('clean', () => {
  return del('dist/*');
});

gulp.task('build-js', () => {
  return browserify("src/app.js")
    .transform("babelify")
    .bundle()
    .pipe(source("application.js"))
    .pipe(gulp.dest("dist"));
});


gulp.task('copy-css', () => {
  return gulp.src('src/**/*.css')
    .pipe(gulp.dest('dist/'));
});

gulp.task('copy-html', () => {
  return gulp.src('src/**/*.html')
    .pipe(gulp.dest('dist/'));
});

Run gulp again, and then refresh your browser, and you should see “Hello from ES2015”

Testing

The last thing I want to do is get some tests running. We’re going to use the Mocha test framework, with the Chai assertion library.

First, let’s create a simple test. Edit your src/hello.js file and add a method we can test, called ten;

class Hello {

  constructor(config) {
    this.target = config.target;
  }

  run() {
    this.target.innerHTML = `
      <p>
        Hello from ES2015
      </p>
    `;
  }

  ten() {
    return 10;
  }
}

export default Hello;

Impressive, isn’t it? But, it gives us an instance method we can target in a unit test.

Now install mocha and chai;

$ npm install mocha gulp-mocha chai --save-dev

Create a directory called test and then a file test/hello_test.js;

import { expect } from 'chai';
import Hello from '../src/hello'

describe("A simple test", () => {
  let hello;

  beforeEach(() => {
    hello = new Hello({});
  });

  it("should return ten", () => {
    expect(hello.ten()).to.eq(10);
  });

});

Now add a task in our gulpfile.babel.js file to run the tests;

...
import mocha from "gulp-mocha";   // <-- add this line
...
gulp.task('test', () => {
  return gulp.src('test/**/*_test.js', { read: false })
    .pipe(mocha({reporter: 'dot'}));
});
...

That’s it. Now we can run our test with gulp test

Because gulp is already configured to use babel to transpile ES2015 files, there’s nothing else we need to do.

Conclusion

There’s a lot more we could do with these tools, such as setting up gulp to watch for changes and run our tests, but the point of this post was to show you how to put the pieces together. I hope you found it useful.

The final source code for this post is available here

Blog Logo

David Salgado


Published

Image

Ronin on Rails

Give a man a fish and he’ll eat for a day.
Give a fish a man and he’ll eat for weeks.

Back to Overview