Light Gulp 4 task still throwing 'async completion' error - asynchronous

I've not used Gulp before, so I'm not migrating an old gulpfile to Gulp 4. I'm just trying to get a few basic tasks set up. However, regardless of the method I use to signal async completion as well documented in this post I still get the "Did you forget to signal async completion?" error.
'use strict';
/*
=====
PLUGINS
=====
*/
var gulp = require('gulp'),
plumber = require('gulp-plumber');
// sass
var sass = require('gulp-sass');
sass.compiler = require('node-sass');
// js
var concat = require('gulp-concat'),
uglify = require('gulp-uglify');
/*
=====
TASKS
=====
*/
gulp.task('sass', function() {
return gulp.src('./lib/**/*.scss')
.pipe(sass().on('error', sass.logError))
.pipe(gulp.dest('./lib/build'))
})
/*
=====
EXPORT TASKS
=====
*/
exports.sass = sass;
With this super barebones setup shouldn't it work? Doesn't return gulp.src return a stream? What am I missing?
Node 10.15.0,
Gulp CLI 2.2.0,
Gulp 4.0.1

Try
// exports.sass = sass;
exports.sass = gulp.series('sass');
or
exports.sass = 'sass';
You are using the task version (gulp.task) so you need the quotes.
If you were using the function version (recommended):
function sass2 () {
return gulp.src('./lib/**/*.scss')
.pipe(sass().on('error', sass.logError))
.pipe(gulp.dest('./lib/build'))
};
then
exports.sass = sass2;
works fine. Note that you have a var sass already so you would have to name your function sass2 to something other than just sass again of course.

Related

gulp4: Did you forget to signal async completion?

