javascriptnode.jsgulpgulp-rename

gulp var defined in one pipe is undefined in another


I am using gulp to processes pages for a site, some of these pages are php files. The problem is that after all the pages are run through the template engine they have a .html file extension. I am adding a property to the file that designates if it's supposed to be a file besides html and then renaming the file to match. However, for some reason gulp-rename keeps saying that the variable I am using to store the extension is undefined.

Corresponding task in gulpfile.js:

var gulp         = require('gulp'),
    gutil        = require('gulp-util'),
    lodash       = require('lodash'),
    data         = require('gulp-data'),
    filesize     = require('gulp-filesize'),
    frontMatter  = require('gulp-front-matter'),
    rename       = require('gulp-rename'),
    util         = require('util');    


gulp.task('metalsmith', function(){
var ext;                     //Variable to store file extension
return gulp.src(CONTENT_DIR)
    .pipe(frontMatter()).on("data", function(file){
        lodash.assign(file, file.frontMatter);
        delete file.frontMatter;
    })
    .pipe(data(function(file){                  //ext is defined here
        if(typeof file.filetype === 'undefined'){
            ext = {extname: '.html'};
        }else{
            ext = {extname: file.filetype};
        }
    }))
    .pipe(tap(function(file){
        console.log(ext);       //when I print to the console, ext is defined and correct
    }))
    .pipe(rename(ext)) //ext is undefined
    .pipe(gulp.dest(BUILD_DIR))
});

when I run the above it errors with Unsupported renaming parameter type supplied.

Also I have tried it having options obj on the same line as rename(), with ext only storing the file extension eg: .pipe(rename({extname: ext})) which causes the files to have undefined added as the extension (instead of phpfile.php the below md file would be named phpfileundefined)

yaml in phpfile.md

---
layout: php.hbt
filetype: ".php"
title: "php"
---

package.json

"devDependencies": {
    "gulp": "^3.9.1",
    "gulp-data": "^1.2.1",
    "gulp-filter": "^4.0.0",
    "gulp-front-matter": "^1.3.0",
    "gulp-rename": "^1.2.2",
    "gulp-util": "^3.0.7",
    "lodash": "^4.11.1"
  }

Solution

  • Your problem is that by the time you execute rename(ext) the value of ext is still undefined because your data(...) code hasn't run yet.

    Gulp plugins work like this:

    1. You invoke the plugin function and pass it any necessary parameter. In your case rename(ext).
    2. The plugin function returns a duplex stream
    3. You pass that duplex stream to .pipe()
    4. Only after all .pipe() calls have been executed does you stream start running.

    So rename(ext) is a function call that is used to construct the stream itself. Only after the stream has been constructed can the stream start running.

    However you set value of ext only once the stream is running. You need the value of ext before that when you are constructing the stream.

    The easiest solution in your case is to simply do the renaming manually in your data(...) function instead of relying on gulp-rename. You can use the node.js built-in path module for that (which is what gulp-rename uses under the hood):

    var path = require('path');
    
    gulp.task('metalsmith', function(){
      return gulp.src(CONTENT_DIR)
        .pipe(frontMatter()).on("data", function(file){
            lodash.assign(file, file.frontMatter);
            delete file.frontMatter;
        })
        .pipe(data(function(file){
            var ext;
            if(typeof file.filetype === 'undefined'){
                ext = '.html';
            }else{
                ext = file.filetype;
            }
            var parsedPath = path.parse(file.path);
            file.path = path.join(parsedPath.dir, parsedPath.name + ext);
        }))
        .pipe(gulp.dest(BUILD_DIR))
    });
    

    Alternatively you could also use gulp-foreach as I suggest in this answer to a similar question. However that's not really necessary in your case since you're already accessing the file object directly in data(...).

    EDIT: for completeness sake here's a version using gulp-foreach:

    var foreach = require('gulp-foreach');
    
    gulp.task('metalsmith', function(){
       return gulp.src(CONTENT_DIR)
        .pipe(frontMatter()).on("data", function(file){
            lodash.assign(file, file.frontMatter);
            delete file.frontMatter;
        })
        .pipe(foreach(function(stream, file){
            var ext;
            if(typeof file.filetype === 'undefined'){
                ext = {extname: '.html'};
            }else{
                ext = {extname: file.filetype};
            }
            return stream.pipe(rename(ext));
        }))
        .pipe(gulp.dest(BUILD_DIR))
    });