Lesson Weekend

What if we have more than one JavaScript file that we need to use in the browser? Remember, we want to improve browser performance by having as few <script> tags as possible. Could we combine these JavaScript files into one and browserify it?

Concatenation

The answer is yes! This process of consolidating multiple JavaScript files into one is called concatenation, and it drastically decreases load time. We will take all the JavaScript the browser needs and place it into one app.js file.

Smushing all the code together in this manner means that the file won't be very easy for humans to read, but it is very fast for the browser to use.

Thankfully, we can have the best of both worlds by preserving our separate JavaScript files for development, but running a concatenation task to create a separate, combined file for the browser to use!

Concatenating with Gulp-Concat

We are going to add another form for the user to submit their email address, and a separate JavaScript file to retrieve the value from the form and display a thank you message.

Perhaps one day we'll have a fancy ping-pong app related newsletter people can sign up for.

First, we'll add the form to our HTML file:

index.html
<!DOCTYPE html>
<html>
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
    <script type="text/javascript" src="build/js/app.js"></script>
    <title>Ping Pong</title>
  </head>
  <body>
    <form id="ping-pong-form">
      <label for="goal">Enter a number:</label>
      <input id="goal" type="number">
      <button type="submit">Submit</button>
    </form>
    <form id="signup">
      <label for="email">Enter your email:</label>
      <input id="email" type="text">
      <button type="submit">Submit</button>
    </form>
    <ul id="solution"></ul>
  </body>
</html>

Now, let's add a JavaScript file to process the form. Let's call it signup-interface.js and put it in our js folder.

signup-interface.js
$(document).ready(function(){
  $('#signup').submit(function(event){
    event.preventDefault();
    var email = $('#email').val();
    $('#signup').hide();
    $('#solution').prepend('<p>Thank you, ' + email + ' has been added to our list!</p>');
  });
});

We'll also need a new package called gulp-concat. We'll install it with npm now:

$ npm install gulp-concat --save-dev

Next, let's require this package and create a gulp task to use it. While the order of tasks in gulpfile.js doesn't actually matter to gulp, let's place it before our browserify task, since this concatenation task will happen before the browserify task. This will help keep our project more organized for human eyes.

gulpfile.js
...

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

...

gulp.task('concatInterface', function() {
  return gulp.src(['./js/pingpong-interface.js', './js/signup-interface.js'])
    .pipe(concat('allConcat.js'))
    .pipe(gulp.dest('./tmp'));
});

...

  • Here, we have created a task called 'concatInterface'. It uses gulp.src to pull in all the files used in the browser. These files are formatted as an array of file names we are passing in.

  • The next line calls our concat() function, created with require at the top. We pass it the name of the file we want it to create, allConcat.js.

  • Next, we use gulp.dest to tell gulp where to save our new file, which contains both of our JavaScript files. We're going to put it inside of a folder called tmp, which stands for temporary. This is because allConcat.js will not be used in the browser. First, we have to browserify it to pull in any modules it uses.

Let's modify the browserify task to do this next.

...
gulp.task('jsBrowserify', ['concatInterface'], function() {
  return browserify({ entries: ['./tmp/allConcat.js'] })
    .bundle()
    .pipe(source('app.js'))
    .pipe(gulp.dest('./build/js'));
});
...

The first thing we've done is added an additional argument to the task definition, after the name of the task. This is an array of dependencies (tasks that we want to run automatically before the task we are defining). In this case, we are telling it to run the concatInterface task to put all client-side JavaScript into one file before browserifying it.

Then we just make one more change to the task. Instead of browserifying the ./js/pingpong-interface.js file, and the ./js/signup-interface.js file, we are going to browserify the new file and use the path ./tmp/allConcat.js.

We define this in the object passed to the browserify function, under the entries key. With this setup, it's easy to see that there is a chain from the browserify task, backwards to the output of its dependency, the concatInterface task.

Now our long file path used in var Calculator = require('./../js/pingpong.js').calculatorModule; makes sense. After running our concatenation task, the js/pingpong-interface.js ends up in the tmp/allConcat.js file. Then, when it is browserified, it will actually be located in tmp. So, the path to our back-end file ,pingping.js, will start in the tmp folder (./), move up one level to the top of our project directory with (../), move down into our development js folder, and then look for our specific file.

Let's run the new version of our browserify task:

$ gulp jsBrowserify

Now if we open our index.html file in the browser we can use our new signup form with no errors.

We can shorten our new concatenation task even more by using a globbing pattern using *, the wildcard symbol. We can tell the gulp-concat package to concatenate and browserify all files inside of our js folder that end in the string -interface.js.

gulpfile.js
...
gulp.task('concatInterface', function() {
  return gulp.src(['./js/*-interface.js'])
    .pipe(concat('allConcat.js'))
    .pipe(gulp.dest('./tmp'));
});
...

If we maintain this naming convention as our project grows we won't need to modify our gulp concatenate/browserify tasks if we add new files - we just name them something ending in -interface.js if they are going to be used in the browser, and keep them in the js folder. Then they will automatically be included in build/js/app.js.

Terminology


  • Concatenation: A process of copying all JavaScript files into one file to be used in the browser and decrease load time.

  • gulp-concat: An npm package that concatenates multiple files into one for use in the browser.

  • Globbing pattern: Using wildcard characters (such as *) to specify sets of filenames.

Examples


The following is an example gulp task for using gulp-concat to consolidate multiple files:

gulpfile.js
gulp.task('concatInterface', function() {
  return gulp.src(['./js/*-interface.js'])
    .pipe(concat('allConcat.js'))
    .pipe(gulp.dest('./tmp'));
});

The following is an example of adding other Gulp tasks as dependencies in a gulp task:

gulp.task('jsBrowserify', ['concatInterface'], function() {
  return browserify({ entries: ['./tmp/allConcat.js'] })
    .bundle()
    .pipe(source('app.js'))
    .pipe(gulp.dest('./build/js'));
});

Here, there is a second argument, ['concatInterface'], after the name of the task. This is an array of dependencies. Each dependency represents other Gulp tasks that will run automatically before this Gulp task we are defining. In the example above, we are instructing Gulp to run the concatInterface task automatically whenever it runs the jsBrowserify task.