Using gulp plugins to transform files

In the last post, I explained how to use tasks, but I didn’t explain yet how to make them useful. Tasks are most useful when they automate certain file operations, and gulp helps with reading and writing files. However, if you need to perform a transformation, then you need to look for plugins that can get that work done for you.

In this post you’ll learn how to set up and use various plugins that can automate some common file operations.

As gulp documentation says: Gulp plugins are Node Transform Streams that encapsulate common behavior to transform files in a pipeline – often placed between src() and dest() using the .pipe() method.

Let’s imagine that:

  • You are creating a control add-in and you are writing a lot of JavaScript
  • You want to isolate your JavaScript features into individual files, one file per feature, and that you want to combine these files all into one file that will be included in your control add-in
  • You want to write modern JavaScript, but still want to make sure all browsers will be able to run it (remember, anybody using earlier versions of Windows than Windows 10, and using the NAV/BC universal client, will be running Internet Explorer in there)
  • You want to minify the resulting file to consume space
  • You want to zip the contents of your control add-in into the Resource.zip file

All valid assumptions, correct? If you are anything like me, this is what you do every day.

Now, some of these things can be done manually, some not really, but all of them certainly can be automated using gulp. Our job would need to contain the following transformations:

  1. Bundle JavaScript files into a single file
  2. Transpile that file into a lower version of JavaScript
  3. Minify that file
  4. Package the contents of the resource directory to produce the Resource.zip file

The more astute of you will immediately notice that all of this cannot be one task. Unless you want to complicate your life, one task should only perform those operations that can be handled with a single pipeline. A single pipeline is what starts with a stream of input files and transforms them until you cannot transform it. This means that as long as the output of the previous operation can be the input into the next operation, you can pipe that output down the pipeline. Packaging the contents of the resource directory is an operation that cannot use the output of the previous operation, so you need to isolate it into a separate task.

Another reason why you may want to separate operations into individual tasks is when an operation makes sense on its own. If you can imagine any of the previous operations as something you’d ever want to run individually, then that operation should also be isolated into a separate task. Tasks can be combined in different ways, and I’ll address that as well in one of the future posts, so don’t be afraid to split up operations that can benefit from splitting.

In my example, packaging the Resource.zip file is an operation that can be done independently of everything else. JavaScript is not the only content of the resource file: you may change CSS, you may add or modify images, configure the manifest, you name it. In all these situations, you may want to package the resource file independent of the JavaScript operation. It just makes sense to turn it into a task of its own.

Good, so we have two tasks then, with the following flow:

  1. Preparing JavaScript file
    1. Collect all JavaScript files into a single stream
    2. Pipe that into a function that combines all the files into one
    3. Pipe that into a function that transpiles your code to a lower version
    4. Pipe that into a function that minifies your code
    5. Store the result into a destination inside the resource subdirectory
  2. Zip the content of your resource subdirectory
    1. Collect all files underneath the resource subdirectory into a single stream
    2. Pipe that into a function that zips the content of the stream
    3. Store the result into a destination directory

Good. Now that we know what steps we need to do, let’s create the two tasks with minimum operations that gulp can do on its own:

const gulp = require("gulp");

function javaScript() {
    return gulp
        .src("src/*.js")
        .pipe(gulp.dest("resource/Script"));
}

function resourceZip() {
    return gulp
        .src("resource/**")
        .pipe(gulp.dest("build"));
}

So far, so good.

Let’s focus now on JavaScript task first. The first operation we need to do on the files is to bundle (or concatenate) them. If bundling the files was the only operation you need, then you wouldn’t really need a plugin as Node.js contains all the necessary APIs for that. However, if your bunding is just a pass-through operation in a pipeline, you’ll need a plugin. Let’s look for a plugin.

Normally, you’d do a general google search, but gulp maintains its own plugin directory which makes your life much easier. Go to https://gulpjs.com/plugins/ and search for “concatenate”, you’ll find a number of plugins. For my example, I chose gulp-concat. When I need a plugin, I look in its documentation to see whether it can do everything I need, and how simple it is to use. I also look at its github repository (all of plugins I used have a github repository) where I can see how much “alive” it is: how many commits and how often, are there pull requests merged into it, how many forks are out there, is the author responding to issues, etc. All of it can contribute to a perceived reliability rating that finally makes me choose one plugin over another.

Good. Now that we know that gulp-concat plugin can do the job for you, how do you use it?

If you remember the first post in the series, you can remember how you imported gulp itself into your workspace: by using npm. Gulp plugins are typically package, and to import them into your workspace, you’ll also use npm. Just remember, any gulp plugin packages that you import are dependencies in your development environment, therefore you must use the –save-dev option. Let’s import gulp-concat:

npm i gulp-concat --save-dev

Now that gulp-concat is a part of your solution and under the management of npm, you can use it inside your gulpfile.js script:

