gulp 4 mulitple SPA task composition with series and parallel - gulp

I have a large build that is composed of multiple sub-projects that are all built from a central set of shared gulp scripts.
Say I want to do this (there are other modules that define the actual tasks for the sub-projects):
var apps = [...]; //list of configuration objects for sub-projects in question
function builds() {
var buildNames = apps.map(function (app) {
return "release-build-" + app.name;
});
return gulp.parallel(buildNames);
}
function tests() {
var testNames = apps.map(function (app) {
return "test-" + app.name;
});
return gulp.series(testNames);
}
gulp.task("test", tests);
gulp.task("release-build", gulp.series(tests, builds));
In gulp 3 I do the following (the code for the release-build task follows the same pattern as test):
var runSequence = require("run-sequence").use(gulp);
gulp.task("test", function (callback) {
// Get the test tasks of all children
var tasks = apps.map(function(app) {
return "test-" + app.name;
});
tasks.push(callback);
//run tasks sequentially
return runSequence.apply(null, tasks);
});
However when I try to run the new gulp 4 version I get the dreaded:
The following tasks did not complete 'test'
Did you forget to signal async completion?
How can I build my tasks names and pass them to gulp.series() or gulp.parallel() in gulp 4 like I did with run-sequence?

Your builds and tests functions generate gulp.parallel() and gulp.series() task compositions. What you want to do is assign those generated task compositions as your test and release-build tasks.
What you are currently doing is to assign the builds and tests functions themselves as your tasks instead of their return values.
You need to actually invoke the builds and tests functions. So instead of this:
gulp.task("test", tests);
gulp.task("release-build", gulp.series(tests, builds));
You need to do this:
gulp.task("test", tests());
gulp.task("release-build", gulp.series(tests(), builds()));

Related

Gulp - passing a paths array to tasks runs but only the last task completes as expected

This small snippet below attempts to copy two projects, projA and projB from their folders into the gulp folder. It passes the paths for the two folders via an array. The code executes correctly but only the last path in the array. So only projB is copied over.
`const gulp = require('gulp');`
`var pathsToProj = [ // source // base destination
['../../projA/eb_aws/**/*.*', 'projA/eb_aws', 'gulp-proj1/src/projA/eb_aws'],
['../../projB/eb_aws/**/*.*', 'projB/eb_aws', 'gulp-proj1/src/projB/eb_aws'],
];
pathsToProj.forEach(pathToProj => {`
gulp.task('copyFiles', function(){
return gulp.src(pathToProj[0], {base: pathToProj[1]})
.pipe(gulp.dest(pathToProj[2]));
});
gulp.task('default', gulp.series('copyFiles', async function (cb){
cb();
}));
});
Another anomaly is that the project folder is copied to /gulp-proj1/ (/gulp/proj1/projB) and not to /gulp-proj1/src/ as I intended it to be.
Any help to resolve this is appreciated. Thanks.
It is because of the combination of forEach and gulp's asynchronous nature. The forEach's will quickly cycle through - without waiting for each function within to complete. So before the first copyFile completes it is called again, which restarts the task and only the last one typically completes. It is entirely dependent on how fast or slow your internal task takes which is not good. That internal function should be synchronous to ensure it does what you expect.
For further discussion, see, e.g., Using async/await with a forEach loop and similar. Also see
MDN forEach:
forEach expects a synchronous function
forEach does not wait for promises. Kindly make sure you are aware of
the implications while using promises(or async functions) as forEach
callback.
So basically any other for type loop would work. Here is a much simpler version of your code that works:
// gulp.task('copyFiles', function (cb) {
function copyFiles(cb) {
for (const pathToProj of pathsToProj) {
gulp.src(pathToProj[0])
.pipe(gulp.dest(pathToProj[1]));
};
cb();
};
gulp.task('default', gulp.series(copyFiles));
Note for testing purposes I focused on this one issue and not your second question about the destination folder.
I modified the code snippet by Mark above as follows to get the desired output:
tasks = function copyFiles(cb) {
var paths = new Array();
for (const pathToProj of pathsToProj) {
paths.push(gulp.src(pathToProj[0], {base: pathToProj[1]})
.pipe(gulp.dest(pathToProj[2])));
};
cb();
return paths
};
gulp.task('default', gulp.series(tasks) );
However, I still cannot get the folders copied where I want. They end up at /gulp-proj1/ instead of ending up at /gulp-proj1/src/. Thanks.

