Search code examples
gulpbrowser-cachebrowserifyreactjs

Using Browserify with a revision manifest in Gulp, getting "Error: write after end"


I'm trying to compile a React app, append a hash to the output filename and produce a revision manifest, so that I can tell the browser to cache it forever. The problem is that the dozen or so tools involved don't all work together perfectly, at least as far as I can figure out by trying to assimilate all of their readmes and juggle .pipe calls.

Gulpfile: gist

Gulp task:

gulp.task( 'compile:js', function() {
    var browserifyTransform = transform( function( filename ) {
        return browserify( filename )
            .transform( reactify )
            .bundle();
    });

    return gulp.src( 'src/index.js' )
        .pipe( browserifyTransform )
        .pipe( sourcemaps.init({ loadMaps: true }))
        .pipe( uglify() )
        .pipe( rev() )
        .pipe( gulp.dest( 'static/dist' ));
});

update Simplified version, same error:

gulp.task( 'compile:js', function() {
    var browserifyTransform = transform( function( filename ) {
        return browserify( filename )
            .transform( reactify )
            .bundle();
    });

    return gulp.src( 'src/index.js' )
        .pipe( browserifyTransform )
        .pipe( gulp.dest( 'static/dist' ));
});

Error stack:

[15:55:04] Using gulpfile ~/Projects/pixsplodr/gulpfile.js
[15:55:04] Starting 'compile:js'...

Error: write after end
    at writeAfterEnd (/home/dan/Projects/pixsplodr/node_modules/browserify/node_modules/readable-stream/lib/_stream_writable.js:161:12)
    at Labeled.Writable.write (/home/dan/Projects/pixsplodr/node_modules/browserify/node_modules/readable-stream/lib/_stream_writable.js:208:5)
    at write (_stream_readable.js:601:24)
    at flow (_stream_readable.js:610:7)
    at _stream_readable.js:578:7
    at process._tickCallback (node.js:419:13)

Solution

  • Well, I have solution that satisfies my requirements, but it isn't perfect.

    1. Let Browserify start the stream from a file name, instead of using gulp.src
    2. Use vinyl-source-stream to transform Browserify's Node stream to a Gulp stream, before passing the data on to other gulp plugins

    I am also using gulp-rev to append a hash to generated filenames, and to create a manifest that maps them to their original filename.

    I was having trouble with source maps earlier too, but I have found that using Browserify transforms and settings seems to work better than using Gulp plugins on Browserify's output. That might be entirely due to user error, but I have been seeing so many conflicting Browserify / Gulp examples and minimal examples that I was unable to extend in an intuitive way, that I am just glad to have a working build with all of the features that I wanted.

    var gulp = require( 'gulp' );
    var gutil = require( 'gulp-util' );
    var watchify = require( 'watchify' );
    var browserify = require( 'browserify' );
    var source = require( 'vinyl-source-stream' );
    var buffer = require( 'vinyl-buffer' );
    var sass = require( 'gulp-sass' );
    var _ = require( 'lodash' );
    var rev = require( 'gulp-rev' );
    
    // Dev build
    gulp.task( 'watch', [ 'sass' ], function() {
        gulp.watch( 'node_modules/quiz/style/**/*.scss', [ 'sass' ]);
        builder( './src/app/index.js', true ).bundle( './dist/static', 'quiz-app.min.js' );
    });
    
    // Production build
    gulp.task( 'build', [ 'sass' ], function() {
        builder( './src/app/index.js', false ).bundle( './dist/static', 'quiz-app.min.js' );
    });
    
    // snip //
    
    function builder( entry, isDev ) {
        var bundler;
    
        if( isDev ) {
            bundler = watchify( browserify(
                './src/app/index.js',
                _.extend( watchify.args, { debug: true })
            ));
        } else {
            bundler = browserify(
                './src/app/index.js'
            );
        }
    
        bundler.transform( 'reactify' );
        bundler.transform( 'es6ify' );
        bundler.transform({ global: true }, 'uglifyify' );
    
        bundler.on( 'log', gutil.log ); // Help bundler log to the terminal
    
        function bundle( dest, filename ) {
            return bundler.bundle()
            .on( 'error', gutil.log.bind( gutil, 'Browserify error' )) // Log errors during build
            .pipe( source( filename ))
            .pipe( buffer() )
            .pipe( rev() )
            .pipe( gulp.dest( dest ))
            .pipe( rev.manifest() )
            .pipe( gulp.dest( dest ));
        }
    
        return { bundle: bundle };
    }
    

    I also tried just using the tools from NPM scripts, but I was unable to find a good way to do incremental builds while watching a set of files.