Having some issue with migrating from gulp3 to 4. I have seen various posts but still cannot seem to get around the error
[12:17:41] The following tasks did not complete: serve-build, build,
[12:17:41] Did you forget to signal async completion?
Already tried adding the async function() and promise/return in function(done){.... done();}
cannot seem to get in the right place. It seems to stop and build my lib and app .js files but they are missing things.. specifically the tinymce plugin..
gulp.task('clean-styles', function(done) {
var files = [].concat(
config.temp + '**/*.css',
config.build + 'styles/**/*.css'
);
clean(files, done);
});
gulp.task('styles', gulp.series('clean-styles', function() {
log('Compiling Less --> CSS');
return gulp
.src(config.less)
.pipe($.plumber()) // exit gracefully if something fails after this
.pipe($.less())
// .on('error', errorLogger) // more verbose and dupe output.
requires emit.
.pipe($.autoprefixer({browsers: ['last 2 version', '> 5%']}))
.pipe(gulp.dest(config.temp));
}));
/**
* Remove all fonts from the build folder
* #param {Function} done - callback when complete
*/
gulp.task('clean-fonts', function(done) {
clean(config.build + 'fonts/**/*.*', done);
});
/**
* Copy TinyMCE fonts
* #return {Stream}
*/
gulp.task('fonts-tinymce', function () {
log('Copying tinymce fonts');
return gulp
.src(config.fontsTinyMCE)
.pipe(gulp.dest(config.build + 'styles/fonts'));
});
/**
* Copy fonts
* #return {Stream}
*/
gulp.task('fonts', gulp.series('clean-fonts', 'fonts-bootstrap', 'fonts-tinymce', function () {
log('Copying fonts');
return gulp
.src(config.fonts)
.pipe(gulp.dest(config.build + 'fonts'));
}));
/**
* Remove all images from the build folder
* #param {Function} done - callback when complete
*/
gulp.task('clean-images', function(done) {
clean(config.build + 'images/**/*.*', done);
});
/**
* Compress images
* #return {Stream}
*/
gulp.task('images', gulp.series('clean-images', function() {
log('Compressing and copying images');
return gulp
.src(config.images)
.pipe($.imagemin({optimizationLevel: 4}))
.pipe(gulp.dest(config.build + 'images'));
}));
gulp.task('less-watcher', function() {
gulp.watch([ config.less ], gulp.series('styles'));
});
/**
* Optimize all files, move to a build folder,
* and inject them into the new index.html
* #return {Stream}
*/
gulp.task('optimize', gulp.series('inject', 'test', function() {
log('Optimizing the js, css, and html');
var assets = $.useref.assets({searchPath: './'});
// Filters are named for the gulp-useref path
var cssFilter = $.filter('**/*.css');
var jsAppFilter = $.filter('**/' + config.optimized.app);
var jslibFilter = $.filter('**/' + config.optimized.lib);
var templateCache = config.temp + config.templateCache.file;
return gulp
.src(config.index)
.pipe($.plumber())
.pipe(inject(templateCache, 'templates'))
.pipe(assets) // Gather all assets from the html with useref
// Get the css
.pipe(cssFilter)
.pipe($.minifyCss())
.pipe(cssFilter.restore())
// Get the custom javascript
.pipe(jsAppFilter)
.pipe($.ngAnnotate({add: true}))
.pipe($.uglify())
.pipe(getHeader())
.pipe(jsAppFilter.restore())
// Get the vendor javascript
.pipe(jslibFilter)
.pipe($.uglify()) // another option is to override wiredep to use min files
.pipe(jslibFilter.restore())
// Take inventory of the file names for future rev numbers
.pipe($.rev())
// Apply the concat and file replacement with useref
.pipe(assets.restore())
.pipe($.useref())
// Replace the file names in the html with rev numbers
.pipe($.revReplace())
.pipe(gulp.dest(config.build));
}));
/**
* Build everything
* This is separate so we can run tests on
* optimize before handling image or fonts
*/
gulp.task('build', gulp.series('optimize', 'images', 'fonts', function() {
log('Building everything');
var msg = {
title: 'gulp build',
subtitle: 'Deployed to the build folder',
message: 'Running `gulp serve-build`'
};
del(config.temp);
log(msg);
notify(msg);
}));
/**
* Remove all tinymce fonts from the build folder
* #param {Function} done - callback when complete
*/
gulp.task('clean-fonts-tinymce', function (done) {
clean(config.build + 'styles/fonts/*.*', done);
});
/**
* serve the build environment
* --debug-brk or --debug
* --nosync
*/
gulp.task('serve-build', gulp.series('build', function() {
serve(false /*isDev*/);
}));
I have a somewhat similar setup and this should help with the error:
/**
* serve the build environment
* --debug-brk or --debug
* --nosync
*/
gulp.task('serve-build', gulp.series('build', function(done) {
serve(false /*isDev*/);
done();
}));
More on async completion can be found in the documentation
With normal gulp tasks (e.g. build my javascript) you have a well defined start and end to that task.
However with dev tasks like devServer or watch tasks these are started and then running perpetually. The reason for the error is generally that you started the task and it was still running when you hit CTRL-C and so gulp does not see the task as finished (because it wasn't).
The other case where you get this error is if your code DID finish but was async and you forgot to tell gulp you were done by using a callback. But I'm going to ignore that case here.
If you don't want an error from your never-ending tasks (devServer/watch/runRedis/etc) when you CTRL-C gulp, you have 2 choices for these type of tasks...
1) Lie to gulp
function myNeverEndingDevTask(done) {
// start an async process (dev server, watch, etc)
done(); // we tell gulp we are done, but we aren't really (async process keeps going)
}
In this scenario gulp will report Finished: myNeverEndingDevTask right away even though the task is not finished (e.g. devServer or watch still running). But you won't get the error when you CTRL-C gulp.
2) Handle CTRL-C
I'll use gulp.watch as an example but it can apply to any neverending gulp tasks.
The issue is that watch is still running when we hit CTRL-C and gulp knows it is still running because we didn't do option #1 above.
So we need to handle the CTRL-C and tell gulp we are done. We can do this by adding a SIGINT event handler that cleans up (if needed) our never-ending task and THEN calls done() to let gulp know we finished the task.
You can wrap this into a utility function for instance...
function gulpWatchTidy (paths, opts, task, done) {
// gulp.watch returns underlying chokidar watcher
var watcher = gulp.watch(paths, opts, task);
process.on('SIGINT', function() {
// chokidar 3 is async and needs .then()
// chokidar 2 (gulp glob-watcher depends on chokidar2)
watcher.close();
done();
});
return watcher;
}
And then instead of something like this...
function taskWatchJavascript() {
var watchOpts = {delay: 1000};
return gulp.watch('src/**/*.js', watchOpts, taskBuildJavascript);
}
...you do this...
function taskWatchJavascript(done) {
var watchOpts = {delay: 1000};
return gulpWatchTidy ('src/**/*.js', watchOpts, taskBuildJavascript, done);
}
Our utility function takes the gulp async callback and handles the SIGINT (CTRL-C) to clean up or stop the watcher (or whatever) and then let gulp know we are done.
The Did you forget to signal async completion? error then goes away because we cleaned up our forever tasks and let gulp know they were all done.

