gulp: blanking output if Browserify fails - gulp

I'm using Browserify in my gulpfile.js like so:
browserify("src/main.js").bundle()
.on("end", function() {
console.log("compiled JavaScript via Browserify");
})
.on("error", function(err) {
console.warn("ERROR: Browserify failed", err);
})
.pipe(source("bundle.js"))
.pipe(gulp.dest("./dist"));
However, it's easy to miss compilation errors - so I'd prefer to blank dist/bundle.js (or insert an alert("ERROR");) instead of just reporting errors in the terminal.
My attempts to implement this failed due to what I assume is a race condition (pipe conclusion overwriting file operations in the "error" event handler), so I'd be grateful for some advice on how to do this properly.

If you think your issue is due to the asynchronicity of the code, you can use async or q to dictate the order in which your code executes (try async.waterfall, for example).

If you want to blank out a file on error, you can write a gulp stream handler that handles control structures that get generated in the stream and blanks the file out, otherwise just passes it through. Here is the code of the handler:
var through2 = require('through2');
var gutil = require('gulp-util');
var blank = function () {
var blank = false;
return through2.obj(
function (file, encoding, cb) {
if (!file.control && !blank) {
this.push(file);
} else if (file.control) {
blank = true;
} else if (file.path && blank) {
var newFile = new gutil.File({
base: file.base,
cwd: file.cwd,
path: file.path,
contents: new Buffer('')
});
this.push(newFile);
}
cb();
}, function (cb) {
cb();
});
};
Then you need to catch the error and generate a control structure. This can be done like this, in the on("error" handler, you place the following line of code:
this.push({"control": "failed"});
If you make the blank handler the last handler in the stream before the output, like this:
.pipe(blank())
.pipe(gulp.dest('./dist'));
Then you will have an empty file instead of the processed one. Of course you could modify this to write the error information to the file instead or do any number of things.
That having been said, why would you simply not want the processing to stop when you encounter this error? Any unhandled stream error should stop processing.

Related

Detect outputs in closure compiler's gulp plugin

I am using gulp and google closure compiler e.g.:
var compilerPackage = require('google-closure-compiler');
var closureCompiler = compilerPackage.gulp();
gulp.task('js-compile', function () {
return closureCompiler({
js: './src/js/**.js',
externs: compilerPackage.compiler.CONTRIB_PATH + '/externs/jquery-1.9.js',
compilation_level: 'ADVANCED',
warning_level: 'VERBOSE',
language_in: 'ECMASCRIPT6_STRICT',
language_out: 'ECMASCRIPT5_STRICT',
output_wrapper: '(function(){\n%output%\n}).call(this)',
js_output_file: 'output.min.js'
})
.src() // needed to force the plugin to run without gulp.src
.pipe(gulp.dest('./dist/js'));
});
Is it possible do detect outputs (warning and error)? For example like this:
.on("error", function(err) {})
.on("warning", function(warn) {});
Thank you for your help

Gulp task executed by watch using cached config stream

I'm trying use gulp to bundle and minify my files using gulp-bundle-assets. Running the tasks on their own is fine. My problem is using gulp.watch to watch for any changes in my config file and re-bundle my scripts.
The first time the watch executes everything works correctly. On successive occasions everything runs, but the exact same files are bundled - any changes in the config are ignored.
If I run my "bundle" task while the watch is running, "bundle" will use the current configuration. While successive watches will continue to use the configuration on the first execution.
My guess would be the data for the stream retrieved by gulp.src is cached. So how do I tell it to always get the latest version?
var gulp = require('gulp');
var bundle = require('gulp-bundle-assets');
var del = require('del');
var index = 0;
gulp.task('bundle', function () {
console.log('Bundling files ' + (index++));
return gulp.src('./bundle.config.js')
.pipe(bundle())
.pipe(gulp.dest('./bundles'));
});
gulp.task('watch', function () {
gulp.watch(['./scripts/**/*.{js,css}', './bundle.config.js'], ['clean', 'bundle']);
});
gulp.task('clean', function (cb) {
console.log('Cleaning files');
del(['./bundles/**/*'], cb);
});
An alternative I tried was to use watch(...).on, and calling gulp.run, but that didn't fix the problem, either. I also tried pasting the code from the bundle task in to the on callback, but still got the same result.
The culprit isn't gulp.src(), but bundle(). The gulp-bundle-assets plugin uses require() to load your bundle.config.js. Since Node.js caches return values from require() you always get the same config object after the file is loaded for the first time.
The solution is to invalidate the require cache in your bundle task:
var gulp = require('gulp');
var bundle = require('gulp-bundle-assets');
var del = require('del');
var index = 0;
gulp.task('bundle', ['clean'], function () { // run clean task before bundle task
// invalidate require cache for ./bundle.config.js
delete require.cache[require.resolve('./bundle.config.js')];
console.log('Bundling files ' + (index++));
return gulp.src('./bundle.config.js')
.pipe(bundle())
.pipe(gulp.dest('./bundles'));
});
gulp.task('watch', function () {
gulp.watch(['./scripts/**/*.{js,css}',
'./bundle.config.js'], ['bundle']); // only run bundle task
});
gulp.task('clean', function () {
console.log('Cleaning files');
return del(['./bundles/**/*']); // return promise object
});
Unrelated to your problem, but I also fixed your clean task. The way you had it set up didn't work.

Gulp and glob-stream task finishing too soon

I have a Gulp task that uses glob-stream to recursively loop through directories and files to perform a task, similar to below, but far more elaborate:
var gs = require('glob-stream');
var config = {
PATH: 'some/path/*.*'
}
function doSomething(filePath) {
var stream = gs.create(filePath);
// Do something
return gs.on('data', doSomething);
}
gulp.task('compile', function() {
var filePath = config.PATH;
return doSomething(filePath);
});
I can have the task achieve the results and compile what I need, but unfortunately Gulp believes the task has finished while it's still running, causing issues in my build process - How can I avoid this? I'm already using run-sequence but to no effect.
Why are you manually walking the directory tree with a recursive function? Why not just let glob-stream do the work for you? Then you only have to take care of the //Do something part:
var config = {
PATH: 'some/path/**' //glob pattern for all subfolder and files
};
function doSomething(filePath) {
//Do something
}
gulp.task('compile', function() {
var stream = gs.create(config.PATH);
stream.on('data', doSomething);
return stream;
});
gulp.task('secondTask', function() {
console.log('secondTask');
});
gulp.task('default', function() {
runSequence('compile', 'secondTask');
});
The some/path/** glob pattern creates a stream of all folders and files below some/path/, so you don't have to implement the recursive tree walk yourself.
Note that the compile task returns the stream. Otherwise gulp can't tell when the compile task has completed and starts running secondTask before compile has finished.

Gulp task - unable to set variable value

I'm using git-rev,gulp-header and run-sequence and trying to add some info - as well as - git commit number automatically to app.js file during building process.
here's the code I have so far:
var runSequence = require('gulp-run-sequence');
var git = require('git-rev');
var header = require('gulp-header');
var pkg = require('./info.json');
var paths = {addHeader: ['./www/js/app.js'], ...}
var commit, timestamp;
function getGitInfo() {
git.short(function (str) {
commit = str;
console.log(str);
});
};
var banner = ['"commit":"' + commit + '",',
'"timestamp":"' + timestamp + '",',
'"appVersion":"<%= pkg.appVersion %>",',
'"appReleaseDate":"<%= pkg.appReleaseDate %>"',
'};\n',
''].join('\n');
gulp.task('get-git-info', getGitInfo());
gulp.task('add-header', function () {
return gulp.src(paths.addHeader)
.pipe(header(banner, {pkg: pkg}))
.pipe(gulp.dest('./www-dev/js/'))
});
gulp.task('build', function (){
runSequence('get-git-info','add-header');
})
the console result is right, I have the commit number, but in app.js all I get is undefined:
aboutPage={
"appVersion":"5.0.0",
"appReleaseDate":"10/02/2016",
"commit":"undefined",
"timestamp":"undefined"
};
I am talking about commit, and NOT timestamp. I'm going to worry about timestamp later.
Any idea what am I doing wrong here?
thanks
There are a couple of things wrong with your Gulpfile:
Your banner variable is initialized before any of your tasks are even defined, let alone have been executed. That means that commit is still undefined when you initialize banner.
gulp.task expects a function as the task body. However the task body for get-git-info in your Gulpfile is getGitInfo(). That means you execute the getGitInfo function and assign the return value of that function call as the task body of get-git-info. Which in your case is undefined.
Even if you had assigned the getGitInfo function itself as the task body (instead of its return value), it still wouldn't work because git.short() is asynchronous. That means that get-git-info returns and add-header is run before your callback function to git.short() is called with the commit-id.
Here's a solution that addresses all three of these problems:
function banner() {
return [
'"commit":"' + commit + '",',
'"timestamp":"' + timestamp + '",',
'"appVersion":"<%= pkg.appVersion %>",',
'"appReleaseDate":"<%= pkg.appReleaseDate %>"',
'};\n',
''
].join('\n');
}
gulp.task('get-git-info', function(done) {
git.short(function (str) {
commit = str;
console.log(str);
done();
});
});
gulp.task('add-header', function () {
return gulp.src(paths.addHeader)
.pipe(header(banner(), {pkg: pkg}))
.pipe(gulp.dest('./www-dev/js/'))
});
banner is now a function so the commit variable isn't accessed until it has been initialized.
getGitInfo is gone. Instead an anonymous function is used as the task body for get-git-info, so it's dead obvious that we are in fact assigning a function here.
The anonymous function accepts a callback done which is called once the commit-id is available. This signals to gulp that get-git-info is finished and that runSequence can proceed with the add-header task.

Copy/Deletion in Gulp randomly gives ENOENT

New to Gulp. My default task is using the pluginrun-sequence which tells task deleteBuild to run, then makeBuild.
Randomly, I am getting an ENOENT error which seems to be telling me that I'm either referencing files that don't exist for deletion or copy. My tasks are:
deleteBuild:
gulp.task('deleteBuild', function(done) {
var del = require('del');
del(['build/**/*'], done);
});
makeBuild:
gulp.task('makeBuild', function() {
var stream = gulp.src(['src/**/*'], { base: 'src/' })
.pipe(gulp.dest('build/');
});
Can someone inform me as to how to best address this issue? I'm hoping to seek a low-level understanding rather than to be shown a solution w/o an explanation. Thanks.
Aside: I tried the deleteBuild without a callback function as well, under the assumption that, as is, it would perform the deletion and only continue to the next task once it is complete, though this doesn't seem to be what is happening.
That's probably because the deleteBuild does not return a gulp stream and thus leave the pipe broken. I would propose the following:
gulp.task('deleteBuild', function() {
var del = require('del');
var vinylPaths = require('vinyl-paths');
return gulp.src(['build']) // no need for glob, just delete the build directory
.pipe(vinylPaths(del));
});
gulp.task('makeBuild', function() {
var stream = gulp.src(['src/**/*'], { base: 'src/' })
.pipe(gulp.dest('build/');
});
gulp.task('default', function(cb) {
var runSequence = require('run-sequence');
runSequence('deleteBuild', ['makeBuild'], cb);
});
These tasks will first delete the build directory before executing the makeBuild task.
You'll need to install one additional plugin:
npm install vinyl-paths
For a ready to use example, please take a look a the gulpfile of skeletonSPA. This works for me ;-)