How to tell Gulp when a task is finished? - javascript

I am trying to write a gulp plugin which will count the number of files in the stream. I used this as a starting point:
function count() {
var count = 0;
function countFiles(data) {
count++;
// added this as per official docs:
this.queue(data);
}
function endStream() {
console.log(count + " files processed");
// not doing this as per original post, as it "ends" the gulp chain:
//this.emit("end");
// so doing this instead as per official docs:
this.queue(null);
}
return through(countFiles, endStream);
}
module.exports = count;
And here is a sample task:
gulp.task("mytask", function () {
gulp
.src("...files...")
.pipe(count()); // <--- here it is
.pipe(changed("./some/path"))
.pipe(uglify())
.pipe(rename({ extname: ".min.js" }))
.pipe(gulp.dest(./some/path))
.pipe(count()); // <--- here it is again
});
It works perfectly, except that it does not begin/end as expected:
[14:39:12] Using gulpfile c:\foo\bar\baz\gulpfile.js
[14:39:12] Starting 'mytask'...
[14:39:12] Finished 'mytask' after 9.74 ms
9 files processed
5 files processed
That means that the thing is running asynchronously and finishes after the task is complete. The docs say that you must use a callback or return a stream. It seems to be doing just that.
How do I make this function behave? Is it because I'm using the through rather than the through2 plugin?

Put return before gulp in your task. You haven't provided any way for gulp to know when your stream is finished.

Related

Using gulp.series to run two tasks sequentially isn't working as expected

First of all, I'm new on using Gulp.
I'm trying to create two tasks for CSS/SASS processing. First task needs to compile SASS into CSS (using gulp-sass and node-sass), and output the resulting files into assets/dist/css folder. This task is working like a charm.
The second task needs to use gulp-purgecss to traverse the resulting CSS files (from task 1) and remove unused CSS rules, outputting the clean CSS files into assets/dist/css/min folder. But, unfortunatelly, this task isn't working as expected.
I've created a "css" task that triggers, with gulp.series, my two tasks in the expected order, but looks like the second task - purgecss - is started before the files are finished by the first task - SASS compiling -, so the clean CSS files doesn't appear on assets/dist/css/min folder.
For testing, I've ran the "css" task twice, and, on the second time, the assets/dist/css/min folder appears containing my clean CSS, but those are based on old CSS files (generated on first run of "css" task), not the "fresh" ones generated on the second time.
Here is the tasks I mentioned:
gulp.task("css:build", (done) => {
gulp.src("./assets/src/scss/admin/admin.scss")
.pipe(sass.sync({
outputStyle: "compressed"
}).on("error", sass.logError))
.pipe(gulp.dest("./assets/dist/css"));
gulp.src("./assets/src/scss/theme/theme.scss")
.pipe(sass.sync({
outputStyle: "compressed"
}).on("error", sass.logError))
.pipe(gulp.dest("./assets/dist/css"));
done();
});
gulp.task("css:purge", (done) => {
gulp.src("./assets/dist/css/theme.css")
.pipe(purgecss({
content: ['./**/*.php']
}))
.pipe(gulp.dest("./assets/dist/css/min"));
done();
});
gulp.task("css:watch", (done) => {
gulp.watch("./assets/src/scss/**/*.scss", gulp.series("css:build", "css:purge"));
done();
});
gulp.task("css", gulp.series("css:build", "css:purge"));
My guess is that gulp.series isn't working as expected, waiting the css:build to complete before starting css:purge.
Can someone help me with this? Thanks!
After a few hours of search, I've reached the solution.
I only needed to remove the callback call done() from my tasks and added a return statement before the gulp.src. First of all, I used done() instead of return because, in the css:build task, I need to process two CSS files. But, now, I figured out that gulp.src accepts an array of strings instead only a string.
So, here's how my code looks now:
gulp.task("css:build", () => {
return gulp.src(["./assets/src/scss/admin/admin.scss", "./assets/src/scss/theme/theme.scss"])
.pipe(sass.sync({
outputStyle: "compressed"
}).on("error", sass.logError))
.pipe(gulp.dest("./assets/dist/css"));
});
gulp.task("css:purge", () => {
return gulp.src("./assets/dist/css/*.css")
.pipe(purgecss({
content: [
"./*.php",
"./templates/**/*.php"
]
}))
.pipe(rename({
suffix: ".min"
}))
.pipe(gulp.dest("./assets/dist/css"));
});
gulp.task("css:watch", () => {
return gulp.watch("./assets/src/scss/**/*.scss", gulp.series("css:build", "css:purge"));
});
gulp.task("css", gulp.series("css:build", "css:purge"));
The trick is: Gulp tasks are asynchronous, so the css:purge task was finishing before css:build, and, because it's asynchronous, the done() was fired before the tasks was really ended. So, returning the task itself (return gulp.src()) is like returning a promise, and gulp.series now waits for this promise to be completed before starting the next task.

