Workflow for static frontend development (Gulp + ReactJs + ES6)

Workflow goal

Getting into frontend development could be overwhelming.

I recently started learning more about frontend development. My intent is to build some static websites to try out things like IPFS.
I wanted to have a productive workflow that allows me to create project, build it, test it, run it and package it. Coming to the frontend world I realized things are really confusing as there are a lot of different tools.

In this post we are going to see how we can structure our project and use Gulp to do common tasks while developing. I am still learning so this post is just describing what I have learned and I plan to improve my understanding & workflow over time.

Goal of the workflow

My current aim with the workflow is to

  1. Create small JS libraries and use them in our web app.
  2. Develop in ES6 using Babel.
  3. Compiled JS files should have debug information so we can debug in ES6.
  4. Use a package manager to get JS packages e.g. reactjs.
  5. Have the capability of livereload so any changes to JS would automagically reflect in the browser.

These are the basic things I needed to get started.

Tools for the job

Framework

I would be creating the workflow with React in mind but you should easily be able to replace the react specific things to any other framework of your liking.

Package manager

Npm is the package manager that comes with node. My aim is to develop not for node but instead create a static web app, so bower is the package manager specifically designed with frontend in mind. You might wonder why as one can use npm for frontend as well, a good answer I found regarding that was on the omniscient Stackoverflow.

Task runner

We have to use a task runner which runs our various tasks of compiling ES6 to JS, bundling it and reloading browser on change. The major two task runners are Grunt & Gulp. The major difference between the two is that Grunt is more configuration based and Gulp is more code based.
I choose gulp because looking around I found that it was the fastest among the two and I liked its code based configuration. I also like the gulp model of chaining various tasks to create workflow.

Project structure

As we discussed earlier that we will create small libraries that we will use in our main web app. The example application we are going to build is a webapp using React that allows you to add numbers. Internally adding number will use our calculationjs library.

Following is the tree for the project structure

umbrella-project
├── calculationjs      /* Calculationjs library */
│   ├── bower.json     /* #Configuration for bower */
│   ├── gulpfile.js    /* Configuration for gulp */
│   ├── index.js       /* Our library code */
│   └── package.json   /* #Npm configuraiton */
└── web-calculator     /* Calculator web app */
    ├── bower.json     /* #Configuraiton for bower */
    ├── gulpfile.js    /* Configuration for gulp */
    ├── package.json   /* #Npm configuration */
    └── src
        ├── index.html /* Our web page entry */
        └── js
        └── app.js     /* Our react app */

[Note] The ones starting by # are generated and maintained by tools like bower, npm

To create bower.json run bower init in the directory where you want to create the bower package. Similarly to create package.js run npm init.

Go ahead and create the project structure.

Calculation library

Navigate to calculationjs folder in terminal and run

npm install --save-dev gulp gulp-util browserify babelify vinyl-source-stream

This will install the require packages. Now lets see the how the gulpfile.js looks like.

First we get all the dependencies that we just installed.

In gulp we have four main functions

  1. gulp.task is used to define a gulp task
  2. gulp.src is used to return a stream of files
  3. gulp.dest is used to write files to disk
  4. gulp.watch is used to watch for any file changes and run the tasks that you list

The .pipe operation is used to pipe output of previous function to the function that you pass to it. It is used to chain various transformations to create your workflow.

We define the build task where browserify is passed in the entry point of your application. It will traverse the imports you have and figure out all the modules you have. We also specify paths in browserify options to let it know which paths to look for modules if they are not specified as relative path. So if you install react then you can import it was import React from 'react/react' where 'react/react' will resolve to bower_components/react/react.js.

Then we use transform and pass it babelify so that we can compile ES6 to javascript that the browser can run.

bundle is used to bundle all the modules into one file, the source is used to convert the output of bundle to representation that gulp works with i.e. vinyl files. At the end we just output the files to the destination using gulp.dest

The watch task is used to watch any change in our JS files and then run the build so that you don't have to as you are developing.

We want to develop this library as we develop the web-calculator app so we need to link to this library. To do that run bower link this will create a global reference in bower so that we can link to it.

Web calculator application

Navigate to web-calculator folder in terminal and run

npm install --save-dev gulp gulp-util browserify babelify vinyl-source-stream watchify browser-sync

Run bower link calculationjs to link to that library and run bower install react to install Reactjs.

In the gulpfile for web-calculator we have a new task copy-static which copies static file (in our case index.html) to destination directory. We use gulp.src to get the stream of the static files we have and then use gulp.dest to place the files in our destination directory.

The build task is exactly the same as we had in our calculationjs project. The watch task is different. We use wacthify package to watch files for change instead of gulp.watch because gulp.watch, on a file change recompiles all the browserify dependencies, making it slow. Watchify doesn't do that, it only recompiles the browserify bundle that changed.

In the watchify update callback we specify what to do when a file changes, most of it is similar to what we have in build task apart from the last part i.e. browserSync.reload. We use Browser Sync to do livereload and also sync the input across browsers so that you can test it across browser without any extra effort.

You may wonder why we have the following after the watcher.on('update', ...)

.bundle()
.pipe(source(paths.OUT))
.pipe(gulp.dest(paths.DEST_BUILD));

It is because the watchify does not emit update callback until we call bundle at least once and consume its stream. And that part also compiles the files for us before it starts watching for any changes.

At the end we just start the server for Browser Sync. Now you should be able to make changes in your code and see the browser auto reload with new changes, even if you make changes to calculationjs lib it should work fine.

You should now have an idea of the basics of gulp and be able to create more tasks to your liking.

You can get the whole example project @github.

References: