Joseph Jude

Consult . Code . Coach

Comprehensive guide to using Gulp with hapi.js & Typescript


code . hapijs . nodejs . tsc

This post is part of Learn to build web-applications with Hapijs and Typescript

TypeScript brings all the features lacking in JavaScript, without abandoning JavaScript. TypeScript is just a superset of JavaScript.

Even though Typescript brings ton of benefits, you can't execute Typescript files directly. You need to compile it into javascript file, which then has to be executed.

This is where gulp helps. It is a tool that automates many of the tasks associated with development - compiling, reloading server, testing, and so on. Such a build tool brings consistency to development process, eliminating development errors.

Why you need gulp

Let us look at a simple hello world hapijs program written in Typescript.

"use strict";

import * as hapi from "hapi";

const server: hapi.Server = new hapi.Server()
server.connection({ port: 3000 });

server.route({
    method: "GET",
    path: "/",
    handler: (request: hapi.Request, reply: hapi.IReply) => {
        reply("Hello World")
    }

});

server.start((err) => {
    if (err) {
        throw err;
    }
    console.log("server running at 3000");
})

This is from Getting started with HapiJS and Typescript post

To execute this, we need to compile it: tsc index.ts which will produce a index.js, which we can execute with node index.js.

We have to repeat this every time we modify the program. This is a boring task adding no value.

Using a build tool, like gulp, we can automate the entire build process.

Getting started with gulp

Getting started with Gulp is a 3-step process.

1. Install gulp globally

npm install -g gulp

2. Install gulp as a dev-dependency in the local project

npm install --save-dev gulp

3. Create a gulpfile.js in the root directory of the project

var gulp = require('gulp');

gulp.task('default', function() {
  // code goes here
});

When we execute gulp, the default task is executed.

So let us consider, this gulpfile.js.

var gulp = require('gulp');

gulp.task('default', function() {
  console.log('hello from gulp');
});

If you invoke gulp at the terminal, you will see this:

[15:02:57] Using gulpfile gulpfile.js
[15:02:57] Starting 'default'...
hello from gulp
[15:02:57] Finished 'default' after 180 μs

Before we dive deep into using gulp to compile our Typescript files, let us understand how gulp works.

Understanding how gulp works

Gulp is based on the concept of tasks and streams. Each task takes an input, does whatever it is supposed to do, and then passes the output to next task.

Because of tasks and streams, we can break down our workflow into smaller tasks, and pipe them together for the needed workflow. We can mix and match these tasks the way we want. This makes gulp highly flexible.

The Gulp API is small. There are only 4 functions. Let us look at these functions.

gulp.src

It selects the files to use. It uses .pipe to chain its output to other tasks. You can use globe or array of globes to select files.

gulp.dest

It points to the output folder for files. In the previous example, the minified js file will be written to a folder named build.

It can also pipe its output to subsequent tasks.

For example, we can select all .js files, minify them, and write to a build folder.

gulp.src('*.js')
.pipe(minify())
gulp.dest('build')

gulp.task

It defines the task to execute. You can also specify tasks that need to be completed prior to executing the current one.

gulp.task('taskname', ()=>{
    // do stuff
});

If you have other tasks to complete prior to compiling, you can specify them too.

var gulp = require('gulp');

gulp.task('first', () => {
    console.log('hello from first task')
});

gulp.task('second', () => {
    console.log('hello from second')
});

gulp.task('default', ['first', 'second'], () => {
    console.log('hello from gulp')
});

If you run this with gulp, you will get output like this:

[19:36:23] Starting 'first'...
hello from first task
[19:36:23] Finished 'first' after 237 μs
[19:36:23] Starting 'second'...
hello from second
[19:36:23] Finished 'second' after 218 μs
[19:36:23] Starting 'default'...
hello from gulp
[19:36:23] Finished 'default' after 143 μs

Keep in mind that gulp doesn't execute these tasks in sequence; they are executed in parallel.

gulp.watch

As the name suggests, it watches files and invokes a task when a file changes.

gulp.watch('scripts/**/*.js', ['lint']);

When any of javascript files changes, this will invoke the lint task.

Gulp plugins for Typescript

Now that we have understood gulp, let us see how we can use it for developing hapijs apps with Typescript.

A typical typescript development workflow goes like this:

We need plugins to achieve each of these. Plugins bring additional functionalities to gulp.

We need the following plugins:

Let us install them with:

npm install typescript gulp gulp-typescript nodemon gulp-nodemon --save-dev && npm install hapi