const concat = require("gulp-concat");

Now, we can use the concat in the pipeline like this:

.pipe(concat("controlAddIn.js"))

The next step is to transpile that using babel. Again, a little search should help you find the gulp-babel plugin that you can then first install into your workspace. However, if you simply use this, you may realize that it does not work.

npm i gulp-babel --save-dev

Some plugins use other packages that they don’t bundle directly, but allow you to choose specific versions of those packages, and sometimes they will provide different install scripts. To install gulp-babel with the necessary dependencies, you should use this:

npm i --save-dev gulp-babel @babel/core @babel/preset-env

… then declare inside the gulpfile.js script:

const babel = require("gulp-babel");

… and finally pipe the concatenated file into it:

.pipe(babel({ presets: ["@babel/env"] }))

Good job! Finally, we are ready for the last step of the first task – the minification. A quick search should reveal any number of possibilities, but I’ll go with this one:

npm i gulp-uglify --save-dev

When it’s installed, declare it:

const uglify = require("gulp-uglify");

… and then pipe the transpilation results into it:

.pipe(uglify())

And you are finished. If you did everything correctly, this is now your first task:

function javaScript() {
    return gulp
        .src("src/*.js")
        .pipe(concat("controlAddIn.js"))
        .pipe(babel({ presets: ["@babel/env"] }))
        .pipe(uglify())
        .pipe(gulp.dest("resource/Script"));
}

For the second task we need to locate a plugin that can zip a stream of files. Again, search the gulp plugin catalog, and you should discover gulp-zip. You are an expert by now, so you know that you first need to install it:

npm i gulp-zip --save-dev

… then declare it:

const zip = require("gulp-zip");

… and finally use it in your zip pipeline:

.pipe(zip("Resource.zip"))

If you did everything correctly, this is the second task:

function resourceZip() {
    return gulp
        .src("resource/**")
        .pipe(zip("Resource.zip"))
        .pipe(gulp.dest("build"));
}

Perfect. The only thing that’s missing is exporting the tasks from your gulpfile.js:

module.exports.javaScript = javaScript;
module.exports.resourceZip = resourceZip;

You can try to see if these two tasks now work:

gulp javaScript

Take a look inside your resource/Script directory and you should fine the controlAddIn.js script in there.

Then, run this:

gulp resourceZip

Now take a look inside the build directory and you should fine the Resource.zip file in there.

Cool, but now we need another task that first invokes the javaScript task and then the resourceZip task in sequence. Luckily, gulp helps with that, too. The gulp.series() method creates a task that runs specified tasks in a serial sequence, one after another. Just export one more task from your gulpfile.js:

module.exports.build = gulp.series(javascript, resourceZip);

If you now delete the files that previous two tests created, and then run this:

gulp build

… you will see that it has correctly built your Resource.zip file.

Just in case, here’s my latest state of the gulpfile.js:

const gulp = require("gulp");
const concat = require("gulp-concat");
const babel = require("gulp-babel");
const uglify = require("gulp-uglify");
const zip = require("gulp-zip");

function javaScript() {
    return gulp
        .src("src/*.js")
        .pipe(concat("controlAddIn.js"))
        .pipe(babel({ presets: ["@babel/env"] }))
        .pipe(uglify())
        .pipe(gulp.dest("resource/Script"));
}

function resourceZip() {
    return gulp
        .src("resource/**")
        .pipe(zip("Resource.zip"))
        .pipe(gulp.dest("build"));
}

module.exports.javaScript = javaScript;
module.exports.resourceZip = resourceZip;
module.exports.build = gulp.series(javaScript, resourceZip);

In my next post, I’ll cover two more topics: deploying the control add-in to an NAV / Business Central instance using PowerShell from a gulp task, and passing configuration to gulp tasks from JSON configuration files.

Vjeko

Vjeko has been writing code for living since 1995, and he has shared his knowledge and experience in presentations, articles, blogs, and elsewhere since 2002. Hopelessly curious, passionate about technology, avid language learner no matter human or computer.

This Post Has 3 Comments

  1. Bjarki

    Thanks for the posts!
    Is there any update regarding the next one in the series?

    “In my next post, I’ll cover two more topics: deploying the control add-in to an NAV / Business Central instance using PowerShell from a gulp task, and passing configuration to gulp tasks from JSON configuration files.”

    1. Vjeko

      Don’t know, I might come back to this.

      However, in two days at TechDays in my Control Add-ins session, I’ll talk about gulp a lot. And after the session I’ll post all my examples as a series of posts. See if any of that info will help you. And I don’t know, I might just come back to gulp at some point to add more stuff. It’s not that I don’t have anything to write about – I absolutely do (like my powershell execution, and zipping, and stuff…) – it’s only that I don’t have any time these days.

Leave a Reply