Writing a simple Gulp plugin tutorial

ยท

5 min read

In this simple Gulp plugin tutorial, I will be going through a plugin I wrote. The plugin is simple and transforms an input JSON file. Though Gulp was a great tool I fully leveraged for bundling front-end apps at its inception, I do not currently recommend using Gulp for new projects. You may, however, find yourself in an existing project that already uses Gulp. Keeping an existing Gulp build system is perfectly reasonable. This tutorial would be useful in that case. In order to gain value from this blog post, you should have some basic experience setting up Gulp to bundle your projects.

Our problem

Writing a Gulp plugin that transforms JSON would be useful when bundling a Chrome extension for distribution. In the final bundle, developers need to provide a manifest.json file. This manifest, among other things, declares lists of domains that you are authorizing your Chrome extension to use. Such domains can include your Chrome extension's back-end API server, for example, a value that changes based on the environment. A plugin that manipulates the manifest.json, adding/removing/replacing values depending on the environment, can be useful to publish different Chrome extension builds for each of the supported environments (development, staging, production). We currently have the following manifest.json file:

{
  "manifest_version": 2,
  "name": "My Cool Extension",
  "version": "1.0.0",
  "content_security_policy": "default-src 'self'; connect-src 'self' http://localhost:1337 https://staging.example.com https://app.example.com",
  "permissions": [
    "tabs",
    "storage",
    "cookies",
    "http://localhost:1337",
    "https://staging.example.com",
    "https://app.example.com"
  ]
}

For production, we would like the manifest.json file to look like this:

{
  "manifest_version": 2,
  "name": "My Cool Extension",
  "version": "1.0.0",
  "content_security_policy": "default-src 'self'; connect-src 'self' https://app.example.com",
  "permissions": [
    "tabs",
    "storage",
    "cookies",
    "https://app.example.com"
  ]
}

Our Gulp plugin needs to remove the localhost and staging entries (http://localhost:1337 and https://staging.example.com) from both the content security policy and the permissions list.

Structuring our Gulp plugin

Ideally we should structure our Gulp plugin in 2 files:

  1. Primary business logic
  2. The glue that ties the business logic to the Gulp context

When we break our plugin into 2 files instead of one, we can isolate our business logic into a simpler method that is a simple function that takes simple arguments. We can then write tests for our business logic alone. This makes our plugin's logic much easier to test, compared to if we had only one file and we needed to test it within the context of a Gulp stream.

The business logic

This file is all of the business logic. Given a file buffer as an argument, as well as any other arguments passed through from the Gulpfile, we can perform actions on the provided input.

// transform-manifest.js
const transformManifest = (opts = {}, manifestJsonBuffer) => {
  const manifest = JSON.parse(manifestJsonBuffer.contents.toString());

  // Modify CSP
  const csp = '[my new CSP that is possibly created by removoing things]'
  manifest.content_security_policy = csp;

  // Modify permissions
  const permissions = manifest.permissions.filter(myFilteringCriteria);
  manifest.permissions = permissions;

  /**
   * Rewrite file buffer
   */
  const stringContents = JSON.stringify(manifest, null, 2);
  const newBuffer = Buffer.alloc(stringContents.length, stringContents);
  manifestJsonBuffer.contents = newBuffer;

  return manifestJsonBuffer;
};


module.exports = transformManifest;

Our resulting file exports a function that takes a JSON file Buffer and returns a JSON file Buffer to continue down the Gulp stream.

The Gulp glue

This is the part that ties our primary business logic to the Gulp stream. The Gulp documentation provides multiple options for how to create plugins, but in this example I'll use the module through2 to help tie it together.

// gulp-transform-manifest.js
const through = require('through2');
const transformManifest = require('./transform-manifest');

module.exports = function (opts) {
  return through.obj(function(file, encoding, callback) {
    callback(null, transformManifest(opts, file));
  });
};

You can essentially just copy and paste this Gulp glue verbatim. Keep in mind that opts are options that you can pass in from the Gulpfile. In my Gulpfile, the implementation is as follows:

// Gulpfile.js
const gulp = require('gulp');
const transformManifest = require('./lib/gulp-transform-manifest');

gulp.task('process-manifest', () => {
  return gulp.src([
    './src/manifest.json'
  ])
  .pipe(transformManifest({ env: process.env.NODE_ENV }))
  .pipe(gulp.dest('./dist'));
});

In the package.json I call my my task like so:

NODE_ENV=production gulp dist

The Gulp task dist uses the process-manifest task as a dependency alongside other tasks. The environment variable is passed through from the executed NPM script to Gulp, which passes it through to our Gulp plugin.

Conclusion

That wraps up this simple Gulp plugin tutorial. Let's take a look at what we've learned:

  • Our plugin transforms JSON and passes it through the Gulp stream on to any following plugins to process
  • We can pass arguments from the invocation of the call in the NPM scripts, to the Gulpfile (where we can add additional options), all the way down to the business logic of our function
  • We've structured our plugin in such a way that we can write unit tests for the business logic of our plugin