After this, the devDependencies part of package.json will look like this:

"devDependencies": {
    "gulp": "^3.9.1",
    "gulp-nodemon": "^2.2.1",
    "gulp-typescript": "^3.1.4",
    "nodemon": "^1.11.0",
    "typescript": "^2.1.5"
  }

Bringing it all together

For this tutorial purpose, we are going to assume that all typescript files are in src folder and the compiled javascript files will be in build folder. With that assumption, our directory structure looks like this:

├── build
├── gulpfile.js
├── node_modules
├── package.json
├── src

Now let us start with automated compiling:

"use strict";

let gulp = require('gulp');
let ts = require("gulp-typescript")

gulp.task("compile", () => {
  console.log("compiling files")
  return gulp.src(['src/**/*.ts'])
  .pipe(ts({module: 'commonjs'})).js
  .pipe(gulp.dest('build'))
})

gulp.task("default", ["compile"]);

If you execute this with gulp, it will compile the files in src folder and create *.js files in build directory.

Now let us add watch functionality, so that compile is invoked every time a *.ts file changes. We are going to add a new task watch and modify the default task to invoke the watch task rather than the compile task.

"use strict";

let gulp = require('gulp');
let ts = require("gulp-typescript")

// **** NEW TASK ****
gulp.task("watch", () => {
  gulp.watch('src/**/*.ts', ["compile"]);
});

gulp.task("compile", () => {
  console.log("compiling files")
  return gulp.src(['src/**/*.ts'])
  .pipe(ts({module: 'commonjs'})).js
  .pipe(gulp.dest('build'))
})

// **** MODIFIED DEFAULT TASK ****
gulp.task("default", ["watch"]);

Now if we execute with gulp, it will compile and watch for any changes. Gulp simply waits and watches for any new change.

[07:21:40] Starting 'watch'...
[07:21:40] Finished 'watch' after 18 ms
[07:21:40] Starting 'default'...
[07:21:40] Finished 'default' after 160 μs

So far, we focused on compiling the typescript files. We didn't start the hapi server. Now let us add that functionality too. We are going to use nodemon to monitor for any change in javascript files and restart the server. The modified gulpfile.js looks like this:

"use strict";

let gulp = require('gulp');
let ts = require("gulp-typescript")
let nodemon = require("gulp-nodemon");

gulp.task("default", ["serve"]);

gulp.task("watch", () => {
  gulp.watch('src/**/*.ts', ["compile"]);
});

gulp.task("compile", () => {
  console.log("compiling files")
  return gulp.src(['src/**/*.ts'])
  .pipe(ts({module: 'commonjs'})).js
  .pipe(gulp.dest('build'))
})

gulp.task("serve", ["compile", "watch"], () => {
  nodemon({
    script: "build/index.js",
    env: { "NODE_ENV": "development" }
  })
    .on("restart", () => {
      console.log("restarted");
    })
})

We added a serve task and modified the default task to this serve task. Now if you execute this with gulp, it will compile, and start the server; if we change any .ts file, it will recompile and restart the server. The output will look like this:

[07:30:55] Starting 'compile'...
compiling files
[07:30:56] Starting 'watch'...
[07:30:56] Finished 'watch' after 13 ms
[07:30:57] Finished 'compile' after 1.72 s
[07:30:57] Starting 'serve'...
[07:30:57] Finished 'serve' after 172 ms
[07:30:57] Starting 'default'...
[07:30:57] Finished 'default' after 46 μs
[07:30:57] [nodemon] 1.11.0
[07:30:57] [nodemon] to restart at any time, enter `rs`
[07:30:57] [nodemon] watching: *.*
[07:30:57] [nodemon] starting `node build/index.js`
server running at 3000

Final Words

Gulp automates many of the tasks in the development process. Use this gulpfile as a basis to add further tasks. We have not added linting and testing in this tutorial. As the project grows, you may also use a CSS preprocessor and copy the resultant files to the build directory.

If you need help in writing gulp tasks, browse through the gulp recipes. Also browse through gulp plugins to identify plugins that you would need.

Interested in learning hapijs with typescript? Subscribe now, using the below form, to receive each new lesson for free.


Like the post? Retweet it. Got comments? Reply.

Comprehensive guide to using @gulpjs with @hapijs & @typescriptlang by @jjude https://t.co/DuWce7BXZq

— Joseph Jude (@jjude) January 31, 2017
Share this on: Twitter / /

Comments

comments powered by Disqus