Context
I have a gulpfile.js
. It basicaly concats two folders with js files to two files to export to a dist folder. It also adds a cache buster and a uglified version for production.
//gulpfile.js
const gulp = require('gulp');
const sass = require('gulp-sass');
const concat = require('gulp-concat');
const rename = require('gulp-rename');
const maps = require('gulp-sourcemaps');
const uglify = require('gulp-uglify');
const gulpif = require('gulp-if');
const inlineCss = require('gulp-inline-css');
const jsValidate = require('gulp-jsvalidate');
const notify = require('gulp-notify');
const rev = require('gulp-rev');
const fs = require("fs");
const del = require('del');
const distFolder = './myapp/dist/';
/* Concat all scripts and add cache buster */
gulp.task('app_concatScripts', (done) => {
/*first remove old app.js files */
return del([distFolder + "app-*.{js,map}"])
.then(paths => {
/*Concat files with sourcemaps, hash them and write to manifest*/
return gulp.src("./myapp/js/source-app/*.js")
.pipe(maps.init())
.pipe(concat({
path: "app.js",
cwd: '',
newLine: '; \n'
}))
.pipe(gulp.dest(distFolder))
.pipe(rev())
.pipe(maps.write('./'))
.pipe(gulp.dest(distFolder))
.pipe(rev.manifest(distFolder + 'rev-manifest.json', {
base: distFolder,
merge: true
}))
.pipe(gulp.dest(distFolder));
});
});
/* Minify scripts and add cache buster */
gulp.task('app_minifyScripts', (done) => {
return gulp.src(distFolder + "app.js", {
allowEmpty: true
})
.pipe(uglify())
.on('error', notify.onError((error) => {
console.log(error);
return error.message + ' in ' + error.fileName;
}))
.pipe(rename("app.min.js"))
.pipe(rev())
.pipe(gulp.dest(distFolder))
.pipe(rev.manifest(distFolder + 'rev-manifest.json', {
base: distFolder,
merge: true
}))
.pipe(gulp.dest(distFolder));
});
/* Concat all scripts and add cache buster, this function is exactly the same as app_concatScripts */
gulp.task('main_concatScripts', (done) => {
/*Concat files with sourcemaps, hash them and write to manifest*/
return del(["./myapp/dist/main-*.{js,map}"])
.then(paths => {
return gulp.src("./myapp/js/source-main/*.js")
.pipe(maps.init())
.pipe(concat({
path: "main.js",
cwd: '',
newLine: '; \n'
}))
.pipe(gulp.dest(distFolder))
.pipe(rev())
.pipe(maps.write('./'))
.pipe(gulp.dest(distFolder))
.pipe(rev.manifest(distFolder + 'rev-manifest.json', {
base: distFolder,
merge: true
}))
.pipe(gulp.dest(distFolder));
});
});
/* Minify scripts and add cache buster, this function is the same as app_minifyScripts */
gulp.task('main_minifyScripts', (done) => {
return gulp.src("./myapp/dist/main.js")
.pipe(uglify())
.on('error', notify.onError((error) => {
console.log(error);
return error.message + ' in ' + error.fileName;
}))
.pipe(rename("main.min.js"))
.pipe(rev())
.pipe(gulp.dest(distFolder))
.pipe(rev.manifest(distFolder + 'rev-manifest.json', {
base: distFolder,
merge: true
}))
.pipe(gulp.dest(distFolder));
});
/* watch task */
gulp.task('watch', (done) => {
gulp.watch('./myapp/js/source-app/*.js', gulp.series('app_concatScripts', 'app_minifyScripts'));
gulp.watch('./myapp/js/source-main/*.js', gulp.series('main_concatScripts', 'main_minifyScripts'));
done();
});
gulp.task('default', gulp.series('main_concatScripts', 'main_minifyScripts', 'app_concatScripts', 'app_minifyScripts', 'watch'));
Output
The gulpfile.js
works. It builds a rev-manifest.json
like this:
{
"app.js": "app-234318a58d.js",
"app.min.js": "app-7788fee7f3.min.js",
"main.js": "main-60788c863c.js",
"main.min.js": "main-1e92517890.min.js",
"style.css": "style-296f22c598.css",
"admin.css": "admin-a3742ed2f6.css",
}
Problem
To get this working i copied the _concatScripts
and _minifyScripts
task for each js file: app.js
and main.js
. Making the gulpfile unnecessary large and hard to maintain.
What I have tried
I tried using an array to define scripts source files and output names. This does not work because the code in the loop block does not wait for the previous block to be ready. So the minify task fails sometimes because the original js file does not exists yet and sometimes the rev-manifest.json
get overwritten by a previous code block. So it is not really a gulp series anymore. What is the best practice to do something like this?
const scripts = [{
name: 'app',
source: './myapp/js/source-app/*.js'
}, {
name: 'main',
source: './myapp/js/source-main/*.js'
}];
/* Concat all scripts and add cache buster */
gulp.task('concatScripts', (done) => {
for (var i = 0; i < scripts.length; i++) {
var script = scripts[i];
del([distFolder + script.name + "-*.{js,map}"])
.then(paths => {
/*Concat files with sourcemaps, hash them and write to manifest*/
gulp.src(script.source)
.pipe(maps.init())
.pipe(concat({
path: script.name + ".js",
cwd: '',
newLine: '; \n'
}))
.pipe(gulp.dest(distFolder))
.pipe(rev())
.pipe(maps.write('./'))
.pipe(gulp.dest(distFolder))
.pipe(rev.manifest(distFolder + 'rev-manifest.json', {
base: distFolder,
merge: true
}))
.pipe(gulp.dest(distFolder));
});
}
});
/* Minify scripts and add cache buster */
gulp.task('minifyScripts', (done) => {
for (var i = 0; i < scripts.length; i++) {
var script = scripts[i];
gulp.src(distFolder + script.name + ".js", {
allowEmpty: true
})
.pipe(uglify())
.on('error', notify.onError((error) => {
console.log(error);
return error.message + ' in ' + error.fileName;
}))
.pipe(rename(script.name + ".min.js"))
.pipe(rev())
.pipe(gulp.dest(distFolder))
.pipe(rev.manifest(distFolder + 'rev-manifest.json', {
base: distFolder,
merge: true
}))
.pipe(gulp.dest(distFolder));
}
});
/* watch task */
gulp.task('watch', (done) => {
gulp.watch(['./myapp/js/source-app/*.js', './myapp/js/source-main/*.js'], gulp.series('concatScripts', 'minifyScripts'));
done();
});
gulp.task('default', gulp.series('concatScripts', 'minifyScripts', 'watch'));
update
My gulp file now looks like this:
//gulpfile.js
const gulp = require('gulp');
const sass = require('gulp-sass');
const concat = require('gulp-concat');
const rename = require('gulp-rename');
const maps = require('gulp-sourcemaps');
const uglify = require('gulp-uglify');
const gulpif = require('gulp-if');
const inlineCss = require('gulp-inline-css');
const jsValidate = require('gulp-jsvalidate');
const notify = require('gulp-notify');
const rev = require('gulp-rev');
const fs = require("fs");
const del = require('del');
/* Concat all scripts and add cache buster */
const distFolder = './myapp/dist/';
function concatTask(opts) {
const taskName = opts.name + '_concatScripts';
gulp.task(taskName, (done) => {
/*Concat files with sourcemaps, hash them and write to manifest*/
return del(opts.del)
.then(paths => {
return gulp.src(opts.source)
.pipe(maps.init())
.pipe(concat({
'path': opts.name + '.js',
'cwd': '',
'newLine': '; \n'
}))
.pipe(gulp.dest(distFolder))
.pipe(rev())
.pipe(maps.write('./'))
.pipe(gulp.dest(distFolder))
.pipe(rev.manifest(distFolder + 'rev-manifest.json', {
'base': distFolder,
'merge': true
}))
.pipe(gulp.dest(distFolder));
});
});
return taskName;
}
function minifyTask(opts) {
const taskName = opts.name + '_minifyScripts';
gulp.task(taskName, (done) => {
return gulp.src(distFolder + opts.name + '.js', opts.sourceParams)
.pipe(uglify())
.on('error', notify.onError((error) => {
console.log(error);
return error.message + ' in ' + error.fileName;
}))
.pipe(rename(opts.name + '.min.js'))
.pipe(rev())
.pipe(gulp.dest(distFolder))
.pipe(rev.manifest(distFolder + 'rev-manifest.json', {
'base': distFolder,
'merge': true
}))
.pipe(gulp.dest(distFolder));
});
return taskName;
}
const taskOptions = [{
'name': 'main',
'source': './myapp/js/source-main/*.js',
'sourceParams': {
'allowEmpty': false
},
'del': ['./myapp/dist/main-*.{js,map}']
}, {
'name': 'app',
'source': './myapp/js/source-app/*.js',
'sourceParams': {
'allowEmpty': false
},
'del': ['./myapp/dist/app-*.{js,map}']
},
];
const taskNames = taskOptions.map(opts => {
return [concatTask(opts), minifyTask(opts)];
});
/* equivalent to defining tasks and assigning:
* var taskNames = [
* ['main_concatScripts', 'main_minifyScripts'],
* ['app_concatScripts', 'app_minifyScripts']
* ];
*/
gulp.task('watch', (done) => {
taskOptions.forEach((opts, i) => {
gulp.watch(opts.source, gulp.series.apply(gulp, taskNames[i]));
});
done();
});
/* equivalent to:
* gulp.task('watch', (done) => {
* gulp.watch('./myapp/js/source-main/*.js', gulp.series('main_concatScripts', 'main_minifyScripts'));
* gulp.watch('./myapp/js/source-app/*.js', gulp.series('app_concatScripts', 'app_minifyScripts'));
* done();
* });
*/
const allTaskNames = taskNames.reduce((arr, names) => arr.concat(names), []); // flatmap the task names into a single array
// console.log(allTaskNames.concat(['watch']));
/* equivalent to:
* let allTaskNames = ['main_concatScripts', 'main_minifyScripts', 'app_concatScripts', 'app_minifyScripts'];
*/
gulp.task('default', gulp.series.apply(gulp, allTaskNames.concat(['styles', 'watch'])));
/* equivalent to:
* gulp.task('default', gulp.series('main_concatScripts', 'main_minifyScripts', 'app_concatScripts', 'app_minifyScripts', 'watch'));
*/
The dynamic task creators work. Unfortunately the minify tasks are starting before the concatenation task are finished. I am getting the following error, adding the allowEmpty option does work, but then the wrong concatenated file gets minified:
[12:57:19] Using gulpfile /repo/myapp/gulpfile.js
[12:57:19] Starting 'default'...
[12:57:19] Starting 'main_concatScripts'...
[12:57:19] Finished 'main_concatScripts' after 47 ms
[12:57:19] Starting 'main_minifyScripts'...
[12:57:19] 'main_minifyScripts' errored after 11 ms
[12:57:19] Error: File not found with singular glob: /repo/myapp/dist/main.js (if this was purposeful, use `allowEmpty` option)