Are tasks in gulp run in sequences?

Basic question but i just cannot find answer yet.
var gulp = require('gulp');
gulp.task('one', function(cb) {
// do stuff -- async or otherwise
cb(err);
});
gulp.task('two', function(cb) {
// do something
cb(err)
});
gulp.task('three', function(cb) {
// do something
cb(err)
});
The Q is: does task 2 only runs when task 1 finishes, task 3 only runs when task 2 finishes ?
With just this setup, only one task will be executed at all, e.g. task two if you invoke gulp two.
You can create composite tasks with the help of the functions series(...) and parallel(...) provided by Gulp. The new task will run the tasks passed to the function either in sequence or in parallel. Calls to the functions can be nested to create more complex scenarios.

Creating multiple output files from a gulp task

I'm learning the gulp way of doing things after using grunt exclusively in the past. I'm struggling to understand how to pass multiple inputs to get multiple outputs w/gulp.
Let's say I have a large project that has specialized js on a per page basis:
The Grunt Way:
grunt.initConfig({
uglify: {
my_target: {
files: {
'dest/everypage.min.js': ['src/jquery.js', 'src/navigation.js'],
'dest/special-page.min.js': ['src/vendor/handlebars.js', 'src/something-else.js']
}
}
}
});
This may be a poor example as it violates the "do only one thing" principle since grunt-uglify is concatenating and uglifying. In any event I'm interested in learning how to accomplish the same thing using gulp.
Thanks to #AnilNatha I'm starting to think with more of a Gulp mindset.
For my case I have a load of files that need to be concatenated. I offloaded these to a config object that my concat task iterates over:
// Could be moved to another file and `required` in.
var files = {
'polyfills.js': ['js/vendor/picturefill.js', 'js/vendor/augment.js'],
'map.js': [
'js/vendor/leaflet.js',
'js/vendor/leaflet.markercluster.min.js',
'js/vendor/jquery.easyModal.js',
'js/vendor/jquery-autocomplete.min.js',
'js/vendor/underscore.1.8.3.js',
'js/map.js'
],
...
};
var output = './build/js';
// Using underscore.js pass the key/value pair to custom concat function
gulp.task('concat', function (done) {
_.each(files, concat);
// bs.reload(); if you're using browsersync
done(); // tell gulp this asynchronous process is complete
});
// Custom concat function
function concat(files, dest) {
return gulp.src(files)
.pipe($.concat(dest))
.pipe(gulp.dest(output));
}

How to run gup task in series

I am new to gulp.
I have written two task that need to be performed. When I run them separately, they work fine. But when I combine them, the "replace" does not work.
gulp.task('bundle-source', function () {
return bundler.bundle(config);
});
gulp.task('bundle-config', function(){
return gulp.src(['config.js'])
.pipe(replace('src/*', 'dist/*'))
.pipe(gulp.dest(''));
});
gulp.task('bundle', ['bundle-config', 'bundle-source']);
I think the issue is that they both manipulate config.js. I think the second task when it saves to disk overwrites the change the first one made. The second task is about 30 seconds.
Gulp tasks are run in parallel by default. So if your tasks are working on the same files, they might step on each others' toes indeed.
You can use gulp's tasks dependencies to have them run one after the other. So if bundle-config should be run before bundle-source :
gulp.task('bundle-source', ['bundle-config'], function () {
return bundler.bundle(config);
});
You can also use a package like run-sequence if you need them to run one after the other :
var seq = require('run-sequence');
gulp.task('bundle', function(cb) {
return seq('bundle-config', 'bundle-source', cb);
});
Finally, You could use gulp 4, which has a built-in mechanism to run tasks in series.