Gulp: call an async function which provides its own callback from within a transform function

I want to create a function for use in a pipe() call in Gulp that'll enable conversion of xlsx files to json.
I had this working with NPM package 'excel-as-json' for gulp 3, however Gulp 4 has forced me to actually understand what it is doing ;-)
Six hours in, and my incapacity of getting this to work due to a lack of js/async/streaming knowledge is punching my curiosity.
Code is as follows:
paths = {excel_sourcefiles: "./sourcefiles/*.xls*", excel_targetdir_local_csvjson: "./targetfiles_local/*.*"}
var replaceExt = require('replace-ext');
var PluginError = require('plugin-error')
var gulpFunction = require('gulp-function').gulpFunction // default ES6 export
var through = require("through2")
var convertExcel = require('excel-as-json').processFile;
var changed = require('gulp-changed');
var assign = Object.assign || require('object.assign');
var notify = require('gulp-notify');
var gulpIgnore = require('gulp-ignore');
var rename = require('gulp-rename');
gulp.task('excel-to-jsoncsv', function() {
return gulp.src(paths.excel_sourcefiles)
// .pipe(debug())
.pipe(gulpIgnore.exclude("*\~*")) // Ignore temporary files by Excel while xlsx is open
.pipe(gulpIgnore.exclude("*\$*")) // Ignore temporary files by Excel while xlsx is open
.pipe(plumber({errorHandler: notify.onError('gulp-excel-to-jsoncsv error: <%= error.message %>')}))
.pipe(changed(paths.excel_targetdir_local_glob, { extension: '.csv' }))
.pipe(GulpConvertExcelToJson()) // This is where the magic should happen
.pipe(rename({ extname: '.csv' })) // Saving as .csv for SharePoint (does not allow .json files to be saved)
.pipe(gulp.dest(paths.excel_targetdir_local))
});
function GulpConvertExcelToJson() {
return through.obj(function(chunk, enc, callback) {
var self = this
if (chunk.isNull() || chunk.isDirectory()) {
callback() // Do not process directories
// return
}
if (chunk.isStream()) {
callback() // Do not process streams
// return
}
if (chunk.isBuffer()) {
convertExcel(chunk.path, null, null, // Converts file found at `chunk.path` and returns (err, `data`) its callback.
function(err, data) {
if (err) {
callback(new PluginError("Excel-as-json", err))
}
chunk.contents = new Buffer(JSON.stringify(data))
self.push(chunk)
callback()
// return
})
} else {
callback()
}
})
}
I (now) know there are other excel > json gulp modules that could allow me to solve this problem without writing my own module, yet I'd like to understand what I should do different here.
The error returned is 'Did you forget to signal Async completion?', which I tried to not do. However, probably my attempt to fix the error 'this.push is not a function' with a var self = this is not what I was supposed to do.
Looking at examples like gulp-zip introduced me to unfamiliar notations, making me unable to autodidact my way out of this.
In summary:
how would I go about calling an async function during a through.obj function call, where the chunk's contents is updated with the (not under my control) async function that only provides me with a callback (err, data)?
This problem is coming deep from the excel library. The end event should be finish.
Unsuccessful Try
I did the following, to install the modified excel.js:
rm -rf node_modules/excel
git clone https://github.com/bdbosman/excel.js node_modules/excel
cd node_modules/excel && npm i && cd ../..
This is not enough since the excel-as-json uses an old version of excel.
Either you have to modify the excel-as-json module, to use excel#1.0.0 (after the Pull Request is merged) or manually edit the file in node_modules. I will describe the second way here.
Temporary Solution
Edit the excel file after installing the modules.
I used:
sed -i "s/'end'/'finish'/g" node_modules/excel/excelParser.js
And then I ran:
$ gulp excel-to-jsoncsv
[19:48:21] Using gulpfile ~/so-question-gulp-async-function-call-xlsx/gulpfile.js
[19:48:21] Starting 'excel-to-jsoncsv'...
[19:48:21] Finished 'excel-to-jsoncsv' after 126 ms
And it apparently worked.

How can I restart my Gulp task on subsequent saves?

