gulp - wrap plugin (which uses through2) output with string

1k views Asked by At

I would like to know how exactly can I manipulate the output of my Gulp plugin so, for example, no matter how many files are passed to the plugin, it will wrap the output with a string. Currently I cannot know when does the last file is done.

The super simplified example below will iterate on 3 files and will create a new file named output.js and in it there will be three times the string xxx (xxxxxxxxx).

I would like the plugin itself to wrap the contents so the output will be: +xxxxxxxxx+.

How can I do this? Thanks!

Gulpfile

var gulp        = require('gulp');
var concat      = require('gulp-concat');
var foo         = require('./index');

gulp.task('default', function() {
    gulp.src([a.html, b.html, c.html])
        .pipe(foo())
        .pipe(concat('output.js'))
        .pipe(gulp.dest('./test/output'))
});

The most basic gulp plugin (index.js):

var through2 = require('through2'),
    gutil    = require('gulp-util');

var PLUGIN_NAME = 'foo';

module.exports = function( options ){
    // through2.obj(fn) is a convenience wrapper around
    // through2({ objectMode: true }, fn)
    return through2.obj(function( file, enc, callback ){
        file.contents = new Buffer( 'xxx' );

        this.push(file);
        callback();
    });
}

I understand the files are currently simply returned modified, but what I don't understand is how to append text and return the concatenated result that I want, while keeping it OK with Gulp working standards.


The "real" plugin should actually wrap the files results with:

var foo = { FILES_CONTENT }

where FILES_CONTENT will actually be a a concatenated string of all the files:

"file_name" : "file_content",
"file_name" : "file_content",
...
2

There are 2 answers

1
Tal Avissar On

You should use the gulp pipeline technique (standard). This means that you can use the gulp-insert package in order to add the string xxx.

var insert = require('gulp-insert');

.pipe(insert.append('xxx')); // Appends 'xxx' to the contents of every file

You can also prepend, append and wrap with this package and it support of course the gulp standards.

So the full example will be:

var gulp        = require('gulp');
var concat      = require('gulp-concat');
var foo         = require('./index');
var insert      = require('gulp-insert');
gulp.task('default', function() {
    gulp.src([a.html, b.html, c.html])
        .pipe(foo()
        .pipe(insert.append('xxx'))
        .pipe(concat('output.js'))
        .pipe(gulp.dest('./test/output'))
});
1
Sven Schoenung On

I would make the following changes to your gulpfile.js:

var gulp        = require('gulp');
var foo         = require('./index.js');

gulp.task('default', function() {
   return gulp.src(['a.html', 'b.html', 'c.html'])
     .pipe(foo({fileName:'output.js', varName:'bar'}))
     .pipe(gulp.dest('./test/output'))
});

Since your foo() plugin itself will concatenate all the files, there's no need to use gulp-concat at all. Instead your plugin should accept an option fileName that provides the name of the generated file. I've also added another option varName that will provide the name of the var in the output file.

I'll assume that a.html, b.html and c.html are simple HTML files, something like this:

<h1 class="header">a</h1>

As you've already realized you need to concat all the files in the plugin itself. That's not really difficult however and doesn't require a lot of code. Here's a index.js which does exactly that:

var through2 = require('through2'),
    gutil    = require('gulp-util'),
    path     = require('path'),
    File     = require('vinyl');

var PLUGIN_NAME = 'foo';

module.exports = function(options) {
  var files = { };
  var outputFile = null;
  return through2.obj(function(file, enc, callback){
    outputFile = outputFile || file;
    var filePath = path.relative(file.base, file.path);
    files[filePath] = file.contents.toString();
    callback();
  }, function(callback) {
    outputFile = outputFile ? outputFile.clone() : new File();
    outputFile.path = path.resolve(outputFile.base, options.fileName);
    outputFile.contents = new Buffer(
      'var ' + options.varName + ' = ' +
      JSON.stringify(files, null, 2) + ';'
    );
    this.push(outputFile);
    callback();
  });
}

Since you want to output a key/value mapping from file names to file contents our transformFunction just stores both of those things in a regular JavaScript object files. None of the input files themselves are emitted. Their names and contents are just stored until we have all of them.

The only tricky part is making sure that we respect the .base property of each file as is customary for gulp plugins. This allows the user to provide a custom base folder using the base option in gulp.src().

Once all files have been processed through2 calls the flushFunction. In there we create our output file with the provided fileName (once again making sure we respect the .base property).

Creating the output file contents is then just a matter of serializing our files object using JSON.stringify() (which automatically takes care of any escaping that has to be done).

The resulting ./test/output/output.js will then look like this:

var bar = {
  "a.html": "<h1 class=\"header\">a</h1>\n",
  "b.html": "<h1 class=\"header\">b</h1>\n",
  "c.html": "<h1 class=\"header\">c</h1>\n"
};