How to pass a parameter to gulp-watch invoked task

I am trying to pass a parameter to a task that is being invoked by gulp-watch. I need it because I am trying to build a modular framework.
So if a file changes in module 1, the other modules don't need to be rebuild.
And I want just one function to create the concatted & uglified files per module.
This is what I got so far:
//here I need the 'module' parameter
gulp.task('script', function(module) { ... }
gulp.task('watch', function() {
gulp.watch('files/in/module1/*.js', ['script']); //here I want to pass module1
gulp.watch('files/in/module2/*.js', ['script']); //here I want to pass module2
});
A lot of the documentation/examples seems to be outdated (gulp.run(), gulp.start()).
I hope someone can help me out here.
I had the very same issue, searched for a while, and the "cleanest" way I came up with, uses the .on() event handler of gulp.watch(), and the .env property of gulp-util:
var gulp = require('gulp');
$.util = require('gulp-util');
var modules = {
module1: {}, // awesome module1
module2: {} // awesome module2
};
gulp.task('script', function(){
var moduleName = $.util.env.module;
// Exit if the value is missing...
var module = modules[moduleName];
if (!module) {
$.util.log($.util.colors.red('Error'), "Wrong module value!");
return;
}
$.util.log("Executing task on module '" + moduleName + "'");
// Do your task on "module" here.
});
gulp.task('watch', function () {
gulp.watch(['files/in/module1/*.js'], ['script']).on('change', function () {
$.util.env.module = 'module1';
});
gulp.watch(['files/in/module2/*.js'], ['script']).on('change', function () {
$.util.env.module = 'module2';
});
});
gulp-util also comes in handy if you need to pass (global) parameters from the shell:
[emiliano#dev ~]# gulp script --module=module1 --minify
Hope this helps someone else out there!
Regards.
In that i will answer directly the question "How to pass a parameter to gulp-watch invoked task"
My way of doing, and one of the possibility i see, is to use a global variable to pass the value between the two blocks. you set it just before launching the task in the watcher. And in the task, just at the start you pass it to a local variable.
See this answer for more details: https://stackoverflow.com/a/49733123/7668448
In what you want to achieve, you can too use just one watcher over the directory that hold all modules. If so is the structure. Then when a change happen, you can recover the changed file path. From that you can deduce what module does belong to. By getting the Module folder. That way you will not need to add a new watcher for each new module. Which can be nice when there is multiple contributors to the project for example when working on open source. And you do it one time, and don't have to care about adding anything. Just like with the delegation principle, with DOM event handling when there is multiple elements. Even if the chosen structure, doesn't have all the modules in one directory. You can stay pass multiple globs to the one watcher.
gulp.watch(['glob1/**/*.js', 'glob2/**/*.js',...], function(evt) {/*.....*/});
And following the structure you have, you can work your way to deduce what module is.
For the watcher here how i suggest you do it:
watch('./your/allModulesFolder/**/*.js', function (evt) {
rebuildModulWatchEvt = evt; //here you update the global var
gulp.start('rebuildModul'); // you start the task
})
The evt here hold multiple info: cwd, base, state, _contents ...etc And what interest us is path. So evt.path will give you the path of the changed file.
In your task either you do that:
gulp.task('rebuildModul', function() {
let evt = rebuildModulWatchEvt; // at all start you pass it to a local var
let filePath = evt.path; // how you get the changed file path
// your code go here for the rest, following your structure, get the path for the module folder
});
or you use a function :
gulp.task('rebuildModul', function() {
rebuildModulTaskRun(rebuildModulWatchEvt);
});
function rebuilModulTaskRun(evt) {
let filePath = evt.path;
// your code go here for the rest, following your structure, get the path for the module folder
}