I have a set of Gulp (v4) tasks that do things like compile Webpack and Sass, compress images, etc. These tasks are automated with a "watch" task while I'm working on a project.
When my watch task is running, if I save a file, the "default" set of tasks gets ran. If I save again before the "default" task finishes, another "default" task begins, resulting in multiple "default" tasks running concurrently.
I've fixed this by checking that the "default" task isn't running before triggering a new one, but this has caused some slow down issues when I save a file, then rapidly make another minor tweak, and save again. Doing this means that only the first change gets compiled, and I have to wait for the entire process to finish, then save again for the new change to get compiled.
My idea to circumvent this is to kill all the old "default" tasks whenever a new one gets triggered. This way, multiples of the same task won't run concurrently, but I can rely on the most recent code being compiled.
I did a bit of research, but I couldn't locate anything that seemed to match my situation.
How can I kill all the "old" gulp tasks, without killing the "watch" task?
EDIT 1: Current working theory is to store the "default" task set as a variable and somehow use that to kill the process, but that doesn't seem to work how I expected it to. I've placed my watch task below for reference.
// watch task, runs through all primary tasks, triggers when a file is saved
GULP.task("watch", () => {
// set up a browser_sync server, if --sync is passed
if (PLUGINS.argv.sync) {
CONFIG_MODULE.config(GULP, PLUGINS, "browsersync").then(() => {
SYNC_MODULE.sync(GULP, PLUGINS, CUSTOM_NOTIFIER);
});
}
// watch for any changes
const WATCHER = GULP.watch("src/**/*");
// run default task on any change
WATCHER.on("all", () => {
if (!currently_running) {
currently_running = true;
GULP.task("default")();
}
});
// end the task
return;
});
https://github.com/JacobDB/new-site/blob/4bcd5e82165905fdc05d38441605087a86c7b834/gulpfile.js#L202-L224
EDIT 2: Thinking about this more, maybe this is more a Node.js question than a Gulp question – how can I stop a function from processing from outside that function? Basically I want to store the executing function as a variable somehow, and kill it when I need to restart it.
There are two ways to set up a Gulp watch. They look very similar, but have the important difference that one supports queueing (and some other features) and the other does not.
The way you're using, which boils down to
const watcher = watch(<path glob>)
watcher.on(<event>, function(path, stats) {
<event handler>
});
uses the chokidar instance that underlies Gulp's watch().
When using the chokidar instance, you do not have access to the Gulp watch() queue.
The other way to run a watch boils down to
function watch() {
gulp.watch(<path>, function(callback) {
<handler>
callback();
});
}
or more idiomatically
function myTask = {…}
const watch = () => gulp.watch(<path>, myTask)
Set up like this watch events should queue the way you're expecting, without your having to do anything extra.
In your case, that's replacing your const WATCHER = GULP.watch("src/**/*"); with
GULP.watch("src/**/*", default);
and deleting your entire WATCHER.on(…);
Bonus 1
That said, be careful with recursion there. I'm extrapolating from your use of a task named "default"… You don't want to find yourself in
const watch = () => gulp.watch("src/**/*", default);
const default = gulp.series(clean, build, serve, watch);
Bonus 2
Using the chokidar instance can be useful for logging:
function handler() {…}
const watcher = gulp.watch(glob, handler);
watcher.on('all', (path, stats) => {
console.log(path + ': ' + stats + 'detected') // e.g. "src/test.txt: change detected" is logged immediately
}
Bonus 3
Typically Browsersync would be set up outside of the watch function, and the watch would end in reloading the server. Something like
…
import browserSync from 'browser-sync';
const server = browserSync.create();
function serve(done) {
server.init(…);
done();
}
function reload(done) {
server.reload();
done();
}
function changeHandler() {…}
const watch = () => gulp.watch(path, gulp.series(changeHandler, reload);
const run = gulp.series(serve, watch);
try installing gulp restart
npm install gulp-restart
As #henry stated, if you switch to the non-chokidar version you get queuing for free (because it is the default). See no queue with chokidar.
But that doesn't speed up your task completion time. There was an issue requesting that the ability to stop a running task be added to gulp - how to stop a running task - it was summarily dealt with.
If one of your concerns is to speed up execution time, you can try the lastRun() function option. gulp lastRun documentation
Retrieves the last time a task was successfully completed during the
current running process. Most useful on subsequent task runs while a
watcher is running.
When combined with src(), enables incremental builds to speed up
execution times by skipping files that haven't changed since the last
successful task completion.
const { src, dest, lastRun, watch } = require('gulp');
const imagemin = require('gulp-imagemin');
function images() {
return src('src/images/**/*.jpg', { since: lastRun(images) })
.pipe(imagemin())
.pipe(dest('build/img/'));
}
exports.default = function() {
watch('src/images/**/*.jpg', images);
};
Example from the same documentation. In this case, if an image was successfully compressed during the current running task, it will not be re-compressed. Depending on your other tasks, this may cut down on your wait time for the queued tasks to finish.

How a gulp function can know that functions called inside it are finished?

I'm writing a gulp task that copy sass files into a tmp folder and then create css.
function copy_sass(done) {
var conponments = setup.conponments;
var sassList = config.conponments.sass;
var mainPath = config.path.src.sass.main;
var rootPath = config.path.src.sass.root;
var source = getPaths(conponments, sassList, mainPath);// get a filtered list of path
var destination = config.path.tmp.sass_tmp;
copyPath(mainPath + 'mixin/**', destination + 'main/mixin/');
copyPath(mainPath + 'settings/**', destination + 'main/settings/');
copyPath(rootPath + 'style.scss', destination);
copyPath(source, destination + 'main/conponment/');
done();
};
function css_build(done) {
var source = config.path.tmp.sass_tmp + '**/*.scss';
var destination = config.path.tmp.css.root;
return src(source)
.pipe(bulkSass())
.pipe(sass())
.pipe(csscomb())
.pipe(cssbeautify({indent: ' '}))
.pipe(autoprefixer())
.pipe(gulp.dest(destination));
done();
};
function copyPath(source, destination) {
return src(source)
.pipe(dest(destination));
};
exports.getcss = series(
copy_sass,
css_build
);
exports.filter = filter_tmp_sass;
exports.css = css_build;
When I call functions in series with the task getcss, gulp don't seem to wait before the copy task is finished and css_build do nothing because the paths are not already copied.
When I launch the copy task and then the css task manually, all is working. So I think that the problem is that the copy_sass function is considered as finished before the end of the copyPath functions, and then css_build is launched before the paths are copied.
What I expect is that the getcss task wait until the copy_sass function and the copyPath function inside it are finished before launch css_build.
Node libraries handle asynchronicity in a variety of ways.
The Gulp file streams (usually started with src()) work asynchronously. This means, that they start some kind of work, but instantly return when being called. This is the reason why you always need to return the stream from the task, so that Gulp knows when the actual work of the task is finished, as you can read in the Gulp documentation:
When a stream, promise, event emitter, child process, or observable is returned from a task, the success or error informs gulp whether to continue or end.
Regarding your specific example, in your task copy_sass you call copyPath multiple times. The copying is started, but the methods instantly return without waiting for completion. Afterwards, the done callback is called telling Gulp that the task is done.
To ensure task completion, you need to return every single stream to Gulp. In your example, you could create a separate task for each copy operation and aggregate them via series() or even parallel():
function copyMixin() {
return src('...').pipe(dest(...))
}
function copySettings() {
return src('...').pipe(dest(...))
}
// ...
var copySass = parallel(copyMixin, copySettings, ...)

Gulp task not waiting the completion of the previous task

I have a gulp task that uses streams to write a revised file (using gulp-rev) and another task that uses the previously written manifest file for replacing a variables in an html file.
My first task:
gulp.task('scripts', function() {
// other stuff...
var b = browserify(mainscript);
b.external('External')
.transform(babelify)
.bundle()
.pipe(source('app.js'))
.pipe(buffer())
.pipe(rev())
.pipe(gulp.dest(dest))
.pipe(rev.manifest({
path: 'appjs-manifest.json'
}))
.pipe(gulp.dest(dest))
}
The previous task, will write a file named something like 'app-34f01b9d.js' and a manifest file 'appjs-manifest.json' containing:
{
"app.js": "app-34f01b9d.js"
}
My second task, theoretically, should wait the completion of the first task before starting :
gulp.task('renameindex',['scripts'], function () {
var paths = gulp.paths;
var jsonContent = JSON.parse(fs.readFileSync(paths.buildDest+'/public/scripts/appjs-manifest.json', 'utf8'));
var src=paths.srcDir+"/index.hbs";
var appjs=jsonContent["app.js"];
console.log("app.js",appjs)
return gulp.src(src)
.pipe(handlebars({appjs:appjs}, null))
.pipe(rename('index.html'))
.pipe(gulp.dest(paths.buildDest));
});
The tasks should read the manifest file and replace "app-34f01b9d.js" to a variable {appjs}} in an handlebar template generating a file "index.html".
What happens is that the javascript file and the manifest file are written correctly but the second task executes without waiting for the manifest file to be written thus grabbing the wrong manifest file content (the one of the previous execution).
Your scripts task is not returning any value. When you do not return anything gulp have no way to know when the task is finished, therefore continues to the next one.
simply say
gulp.task('scripts', function() {
// other stuff...
var b = browserify(mainscript);
return b.external('External')
...
In case your task is doing something other than a gulp pipe line, you can manually call the callback which is passed to the task as a parameter:
gulp.task('someTask', function(done) {
...
done();
}

Categories

Resources