docs: function compileJs(srcDir, srcFilename, destDir, options) {
    options = options || {};
  
    if (options.minify) {
      const startTime = Date.now();
      return closureCompile(
          srcDir + srcFilename + '.js', destDir, options.minifiedName, options)
          .then(function() {
            fs.writeFileSync(destDir + '/version.txt', internalRuntimeVersion);
            if (options.latestName) {
              fs.copySync(
                  destDir + '/' + options.minifiedName,
                  destDir + '/' + options.latestName);
            }
          })
          .then(() => {
            endBuildStep('Minified', srcFilename + '.js', startTime);
          });
    }
  
    var bundler = browserify(srcDir + srcFilename + '-babel.js', {debug: true})
        .transform(babel, {loose: 'all'});
    if (options.watch) {
      bundler = watchify(bundler);
    }
  
    var wrapper = options.wrapper || '<%= contents %>';
  
    var lazybuild = lazypipe()
        .pipe(source, srcFilename + '-babel.js')
        .pipe(buffer)
        .pipe($$.replace, /\$internalRuntimeVersion\$/g, internalRuntimeVersion)
        .pipe($$.wrap, wrapper)
        .pipe($$.sourcemaps.init.bind($$.sourcemaps), {loadMaps: true});
  
    var lazywrite = lazypipe()
        .pipe($$.sourcemaps.write.bind($$.sourcemaps), './')
        .pipe(gulp.dest.bind(gulp), destDir);
  
    var destFilename = options.toName || srcFilename + '.js';
    function rebundle() {
      const startTime = Date.now();
      return toPromise(bundler.bundle()
        .on('error', function(err) {
          if (err instanceof SyntaxError) {
            console.error($$.util.colors.red('Syntax error:', err.message));
          } else {
            console.error($$.util.colors.red(err.message));
          }
        })
        .pipe(lazybuild())
        .pipe($$.rename(destFilename))
        .pipe(lazywrite())
        .on('end', function() {
        })).then(() => {
          endBuildStep('Compiled', srcFilename, startTime);
        });
    }
  
    if (options.watch) {
      bundler.on('update', function() {
        rebundle();
        // Touch file in unit test set. This triggers rebundling of tests because
        // karma only considers changes to tests files themselves re-bundle
        // worthy.
        touch('test/_init_tests.js');
      });
    }
  
    if (options.watch === false) {
      // Due to the two step build process, compileJs() is called twice, once with
      // options.watch set to true and, once with it set to false. However, we do
      // not need to call rebundle() twice. This avoids the duplicate compile seen
      // when you run `gulp watch` and touch a file.
      return Promise.resolve();
    } else {
      // This is the default options.watch === true case, and also covers the
      // `gulp build` / `gulp dist` cases where options.watch is undefined.
      return rebundle();
    }
  }