github dribbble twitter facebook google arrow-up arrow-down arrow-left arrow-right

Search and Replace with Gulp

January 30, 2017 - Comments

Gulp is great! I’ve been playing around with it the past week and ended up completely rewriting my build scripts. I was surprised how easy and fast it was to use and just how many plugins are available.

While I was writing my scripts, I decided to create an automated way of managing the current version of my projects. Typically, I output versions in a few different places throughout a project. This always made it a pain when I’d push a new update because I would have to manually go into 5-6 files and update the version number. This usually led to me forgetting a few files, or just not updating the version at all.

So lets write a search and replace task in gulp! The first thing we’ll need to do is install gulp and gulp-replace locally, or just gulp-repace if you already have gulp installed:

# Install gulp and save as a dev dependency
npm install gulp --save-dev
# Install gulp-replace and save as a dev dependency
npm install gulp-replace --save-dev

Next, lets include our modules in gulpfile.js:

var
  gulp = require('gulp'),
  replace = require('gulp-replace')
;

Then we write our search and replace task, which we’ll just name replace:

gulp.task('replace', function() {
  return gulp.src(['**', '!./node_modules/**'], { base: './' })
    .pipe(replace('1.0.0', '1.0.1'))
    .pipe(gulp.dest('./'));
});

This is as simple as it gets. We can run it using:

gulp replace

If it’s not clear ['**', '!./node_modules/**'] will scan all files excluding anything in node_modules. The item that is searched for is the first string passed to the replace function (in our case, '1.0.0') and replaced with the second string ('1.0.1'). Lastly, we set the base parameter './' in the gulp.src function and pass it again to gulp.dest which will make sure to override our files instead of making a copy.

Lets improve this further. The first thing we want to do is remove the need to hardcode variables. Instead, lets allow them to be passed through the command line. To do this, we’ll need another node package minimist:

npm install minimist --save-dev

Lets include it in our module references. We also want to create two variables. The first is an object where we will store preset file search arrays and the other is where we store any passed variables using minimist:

var
  // Modules
  gulp = require('gulp'),
  replace = require('gulp-replace'),
  minimist = require('minimist'),

  // File sets to use for search and replace
  searchFiles = {
    exclude: []
  },

  // Save passed parameters to use in gulp tasks
  options = minimist(process.argv.slice(2))
;

Next we can update our task to use passed variables like so:

gulp.task('replace', function() {
  return gulp.src(String(options.f), { base: './' })
    .pipe(replace(String(options.s), String(options.r)))
    .pipe(gulp.dest('./'));
});

Because we’re using minimist, the parameters we pass in command line can be accessed using options.s for -s <SEARCH> or options.r for -r <REPLACE>, etc. Now we can run this task and pass in our search, replace and source files like this:

gulp replace -s 1.0.0 -r 1.0.1 -f 'file.js'

Now we’re cooking with fire! The last thing I’d like to do is improve how we manage files we search through. This is really what will determine how long this task will take and help us remove any potential for editing files we didn’t mean to edit. For example, if we know the files we want to search through, we can add those as search arrays in the searchFiles object instead of scanning our entire project. Lets also add an exclude array for the directories we’ll absolutely never want to edit:

searchFiles = {
  // Files that have our current version output
  version: [
    'README.md',
    'package.json',
    'src/scss/styles.scss',
    'src/js/scripts.js'
  ],
  // Files that output the current year
  year: [
    'README.md',
    'LICENSE',
    'src/scss/styles.scss',
    'src/js/scripts.js'
  ],
  // Files we never want to edit
  exclude: [
    '!./node_modules/**'
  ]
}

Now lets incorporate the search file arrays into our task while also allowing the option to pass custom file sources. We’ll also output a usage message if any of the required parameters are missing:

gulp.task('replace', function() {

  // Check if the exclude array exists and pass it or an empty array into `src`
  var src = typeof searchFiles['exclude'] != "undefined" ? searchFiles['exclude'] : [];

  // Check if all required parameters have been set
  if ((options.s == undefined) || (options.r == undefined) || (options.f == undefined)) {
    // If any are missing, output a usage error
    console.error('USAGE: gulp replace -s <SEARCH> -r <REPLACE> -f <FILES>');
  } else {
    // Check if the passed files is the key of an existing searchFiles array
    if (searchFiles[options.f] != undefined) {
      // If so, combine it with our excludes array
      src = searchFiles[options.f].concat(src);
    } else {
      // If not, create an array using the passed string (split by `,`)
      // and combine it with excludes array
      src = String(options.f)
        .replace(/\s+/g, '')
        .split(',')
        .concat(src);
    }

    // Run our task!
    return gulp.src(src, { base: './' })
      .pipe(replace(String(options.s), String(options.r)))
      .pipe(gulp.dest('./'));
  }

});

That’s it! We now have a super robust and safe gulp task for search and replace. We can add all files that contain the current version in our project to the searchFiles.version array and update our version with:

# Update our current version to a new one
gulp replace -s 1.0.0 -r 1.0.1 -f version

# Or, output a usage message if a parameter is missing
USAGE: gulp replace -s <SEARCH> -r <REPLACE> -f <FILES>

Comments