Run a command after a grunt task finishes?

I want to run a command but after a task finishes in grunt.
uglify: {
compile: {
options: {...},
files: {...}
}
?onFinish?: {
cmd: 'echo done!',
// or even just a console.log
run: function(){
console.log('done!');
}
}
},
Either run a command in shell, or even just be able to console.log. Is this possible?
Grunt does not support before and after callbacks, but next version could implement events that would work in the same way, as discussed in issue #542.
For now, you should go the task composition way, this is, create tasks for those before and after actions, and group them with a new name:
grunt.registerTask('newuglify', ['before:uglify', 'uglify', 'after:uglify']);
Then remember to run newuglify instead of uglify.
Another option is not to group them but remember to add the before and after tasks individually to a queue containing uglify:
grunt.registerTask('default', ['randomtask1', 'before:uglify', 'uglify', 'after:uglify', 'randomtask2']);
For running commands you can use plugins like grunt-exec or grunt-shell.
If you only want to print something, try grunt.log.
The grunt has one of the horrible code that I've ever seen. I don't know why it is popular. I would never use it even as a joke. This is not related to "legacy code" problem. It is defected by design from the beginning.
var old_runTaskFn = grunt.task.runTaskFn;
grunt.task.runTaskFn = function(context, fn, done, asyncDone) {
var callback;
var promise = new Promise(function(resolve, reject) {
callback = function (err, success) {
if (success) {
resolve();
} else {
reject(err);
}
return done.apply(this, arguments);
};
});
something.trigger("new task", context.name, context.nameArgs, promise);
return old_runTaskFn.call(this, context, fn, callback, asyncDone);
}
You can use callback + function instead of promise + trigger. This function will request the new callback wrapper for new task.

passing grunt parameters from one task to another

