77

In my gulpfile I have a version number in a string. I'd like to write the version number to a file. Is there a nice way to do this in Gulp, or should I be looking at more general NodeJS APIs?

Daryl
  • 1,023
  • 1
  • 8
  • 9

9 Answers9

79

If you'd like to do this in a gulp-like way, you can create a stream of "fake" vinyl files and call pipe per usual. Here's a function for creating the stream. "stream" is a core module, so you don't need to install anything:

const Vinyl = require('vinyl')

function string_src(filename, string) {
  var src = require('stream').Readable({ objectMode: true })
  src._read = function () {
    this.push(new Vinyl({
      cwd: "",
      base: "",
      path: filename,
      contents: Buffer.from(string, 'utf-8')
    }))
    this.push(null)
  }
  return src
}

You can use it like this:

gulp.task('version', function () {
  var pkg = require('package.json')
  return string_src("version", pkg.version)
    .pipe(gulp.dest('build/'))
})
m93a
  • 8,866
  • 9
  • 40
  • 58
  • I ended up doing basically this, but with event-stream's [readArray](https://github.com/dominictarr/event-stream#readarray-array) to create the stream. – Daryl May 02 '14 at 20:13
  • 2
    For those who aren't up to speed on streams, you can have this work on multiple files strings by repeating the two lines within `_read` for each file. In this instance, an array of file objects would be a better `sting_src` parameter. – Wes Johnson Aug 23 '14 at 00:56
  • This covered my use case of creating a stream to pipe with other Gulp plugins down the road. – Claudia Dec 14 '14 at 23:03
  • 4
    If anyone else is as unfamiliar with node as me, node provides `Buffer` as a global (https://nodejs.org/api/buffer.html). – contrebis Jul 17 '15 at 14:35
  • This worked for me but `this.push(null)` throws an error `stream.push() after EOF` for me, not sure why. Commenting out that line works but then gulp hangs indefinitely. – justin.m.chase Jul 05 '16 at 23:15
  • @Daryl can you please share your code somewhere? I'd like to do this for multiple files but I'm new to pipes in `nodejs` – Thatkookooguy Sep 28 '16 at 10:08
  • This doesn't appear to work any more. Not sure which one of the myriad of dependencies in gulp has caused this to fail though. – Peter Donker Jun 27 '17 at 09:13
  • **Note**: `new Buffer()` is [deprecated since v6](https://nodejs.org/api/buffer.html#buffer_new_buffer_string_encoding). Use `Buffer.from()` instead. – zypA13510 Jul 16 '19 at 06:35
75

It's pretty much a one-liner in node:

require('fs').writeFileSync('dist/version.txt', '1.2.3');

Or from package.json:

var pkg = require('./package.json');
var fs = require('fs');
fs.writeFileSync('dist/version.txt', 'Version: ' + pkg.version);

I'm using it to specify a build date in an easily-accessible file, so I use this code before the usual return gulp.src(...) in the build task:

require('fs').writeFileSync('dist/build-date.txt', new Date());
fregante
  • 29,050
  • 14
  • 119
  • 159
  • This is probably the best way to do this (unless you're using ShellJS). – Claudia Dec 14 '14 at 23:02
  • 1
    `writeFile` function is now asynchronous and requires a callback. You might want to use 'writeFileSync' or better being reactive and pass a callback to do something when the file is actually written on disk. – monzonj Jan 05 '15 at 22:21
  • 2
    How does one go about chaining (pipe?) to access the file created from this? Since it's more of a node method, I cannot gain access to the file created within the same build task. – beauXjames Jun 24 '15 at 20:18
  • 2
    @beauXjames you can use [mjhasbach's solution](http://stackoverflow.com/a/30455405/288906) or just add `gulp.src('dist/version.txt').pipe(…)` after the `.writeFileSync` line. – fregante Aug 09 '15 at 07:40
  • I just threw this in before one of my tasks and worked like a charm. – Nick Pineda Jun 07 '16 at 23:28
  • Thanks, it is only way to do on windows machine! – Maciej Sikora Nov 09 '16 at 12:35
  • 2
    Loved it. I used this to generate a random string to use as a cache buster. https://gist.github.com/cmeza/e5488609d09722bd2c82e954859f36cf – cmeza Aug 17 '17 at 15:09
28

This can also be done with vinyl-source-stream. See this document in the gulp repository.

var gulp = require('gulp'),
    source = require('vinyl-source-stream');

gulp.task('some-task', function() {
    var stream = source('file.txt');

    stream.end('some data');
    stream.pipe(gulp.dest('output'));
});
mjhasbach
  • 906
  • 9
  • 18
  • This is probably the cleanest node way if you want to use `.pipe()` – fregante Aug 07 '15 at 21:26
  • Could you explain the `process.nextTick(fn() { stream.end(); }`? – Chic Sep 21 '15 at 15:56
  • @Chic I ended the stream in a `process.nextTick` callback because that is how it was done in the gulp documentation. It does not appear to be necessary, so I updated the answer. Thanks for pointing that out. – mjhasbach Sep 23 '15 at 05:19
26

According to the maintainer of Gulp, the preferred way to write a string to a file is using fs.writeFile with the task callback.

var fs = require('fs');
var gulp = require('gulp');

gulp.task('taskname', function(cb){
  fs.writeFile('filename.txt', 'contents', cb);
});

Source: https://github.com/gulpjs/gulp/issues/332#issuecomment-36970935

GOTO 0
  • 42,323
  • 22
  • 125
  • 158
  • Only problem with this is it requires the file to exist... isn't there a flag that allows this file to be created if it doesn't exist? – Serj Sagan Dec 29 '17 at 05:34
  • @SerjSagan Not sure what could cause this behavior. [writeFile](https://nodejs.org/api/fs.html#fs_fs_writefile_file_data_options_callback) with the default settings creates the file if it doesn't exist. – GOTO 0 Dec 29 '17 at 08:13
17

You can also use gulp-file:

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

gulp.task('version', function () {
    var pkg = require('package.json')

    return gulp.src('src/**')
        .pipe(file('version', pkg.version))
        .pipe(gulp.dest('build/'))
});

or without using gulp.src():

gulp.task('version', function () {
    var pkg = require('package.json')

    return file('version', pkg.version, {src: true})
        .pipe(gulp.dest('build/'))
});
Dave James Miller
  • 4,928
  • 3
  • 20
  • 18
5

The gulp-header package can be used to prefix files with header banners.

eg. This will inject a banner into the header of your javascript files.

var header = require('gulp-header');
var pkg = require('./package.json');
var banner = ['/**',
  ' * <%= pkg.name %> - <%= pkg.description %>',
  ' * @version v<%= pkg.version %>',
  ' * @link <%= pkg.homepage %>',
  ' * @license <%= pkg.license %>',
  ' */',
  ''].join('\n');

gulp.src('./foo/*.js')
  .pipe(header(banner, { pkg: pkg } ))
  .pipe(gulp.dest('./dist/')

Gulp is a streaming build system leveraging pipes.

If you simply want to write a new file with an arbitrary string, you can use built in node fs object.

Mike Causer
  • 8,196
  • 2
  • 43
  • 63
3

Using the string-to-stream and vinyl-source-stream modules:

var str = require('string-to-stream');
var source = require('vinyl-source-stream');
var gulp = require('gulp');

str('1.4.27').pipe(source('version.txt')).pipe(gulp.dest('dist'));
Jonas Berlin
  • 3,344
  • 1
  • 27
  • 33
3

Here's an answer that works in 2019.

Plugin:

var Vinyl = require('vinyl');
var through = require('through2');
var path = require('path');

// https://github.com/gulpjs/gulp/tree/master/docs/writing-a-plugin#modifying-file-content
function stringSrc(filename, string) {
    /**
     * @this {Transform}
     */
    var transform = function(file, encoding, callback) {
        if (path.basename(file.relative) === 'package.json') {
            file.contents = Buffer.from(
                JSON.stringify({
                    name: 'modified-package',
                    version: '1.0.0',
                }),
            );
        }

        // if you want to create multiple files, use this.push and provide empty callback() call instead
        // this.push(file);
        // callback();

        callback(null, file);
    };

    return through.obj(transform);
}

And in your gulp pipeline:

gulp.src([
    ...
])
.pipe(stringSrc('version.json', '123'))
.pipe(gulp.dest(destinationPath))

From source: https://github.com/gulpjs/gulp/tree/master/docs/writing-a-plugin#modifying-file-content

The function parameter that you pass to through.obj() is a _transform function which will operate on the input file. You may also provide an optional _flush function if you need to emit a bit more data at the end of the stream.

From within your transform function call this.push(file) 0 or more times to pass along transformed/cloned files. You don't need to call this.push(file) if you provide all output to the callback() function.

Call the callback function only when the current file (stream/buffer) is completely consumed. If an error is encountered, pass it as the first argument to the callback, otherwise set it to null. If you have passed all output data to this.push() you can omit the second argument to the callback.

Generally, a gulp plugin would update file.contents and then choose to either:

call callback(null, file) or make one call to this.push(file)

William Bernting
  • 561
  • 4
  • 15
1

This can also be achieved using gulp-tap

This can be especially helpful if you have identified multiple files that require this header. Here is relevant code (Also from gulp-tap documentation)

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

gulp.src("src/**")
    .pipe(tap(function(file){
           file.contents = Buffer.concat([
             new Buffer('Some Version Header', 'utf8'),
             file.contents
           ]);
         }))
    .pipe(gulp.dest('dist');
Rimian
  • 36,864
  • 16
  • 117
  • 117
Vikram
  • 4,162
  • 8
  • 43
  • 65