Quick Tip: Creating Multiple JavaScript Files using Gulp

Bundling scripts in Gulp is easy, but what if you want optimized scripts for each page? In today's post, we look at a Gulp script to create multiple, optimized scripts for pages.

Written by Jonathan "JD" Danylko • Last Updated: • Develop •

Newspapers bundled together with twine

Since Gulp was integrated into Visual Studio, it's made front-end tasks easier when building a project.

Even though I was late to the party, I introduced the front-end tooling back in 2016 and then followed it up with some task runner recipes in Advanced Basics: Using Task Runner.

Think of Task Runners like a mini-DevOps operation in your Visual Studio. You can automate as much as you want before the server-side code compiles.

While I've seen some gulpfile.js functions combining everything into one task, I like piece-mealing my Gulp recipes. I process the following in my gulpfile.js.

  • TypeScript
  • Minify, Bundle, and Uglify JavaScript
  • and SASS (CSS)

Definitely standard tasks, but there was one problem I had with bundling JavaScript.

With a particular app I'm working on, I didn't require every single script bundled into one big ball of mud for every single page. There were specific scripts to accomplish certain tasks based on the page. Why would you load every unused script for one single page?

With everything coming together in my learning of front-end tooling, I wanted to share this quick tip/script with everyone.

Bottom line: I wanted an optimized script for each page and not a large JS framework.

The Process

First, you need a directory structure to accommodate your JavaScript on each page.

I decided to go with the directory structure I proposed in the post CSS Bloat, but with JavaScript.

I had the following directories created:

  • wwwroot/content/src - The source code (.ts and transpiled .js files); Specific web page scripts reside in here with a /common directory underneath src providing code reuse for other scripts.
  • wwwroot/content/js - The bundled, minified, and Uglified final version of JavaScript used in web pages

Second, we need our Gulp packages. Our package.json is as follows:

"devDependencies": {
  "@babel/core": "^7.13.8",
  "@types/gulp": "^4.0.8",
  "babel-preset-es2015": "^6.24.1",
  "babelify": "^7.3.0",
  "browserify": "^13.3.0",
  "gulp": "^4.0.2",
  "gulp-babel": "^8.0.0",
  "gulp-browserify": "^0.5.1",
  "gulp-clean": "^0.4.0",
  "gulp-sass": "^4.1.0",
  "gulp-sourcemaps": "^2.6.5",
  "gulp-typescript": "^6.0.0-alpha.1",
  "gulp-uglify": "^3.0.2",
  "node-sass": "^4.14.1",
  "ts-loader": "^4.4.2",
  "typescript": "^4.2.2",
  "vinyl-buffer": "^1.0.1",
  "vinyl-source-stream": "^2.0.0",
  "vinyl-transform": "^1.0.0"
},

Perform an npm install to have these packages installed into your rabbit hole called "node_modules" ;-)

Next, define your packages at the top of your gulpfile.js.

var path = require('path'),
    gulp = require('gulp'),
    gp_clean = require('gulp-clean'),
    sourcemaps = require('gulp-sourcemaps'),
    uglify = require("gulp-uglify"),
    buffer = require('vinyl-buffer'),
    source = require('vinyl-source-stream'),
    rename = require("gulp-rename"),
    browserify = require("browserify"),
    // defined for proper TypeScript settings.
    launch = require('./Properties/launchSettings.json');

Finally, we create our Gulp task.

gulp.task('js', done => {

   srcPaths.js.forEach(file => {

       const b = browserify({
            entries: file,
            debug: true,
            transform: [['babelify', { 'presets': ["es2015"] }]]
        });

       b.bundle()
            .pipe(source(path.basename(file)))
            .pipe(rename(path => {
                path.basename += ".min";
                path.extname = ".js";
            }))
            .pipe(buffer())
            .pipe(sourcemaps.init({ loadMaps: true }))
            .pipe(uglify())
            .pipe(sourcemaps.write())
            .pipe(gulp.dest(destPaths.js));

       done();
    });
});

As you can see, this is a loaded task, but we'll walk through it.

We take the list of specific JavaScript files (srcPaths.js) and run each file through this process.

Two of the big components here are Browserify and Babelify. Browserify is the engine, but Babelify is what reads your JavaScript files and bundles all the scripts by following the dependency tree based on your import { function } from "./script" at the top of each script. This gives you the modularity we've been looking for all along in JavaScript. We are defining the settings for how our scripts should be handled inside browsers. In this case, we are using the es2015 preset.

Once we have our config, we can proceed with bundling our files.

Browserify and Babelify bundles everything up and we grabs the basename of the current file we're using (page1script.js) and rename the file, in this case, to page1script.min.js.

The buffer() (gulp-buffer) function gives us a way to support streaming file support when other packages don't support it.

We continue with sourcemaps, uglifying the javascript, write out our sourcemaps, and write our JavaScript file out to the public directory which is defined in our destPaths.js.

Since everything is packaged up nicely in each JavaScript file, I placed all scripts inside the JS folder with no subfolders. As an option, you could create the folders based on certain areas of your application, but I leave that exercise to the reader. 

As a final process, we tell Gulp we are done with this one file by calling the done() method (passed in at the top of the method) and we're ready to move on to the next file in the array list.

Oh, speaking of destPaths.js...

I almost forgot. We need our srcPaths and destPaths code.

var basePath = path.resolve(__dirname, "wwwroot/content");

var
 srcPaths = {
    srcJs: path.resolve(basePath, 'src/**/*.js'),
    js: [
        path.resolve(basePath, 'src/page1script.js'),
        path.resolve(basePath, 'src/page2script.js')
    ]
};

var
 destPaths = {
    js: path.resolve(basePath, 'js')
};

Now you can focus on writing modular JavaScript code and having it compile to a single web page or a group of webpages.

Why Do This?

There are a number of reasons why you would want to have optimized JavaScript for each page.

  • Page Bloat - Based on past web page sizes, it's about time we slim down on JavaScript in web pages.
  • Performance - It's easier to download a 1K file as opposed to a large JS Framework to perform small interactions.
  • Structured Applications - This method allows your common libraries to be reused across various scripts making the application more modular and well-structured (even more if you are using TypeScript).

Based on your naming convention, you can easily map a script to a page making it a little easier to debug.

And isn't that what we all want?

Conclusion

This may have been a little long for a quick tip, but I feel it's a full-blown example to leverage how powerful Task Runners can be in Visual Studio 2019.

How are you using Gulp in Visual Studio 2019? Are you optimizing images? Share your tips below in the comments and let's discuss.

ASP.NET 8 Best Practices on Amazon

ASP.NET 8 Best Practices by Jonathan Danylko


Reviewed as a "comprehensive guide" and a "roadmap to excellence" with over 120 Best Practices for ASP.NET Core 8, Jonathan's first book by Packt Publishing explores proven techniques for every phase of the SDLC.

Learn industry-standard concepts to improve your coding, debugging, and deployment of ASP.NET Core websites.

Order now on Amazon.com button

Picture of Jonathan "JD" Danylko

Jonathan "JD" Danylko is an author, web architect, and entrepreneur who's been programming for over 30 years. He's developed websites for small, medium, and Fortune 500 companies since 1996.

He currently works at Insight Enterprises as an Architect.

When asked what he likes to do in his spare time, he replies, "I like to write and I like to code. I also like to write about code."

comments powered by Disqus