I'm trying to pass the configuration values returned from the server(zookeeper) into compass (cdnHost, environment, etc) and seem to be having a hard time using the right approach.
I looked at ways to pass around args from one task to another on this page as a starting point
http://gruntjs.com/frequently-asked-questions#how-can-i-share-parameters-across-multiple-tasks
module.exports = function(grunt) {
grunt.initConfig({
compass: {
dist: {
//options: grunt.option('foo')
//options: global.bar
options: grunt.config.get('baz')
}
},
...
grunt.registerTask('compassWithConfig', 'Run compass with external async config loaded first', function () {
var done = this.async();
someZookeeperConfig( function () {
// some global.CONFIG object from zookeeper
var config = CONFIG;
// try grunt.option
grunt.option('foo', config);
// try config setting
grunt.config.set('bar', config);
// try global
global['baz'] = config;
done(true);
});
});
...
grunt.registerTask('default', ['clean', 'compassWithConfig', 'compass']);
I also tried calling the compass task directly, and it made no difference.
grunt.task.run('compass');
Any insights would be greatly appreciated. (e.g. way to use initConfig and have the value be available).
Thanks
When you write:
grunt.initConfig({
compass: {
dist: {
options: grunt.config.get('baz')
}
}
... grunt.config is called right away, and returns the value of baz as it is right now. Altering it (later) in another task simply won't get picked-up.
How to solve that?
#1: update compass.dist.options instead of updating baz
grunt.registerTask('compassWithConfig', 'Run compass with external async config loaded first', function () {
var done = this.async();
someZookeeperConfig( function () {
// some global.CONFIG object from zookeeper
var config = CONFIG;
grunt.config.set('compass.dist.options', config);
done();
});
});
Now, running task compassWithConfig first, then task compass will get the result you expect.
#2: wrap-up compass task execution in order to abstract away config mapping
grunt.registerTask('wrappedCompass', '', function () {
grunt.config.set('compass.dist.options', grunt.config.get('baz'));
grunt.task.run('compass');
});
// Then, you can manipulate 'baz' without knowing how it needs to be mapped for compass
grunt.registerTask('globalConfigurator', '', function () {
var done = this.async();
someZookeeperConfig( function () {
// some global.CONFIG object from zookeeper
var config = CONFIG;
grunt.config.set('baz', config);
done();
});
});
Finally, running task globalConfigurator then wrappedCompass will get you to the result.

grunt less task fails silently when not using grunt-cli

I am at a loss as to why the less task fails silently. If I run it using grunt-cli and Gruntfile.js it works fine, but when I try to port it into another script the less task does not generate any output. Any help or insight as to why would be greatly appreciated.
'use strict';
var grunt = require('grunt'),
_ = require('underscore'),
path = require('path'),
fs = require('fs'),
dir = require('node-dir');
var cssSrc = [];
var cssPaths = [];
var templates = [];
dir.paths('repo', function (err, paths) {
if (err) {
throw err;
}
_.each(paths.files, function (file) {
if (path.extname(file) === '.less') {
cssSrc.push(file);
}
});
cssPaths = paths.dirs;
grunt.task.loadNpmTasks('grunt-contrib-less');
grunt.initConfig({
less: {
options: {
paths: cssPaths
},
files: {
'tmp/target.css': cssSrc
}
}
});
grunt.task.run('less');
});
The dependencies implicit to grunt-cli:
nopt
findup-sync
resolve
are not explicitly included in a standalone script.
The Gruntfile.js script contains the path information:
repo paths found using the dir.paths callback
tmp/target.css paths found using _.each
.less source paths found using paths.dirs
and uses the dependencies to do pathfinding.
There are unrelated questions about running less via Rhino and wsh which explain the parameters for doing path finding explicitly.
References
npm: less
npm: grunt-cli
npm scripts man page
grunt-cli source
less: Third Party Compilers

Trying to build LESS (less css) using a build script with nodeJS

We are using NodeJS to build our project. We have integrated LESS CSS as a part of the project. We are attempting to keep our build clean and would like to be able to call the lessc command (or something comparable) in order to build our LESS files.
The LESS documentation isn't very in depth, but wanted to reach out to the community to find a solution. Google has not be too helpful to me on this topic.
We have a build.sh file, that calls various other build.js files (in respective directories). How can I run LESSC to compile my LESS files?
Using node.js
var less = require('less')
,fs = require('fs');
fs.readFile('style.less',function(error,data){
data = data.toString();
less.render(data, function (e, css) {
fs.writeFile('style.css', css, function(err){
console.log('done');
});
});
});
Few years later ... less#3.9.0 returns output as:
{
css: 'rendered css',
imports: ['source.css']
}
Updated code can look like this:
const outputFile = 'style-iso.css';
const data = `.bootstrap-iso {
#import (less) "style.css";
} `;
less.render(data, { strictMath: true }).then(
output => {
fs.writeFile(outputFile, output.css, err => {
if (err) {
throw err;
}
console.log(`${outputFile} saved!`);
});
},
err => {
console.error(err);
});
Tip
strictMath: true
is necessary if you want to render bootstrap.css

Resources