I have created a very complex build process for front-end of one web app, which is being tested on Appveyor. If some parts of the app are not being built correctly with gulp, if some gulp tasks fail, how do I signal the Appveyor that the build has failed in its entirety?
To solve this problem, I have used instructions from this article. I had a need to separate build process into two similar parts: one for development environment and another one for production environment. Main difference was that production environment should always break if error is found in some of the tasks. Feodor Fitsner suggested that process should exit with non-zero error code.
Combining these two solutions, I created this small JS module that should be used as a wrapper for gulp tasks:
const msg = require('bit-message-box')
const chalk = require('chalk')
module.exports = (taskFn, production = false) => function(done) {
let onSuccess = () => {
done()
}
let onError = (err) => {
if (production) {
// If build process is initiated in production env, it should always break
// on error with exit code higher than zero. This is especially important
// for Appveyor CI
msg.error(`ERROR! BUILD PROCESS ABORTED!`)
console.error(chalk.bgRed.white(err))
process.exit(1)
}
else { done() }
}
let outStream = taskFn(onSuccess, onError);
if (outStream && typeof outStream.on === 'function') {
outStream.on('end', onSuccess);
}
}
Then in gulp itself, you can import this module and use it in a following way:
const gulp = require('gulp')
const handleCI = require('./handleCI')
const sass = require('gulp-sass')
const PRODUCTION = true // use your own system to decide if this is true or false
gulp.task('styles', handleCI((success, error) => {
return gulp.src('./scss/style.scss')
.pipe(
sass()
.on('error', error) // Add this to handle errors
)
.pipe(
gulp.dest('./styles/')
.on('error', error)
)
}, PRODUCTION))
When I use the stream() method like this it works well:
var gulp = require('gulp'),
watch = require('gulp-watch'),
browserSync = require('browser-sync').create();
gulp.task('watch', function () {
browserSync.init({
notify: false,
server: {
baseDir: "app"
}
});
watch('./app/assets/styles/**/*.css', gulp.series('cssInject'));
});
gulp.task('cssInject', function () {
return gulp.src('./app/temp/styles/styles.css').pipe(browserSync.stream());
});
When I add another task before executing the stream() only that task is accomplished.
gulp.task('cssInject', gulp.series('styles'), function () {
return gulp.src('./app/temp/styles/styles.css').pipe(browserSync.stream());
});
The console outputs then:
[15:16:00] Starting 'cssInject'...
[15:16:00] Starting 'styles'...
[15:16:00] Finished 'styles' after 50 ms
[15:16:00] Finished 'cssInject' after 53 ms
but CSS wasn't injected, the changes are applied only after manual reload.
I followed this course where they use few outdated packages with different syntax: e.g. the second argument of the task() is set as an array: ['styles']. It didn't work and I found out that you should use series() or paralell() instead.
There is a question on StackOverflow which looks the same as mine, but I didn't manage to find the answer for my issue there as I am gulp novice user.
Solved by: Defined the cssInject as separate task() and put it then into series() as second argument.
gulp.task('cssInject', function () {
return gulp.src('./app/temp/styles/styles.css').pipe(browserSync.stream());
});
gulp.task('manageCSS', gulp.series('styles', 'cssInject'));
Too much information about conditions tasks inside pipes (e. g. "gulp-if" plugin). However, actually it is not "conditional tasks": it is the "conditional plugin usage", and one task can use multiple plugins. Here is how to conditionally run task NOT inside pipe (for example, inside gulp.paralell())
Suppose that task name can contain spaces for providing easy-to-understand task meaning.
gulp.task('Build', gulp.paralell(
'Preprocess HTML',
'Prepeocess styles',
done => {
if(checkSomeCondition()){
runTask('some Task') // but how?
}
else {
done();
}
}
))
The beauty of gulp4.0 is that your tasks can just be functions, so the following works:
gulp.task('Preprocess HTML', function () {
console.log("in Preprocess HTML");
return gulp.src('./');
});
You can use either the above version (the 'old way') or the newer
way below.
I show two tasks here that use both versions but I personally wouldn't mix them.
// function PreprocessHTML() {
// console.log("in Preprocess HTML");
// return gulp.src('./');
// }
function PreprocessStyles() {
console.log("in Preprocess styles");
return gulp.src('./');
}
function testTaskTrue() {
console.log("in testTaskTrue");
return gulp.src('./');
}
function testTaskFalse() {
console.log("in testTaskFalse");
return gulp.src('./');
}
function checkSomeCondition() {
console.log("in checkSomeCondition");
return false;
}
// Again, I definitely wouldn't mix the two versions of tasks as shown below.
// Just here for demonstration purposes.
gulp.task('test', gulp.parallel( 'Preprocess HTML', PreprocessStyles,
done => {
if (checkSomeCondition()) {
// so testTaskTrue can be any gulp4.0 task, easy to call since it just a function
testTaskTrue();
}
else {
testTaskFalse();
}
done();
}
));
For gulp 4, first create this helper function:
function gulpTaskIf(condition, task) {
task = gulp.series(task) // make sure we have a function that takes callback as first argument
return function (cb) {
if (condition()) {
task(cb)
} else {
cb()
}
}
}
As its first argument, this helper takes a condition in the form of a function. The condition function is run at the time when the task execution is to start, so you can i.e. check output of previous steps in the condition function.
The second arguments specifies the task to be run and can be the same values that gulp.parallel() or gulp.series() accept as arguments, i.e. string, function reference, or a return value from another gulp.parallel() or gulp.series().
Returns a function that can be passedto gulp.task() as second argument or as an argument to gulp.parallel() or gulp.series() call.
Examples (first one matches question):
embedded in e.g. gulp.parallel() or gulp.series(), calling task by name
gulp.task('Build', gulp.parallel(
'Preprocess HTML',
'Prepeocess styles',
runTaskIf(checkSomeCondition, 'some Task')
))
as a task, calling task by name
function myTask() {
return gulp.src(...)
...
.dest(...)
}
gulp.task('my-task', myTask)
gulp.task('default', gulpTaskIf(
function () {
return Math.random() < 0.5; // example condition
},
'my-task')
as a standalone task, calling task by function reference
function myTask() {
return gulp.src(...)
...
.dest(...)
}
gulp.task('default', gulpTaskIf(() => Math.random() < 0.5, myTask)
as a standalone task, calling gulp.parallel() or gulp.series() reference
const manyTasks = gulp.parallel(task1, task2, task3)
gulp.task('default', gulpTaskIf(
function () {
return Math.random() < 0.5;
},
manyTasks)
It is really simple. No need of helper function:
gulp.task('Build', function() {
const tasks = ['Preprocess HTML', 'Preprocess styles'];
if(checkSomeCondition()) tasks.push('some Task');
return gulp.parallel(tasks);
}());
The clue is in calling the function at the last line - it will return an adjusted gulp.parallel task - I am using this to handle command line arguments (yargs)
WARNING: this will be executed before the first task is executed and will be executed also when other task than 'Build' is run. Just have it on your mind when implementing logic ;)
I have an array for plugins where I do a simple If query and then expand the array, see line 280 here.
Based on Gulp 4.
For the build process I changed the Const to Let and also queried it with If.
I'm trying to create two gulp tasks, and I'd like the second task to take the first one's output stream and keep applying plugins to it.
Can I pass the first task's return value to the second task?
The following doesn't work:
// first task to be run
gulp.task('concat', function() {
// returning a value to signal this is sync
return
gulp.src(['./src/js/*.js'])
.pipe(concat('app.js'))
.pipe(gulp.dest('./src'));
};
// second task to be run
// adding dependency
gulp.task('minify', ['concat'], function(stream) {
// trying to get first task's return stream
// and continue applying more plugins on it
stream
.pipe(uglify())
.pipe(rename({suffix: '.min'}))
.pipe(gulp.dest('./dest'));
};
gulp.task('default', ['minify']);
Is there any way to do this?
you can't pass stream to other task.
but you can use gulp-if module to skip some piped method depending on conditions.
var shouldMinify = (0 <= process.argv.indexOf('--uglify'));
gulp.task('script', function() {
return gulp.src(['./src/js/*.js'])
.pipe(concat('app.js'))
.pipe(gulpif(shouldMinify, uglify())
.pipe(gulpif(shouldMinify, rename({suffix: '.min'}))
.pipe(gulp.dest('./dest'));
});
execute task like this to minify
gulp script --minify
I am looking for the same solution, but ended up just chaining functions.
This is quite flexible.
Please share if anyone has better solution without using additional packages.
function concatenate() {
return gulp
.src(['./src/js/*.js'])
.pipe(concat('app.js'));
}
function minify() {
return this
.pipe(uglify())
.pipe(rename({suffix: '.min'}));
}
function output() {
return this.pipe(gulp.dest('./src'));
}
gulp.task('concat', function() {
return output.call(concatenate());
});
gulp.task('minify', function() {
return output.call(minify.call(concatenate()));
});
gulp.task('default', ['minify']);
The problem: I want to maintain 'collections' of files. This will help with build times, and flexibility. for example, everytime i edit my app.js file, I don't want to re-compile all my twitter bootstrap files.
I can certainly achieve this with 2 tasks and 2 watch declarations - the problem is that the tasks are identical save for the files array. Ideally I would like to pass through these as parameters in the watch declaration Is there a way to do something like the following psuedo-code?:
var files = {
scripts: [
'www/assets/scripts/plugins/**/*.js',
'www/assets/scripts/main.js',
],
vendor: [
'vendor/jquery/dist/jquery.js',
'vendor/jqueryui/ui/jquery.ui.widget.js',
'vendor/holderjs/holder.js'
],
};
...
gulp.task('js', ['lint'], function (files, output) {
return gulp.src(files)
.pipe(debug())
.pipe(concat(output))
.pipe(uglify({outSourceMap: true}))
.pipe(gulp.dest(targetJSDir))
.pipe(notify('JS minified'))
.on('error', gutil.log)
});
...
gulp.watch('scripts/**/*.js', ['lint', 'js'], files.scripts, 'app.min.js');
gulp.watch('vendor/**/*.js', ['lint', 'js'], files.vendor, 'vendor.min.js');
Flipping round another way: is to namespace the watch declaration that called the task? That way I could check which watch triggered the task, and conditional those things within the task itself.
the problem is that the tasks are identical save for the files array.
I believe lazypipe (see its gh page) is well
suited to your wants. This was an interesting problem. I'm going to try to answer both what I think you're asking about (which is satisfied by lazypipe) as well as what I think you're probably thinking about or would end up thinking about if you got past the parameterization of pipes issue.
One aspect of what we want is that we don't want to rerun jshint on files that haven't changed. Additionally, we want to keep it DRY, and we want to pick up new files in addition to changed ones.
This is tested and works for me:
var gulp = require('gulp');
var $ = require('gulp-load-plugins')();
var es = require('event-stream');
var lazypipe = require('lazypipe');
var gutil = require('gulp-util');
var path = require('path');
var files = {
scripts: ['src/**/*.js'],
vendor: ['vendor/**/*.js']
};
// sets up a lazy pipe that does jshint related stuff
function getJsMultiPipe(name) {
return lazypipe()
.pipe($.jshint)
.pipe($.jshint.reporter, 'jshint-stylish')
// if you don't want to fail on style errors remove/comment this out:
.pipe($.jshint.reporter, 'fail');
}
// sets up a lazy pipe that does concat and post-concat stuff
function getJsCombinedPipe(groupName, outfile) {
return lazypipe()
.pipe($.concat, outfile)
.pipe($.uglify, {outSourceMap: true})
.pipe(gulp.dest, 'build')
.pipe($.notify, {message: groupName + ' JS minified', onLast: true});
}
// sets up a pipe for the initial build task, combining the above two pipes
function getBuildPipe(groupName, outfile) {
return gulp.src(files[groupName])
.pipe(getJsMultiPipe(groupName)())
.pipe(getJsCombinedPipe(groupName, outfile)());
}
// sets up a watch pipe, such that only the changed file is jshinted,
// but all files are included in the concat steps
function setWatchPipe(groupName, outfile) {
return $.watch({
glob: files[groupName],
name: groupName,
emitOnGlob: false,
emit: 'one'
}, function(file, done) {
return file
.pipe($.debug({title: 'watch -- changed file'}))
.pipe(getJsMultiPipe(groupName)())
// switch context
.pipe(gulp.src(files[groupName]))
.pipe($.debug({title: 'watch -- entire group'}))
.pipe(getJsCombinedPipe(groupName, outfile)())
.pipe($.debug({title: 'watch -- concatted/source-mapped'}))
.pipe($.notify({message: 'JS minified', onLast: true}));
});
}
// task to do an initial full build
gulp.task('build', function() {
return es.merge(
getBuildPipe('scripts', 'app.min.js'),
getBuildPipe('vendor', 'vendor.min.js')
)
.pipe($.notify({message: 'JS minified', onLast: true}));
});
// task to do an initial full build and then set up watches for
// incremental change
gulp.task('watch', ['build'], function(done) {
setWatchPipe('scripts', 'app.min.js');
setWatchPipe('vendor', 'vendor.min.js');
done();
});
My dependencies look like:
"devDependencies": {
"jshint-stylish": "^0.1.5",
"gulp-concat": "^2.2.0",
"gulp-uglify": "^0.2.1",
"gulp-debug": "^0.3.0",
"gulp-notify": "^1.2.5",
"gulp-jshint": "^1.5.3",
"gulp": "^3.6.0",
"gulp-load-plugins": "^0.5.0",
"lazypipe": "^0.2.1",
"event-stream": "^3.1.1",
"gulp-util": "^2.2.14",
"gulp-watch": "^0.5.3"
}
EDIT: I just glanced at this again and I notice these lines:
// switch context
.pipe(gulp.src(files[groupName]))
Be aware that I believe the gulp.src API has changed since I wrote this, and that it currently doesn't switch the context when you pipe things into gulp.src, therefore this spot might require a change. For newer versions of gulp, I think what will happen is that you will be adding to the context, instead and presumably losing a small bit of efficiency.
You could write a wrapper function for tasks to capture parameters and pass it to the task. E.g. (with the help of the lodash library):
// We capture the options in this object. We use gulp.env as a base such that
// options from cli are also passed to the task.
var currentOpts = _.clone(gulp.env);
// Here we define a function that wraps a task such that it can receive
// an options object
function parameterized(taskFunc) {
return function() {
taskFunc.call(null, currentOpts);
}
}
// Here we create a function that can be used by gulp.watch to call
// a parameterized task. It can be passed an object of "task" : {options} pairs
// and it will return a task function that will capture these options
// before invoking the task.
function withArgs(tasks) {
return function() {
_.each(tasks, function (opts, task) {
currentOpts = _.extend(currentOpts, opts);
gulp.run(task);
currentOpts = _.clone(gulp.env);
});
}
}
var files = {
scripts : [ "src/**/*.js"],
vendor : ["vendor/**/*.js"
};
// We pass the task function to parameterized. This will create a wrapper
// function that will pass an options object to the actual task function
gulp.task("js", parameterized(function(opts) {
gulp.src(files[opts.target])
.pipe(concat(opts.output));
}));
gulp.task("watch", function() {
// The withArgs function creates a watch function that invokes
// tasks with an options argument
// In this case it will invoke the js task with the options object
// { target : "scripts", output : "scripts.min.js" }
gulp.watch(files.scripts, withArgs({
js : {
target : "scripts",
output : "scripts.min.js"
}
}));
gulp.watch(files.vendor, withArgs({
js : {
target : "vendor",
output : "vendor.min.js"
}
}));
});
I've faced the same problem - how to pass parameters to a gulp task. It's wierd that this feature is not builtin (it's such a common task to build, for instance, two versions of a package, parametrized task seems like a very DRY solution).
I wanted to make it as simple as possible, so my solution was to dynamically create tasks for an each possible parameter. It works ok if you have a small number of exactly defined values. It won't work for wide range values, like ints or floats.
The task definition is wrapped in a function that takes desired parameter and the parameter is appended to the task's name (with '$' between for convenience).
Your code could look like this:
function construct_js(myname, files, output) {
gulp.task('js$' + myname, ['lint'], function () {
return gulp.src(files)
.pipe(debug())
.pipe(concat(output))
.pipe(uglify({outSourceMap: true}))
.pipe(gulp.dest(targetJSDir))
.pipe(notify('JS minified'))
.on('error', gutil.log)
});
}
construct_js("app", files.scripts, 'app.min.js');
construct_js("vendor", files.vendor, 'vendor.min.js');
gulp.watch('scripts/**/*.js', ['lint', 'js$app']);
gulp.watch('vendor/**/*.js', ['lint', 'js$vendor']);
Or better, with a little change in the data definition, we invoke task generation in a loop (so if you add a new "version" in the configs array it will work right away:
var configs = [
{
name : "app",
output: 'app.min.js',
files: [ 'www/assets/scripts/plugins/**/*.js',
'www/assets/scripts/main.js',
]
},
{
name : "vendor",
output: 'vendor.min.js',
files: [ 'vendor/jquery/dist/jquery.js',
'vendor/jqueryui/ui/jquery.ui.widget.js',
'vendor/holderjs/holder.js'
]
}
];
function construct_js(taskConfig) {
gulp.task('js$' + taskConfig.name, ['lint'], function () {
return gulp.src(taskConfig.files)
.pipe(debug())
.pipe(concat(taskConfig.output))
.pipe(uglify({outSourceMap: true}))
.pipe(gulp.dest(targetJSDir))
.pipe(notify('JS minified'))
.on('error', gutil.log)
});
}
for (var i=0; i < configs.length; i++) {
construct_js(configs[i]);
}
If we use underscore for the last "for":
_(configs).each(construct_js);
I've used this approach in my project with good results.
I'd like to propose some alternatives. Suppose we have a task called build that we would like to conditionally uglify given a certain param.
The two approaches use two watches with a single build task.
Alternative #1:
You can use gulp-exec to fire up a task with parameters.
var exec = require('child_process').exec;
gulp.task('build', function(){
// Parse args here and determine whether to uglify or not
})
gulp.task('buildWithoutUglify' function(){
exec('gulp build --withoutUglify')
})
gulp.task('watch', function(){
gulp.watch(someFilePath, ['buildWithoutUglify'])
})
Please note that this approach is a bit slow since what it does is execute command line gulp.
Alternative #2:
Set a global variable:
var withUglify = false;
gulp.task('build', function(){
// Use something like ``gulp-if`` to conditionally uglify.
})
gulp.task('buildWithoutUglify' function(){
withUglify = true;
gulp.start('build');
})
gulp.task('watch', function(){
gulp.watch(someFilePath, ['buildWithoutUglify'])
})