Files
pathfinder/gulpfile.js
2020-04-06 17:45:47 +02:00

1168 lines
38 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* GULP itself */
'use strict';
let util = require('util');
let fs = require('fs');
let ini = require('ini');
let gulp = require('gulp');
let requirejsOptimize = require('gulp-requirejs-optimize');
let filter = require('gulp-filter');
let gulpif = require('gulp-if');
let jshint = require('gulp-jshint');
let sourcemaps = require('gulp-sourcemaps');
let zlib = require('zlib');
let gzip = require('gulp-gzip');
let brotli = require('gulp-brotli');
let uglifyjs = require('uglify-es');
let composer = require('gulp-uglify/composer');
let sass = require('gulp-sass');
let autoprefixer = require('gulp-autoprefixer');
let cleanCSS = require('gulp-clean-css');
let imageResize = require('gulp-image-resize');
let imagemin = require('gulp-imagemin');
let imageminWebp = require('imagemin-webp');
let rename = require('gulp-rename');
let bytediff = require('gulp-bytediff');
let debug = require('gulp-debug');
let notifier = require('node-notifier');
// -- Helper & NPM modules ----------------------------------------------------
let flatten = require('flat');
let padEnd = require('lodash.padend');
let merge = require('lodash.merge');
let minimist = require('minimist');
let slash = require('slash');
let fileExtension = require('file-extension');
let log = require('fancy-log');
let colors = require('ansi-colors');
let stylish = require('jshint-stylish');
let Table = require('terminal-table');
let prettyBytes = require('pretty-bytes');
let del = require('promised-del');
let minify = composer(uglifyjs, console);
sass.compiler = require('node-sass');
// == Settings ========================================================================================================
// build/src directories
let PATH = {
JS_HINT: {
CONF: '/.jshintrc'
},
ASSETS: {
DEST: './public'
},
JS: {
SRC: 'js/**/*.js',
SRC_LIBS: './js/lib/**/*',
DEST: 'public/js',
DEST_BUILD: './public/js/vX.X.X'
},
CSS: {
SRC: 'sass/**/*.scss',
},
IMG: {
SRC: 'img/**/*.{jpg,png,svg}',
SRC_HEADER: 'img/header/*.{jpg,png}',
SRC_GALLERY: 'img/gallery/**/*.{jpg,png}',
SRC_SVG: 'img/svg/*.svg'
}
};
// Pathfinder config files
let pathfinderConfigFileApp = './app/pathfinder.ini';
let pathfinderConfigFileConf = './conf/pathfinder.ini';
// CLI box size in characters
let cliBoxLength = 80;
// cache for already combined JS files
let combinedJsFiles = [];
// cache for tracked JS files
let trackedFiles = {};
// columns for tracked JS files (used for mapping)
let trackTable = {
cols: [
'file',
'src',
'src_percent',
'uglify',
'gzipFile',
'gzip_percent',
'gzip',
'brotliFile',
'brotli_percent',
'brotli',
'all_percent',
'mapFile',
'map'
]
};
// UglifyJS options
// https://www.npmjs.com/package/uglify-es
let uglifyJsOptions = {
warnings: true,
toplevel: false,
ecma: 8,
nameCache: {}, // cache mangled variable and property names across multiple invocations of minify()
keep_classnames: true // pass true to prevent discarding or mangling of class names.
};
// Sourcemaps options
// https://www.npmjs.com/package/gulp-sourcemaps
// -- Error output ------------------------------------------------------------
/**
* print error box output
* @param title
* @param example
*/
let printError = (title, example) => {
log('');
log(colors.danger.divider('error'));
log(colors.iconDanger(title));
if(example){
log(` ${colors.gray(example)}`);
}
log(colors.danger.divider(' '));
log('');
};
// == Settings ========================================================================================================
// parse pathfinder.ini config file for relevant data
let tagVersion;
try{
let pathfinderAppIni = ini.parse(fs.readFileSync(pathfinderConfigFileApp, 'utf-8'));
let pathfinderConfIni = fs.existsSync(pathfinderConfigFileConf) ?
ini.parse(fs.readFileSync(pathfinderConfigFileConf, 'utf-8')) : {};
let pathfinderIni = merge(pathfinderAppIni, pathfinderConfIni);
try{
tagVersion = pathfinderIni.PATHFINDER.VERSION;
}catch(err){
printError(
err.message,
'Missing "PATHFINDER.VERSION" in "' + pathfinderConfigFileApp + '"');
process.exit(1);
}
}catch(err){
printError(
err.message,
'Check read permissions for "' + pathfinderConfigFileApp + '"');
process.exit(1);
}
// parse CLI parameters
let options = minimist(process.argv.slice(2));
// custom task configuration (user CLI options if provided) (overwrites default)
let CONF = {
TASK: options.hasOwnProperty('_') ? options._[0] : undefined,
TAG: options.tag ? options.tag : tagVersion ? tagVersion : undefined,
JS: {
UGLIFY: options.hasOwnProperty('jsUglify') ? options.jsUglify === 'true': undefined,
SOURCEMAPS: options.hasOwnProperty('jsSourcemaps') ? options.jsSourcemaps === 'true': undefined,
GZIP: options.hasOwnProperty('jsGzip') ? options.jsGzip === 'true': undefined,
BROTLI: options.hasOwnProperty('jsBrotli') ? options.jsBrotli === 'true': undefined
},
CSS: {
SOURCEMAPS: options.hasOwnProperty('cssSourcemaps') ? options.cssSourcemaps === 'true': undefined,
GZIP: options.hasOwnProperty('cssGzip') ? options.cssGzip === 'true': undefined,
BROTLI: options.hasOwnProperty('cssBrotli') ? options.cssBrotli === 'true': undefined
},
IMG: {
ACTIVE: options.hasOwnProperty('imgActive') ? options.imgActive === 'true': undefined,
},
DEBUG: false
};
// -- Plugin options ----------------------------------------------------------
let sassOptions = {
errorLogToConsole: true,
outputStyle: 'compressed' // nested, expanded, compact, compressed
};
let autoprefixerOptions = {
// browsers: ['last 2 versions'], read from package.json key: "browserslist"
cascade: false
};
let cleanCssOptions = {
compatibility: '*',
level: 2
};
let gZipOptions = {
append: false, // disables default append ext .gz
extension: 'gz', // use "custom" ext: .gz
threshold: '1kb', // min size required to compress a file
deleteMode: PATH.JS.DEST_BUILD, // replace *.gz files if size < 'threshold'
gzipOptions: {
level: 9 // zlib.Gzip compression level [0-9]
},
skipGrowingFiles: true // use orig. files in case of *.gz size > orig. size
};
let brotliOptions = {
extension: 'br', // use "custom" ext: .br
skipLarger: true, // use orig. files in case of *.br size > orig. size
params: {
[zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_TEXT, // compression mode for UTF-8 formatted text
[zlib.constants.BROTLI_PARAM_QUALITY]: zlib.constants.BROTLI_MAX_QUALITY // quality [1 worst - 11 best]
}
};
let compressionExt = [gZipOptions.extension, brotliOptions.extension];
let imageminWebpOptions = {quality: 80};
let imgResizeOptions = {
crop : true,
upscale : false,
noProfile: true
};
colors.theme({
primary: colors.cyan,
success: colors.green,
info: colors.blue,
warning: colors.yellow,
danger: colors.red,
disabled: (...args) => colors.dim.gray(util.format(...args)),
comment: (...args) => colors.disabled.italic(util.format(...args)),
//badgeSuccess: (...args) => `${colors.success.bold(`[OK]`)} ${colors.success(util.format(...args))}`,
//badgeError: (...args) => `${colors.danger.bold(`[ERROR]`)} ${colors.danger.italic(util.format(...args))}`,
iconSuccess: text => ` ${colors.success(``)} ${colors.success(text)}`,
iconDanger: text => ` ${colors.danger(``)} ${colors.danger(text)}`,
divider: text => colors.disabled(`${(text || '').trim().length ? `${(text || '').trim().toUpperCase()}` : ''}`.padEnd(cliBoxLength, '═'))
});
log(colors.primary('primary'));
log(colors.success('success'));
log(colors.info('info'));
log(colors.warning('warning'));
log(colors.danger('danger'));
log(colors.disabled('disabled'));
log(colors.comment('comment'));
//log(colors.badgeSuccess(`Success`));
//log(colors.badgeError('Error!'));
log(colors.iconSuccess('success'));
log(colors.iconDanger('danger'));
log(colors.bold('bold'));
log(colors.italic('italic'));
log(colors.underline('underline'));
log('');
log(colors.magenta('magenta'));
log(colors.magentaBright('magentaBright'));
log(colors.cyan('cyan'));
log(colors.cyanBright('cyanBright'));
log(colors.blue('blue'));
log(colors.blueBright('blueBright'));
log(colors.yellow('yellow'));
log(colors.yellowBright('yellowBright'));
log('');
log('');
// == Helper methods ==================================================================================================
/**
* track a file by size and filename, provide a mapping (see trackTable)
* @param data
* @param mapping
*/
let trackFile = (data, mapping) => {
let fileNameParts = data.fileName.split('.');
let fileExt = fileNameParts.pop();
let srcFileName = compressionExt.concat(['map']).includes(fileExt) ? fileNameParts.join('.') : data.fileName;
let fileData = trackedFiles[srcFileName] || [];
// change mapping for *.map files
switch(fileExt){
case 'js':
mapping.all_percent = 'percent';
break;
case 'map':
mapping = {mapFile: 'fileName', map: 'endSize'};
break;
case gZipOptions.extension:
data.all_percent = data.endSize / fileData[0];
mapping = {gzipFile: 'fileName', gzip_percent: 'percent', gzip: 'endSize', all_percent: 'all_percent'};
break;
case brotliOptions.extension:
data.all_percent = data.endSize / fileData[0];
mapping = {brotliFile: 'fileName', brotli_percent: 'percent', brotli: 'endSize', all_percent: 'all_percent'};
break;
}
for(let col in mapping){
for(let i = 0; i < trackTable.cols.length; i++){
if(trackTable.cols[i] === col){
fileData[i - 1] = data[mapping[col]];
break;
}
}
}
trackedFiles[srcFileName] = fileData;
};
/**
* recursive "merge" two config objects
* @param confUser
* @param confDefault
* @returns {*}
*/
let mergeConf = (confUser, confDefault) => {
for (let confKey in confUser) {
if (confUser.hasOwnProperty(confKey)){
if(confDefault.hasOwnProperty(confKey)){
if(
typeof confUser[confKey] === 'object' &&
typeof confDefault[confKey] === 'object'
){
confUser[confKey] = mergeConf(confUser[confKey], confDefault[confKey]);
}else if(typeof confUser[confKey] === 'undefined'){
confUser[confKey] = confDefault[confKey];
}
}
}
}
return confUser;
};
// == CLI output ======================================================================================================
/**
* print help information for all Gulp tasks
*/
let printHelp = () => {
log(`
${colors.divider('help')}
${colors.bold('docs:')} ${colors.info('https://github.com/exodus4d/pathfinder/wiki/GulpJs')}
${colors.bold('command:')} ${colors.magenta('$ npm run gulp')} ${colors.primary.italic('[task]')} ${colors.yellow.italic('-- [--options] …')}
${colors.bold('tasks:')}
${colors.primary('help')} This view
${colors.primary('default')} Development environment. Working with row src files and file watcher, default:
${colors.primary('')} ${colors.gray('--jsUglify=false --jsSourcemaps=false --cssSourcemaps=false --jsGzip=false --cssGzip=false --jsBrotli=false --cssBrotli=false')}
${colors.primary('production')} Production build. Concat and uglify static resources, default:
${colors.primary('')} ${colors.gray('--jsUglify=true --jsSourcemaps=true --cssSourcemaps=true --jsGzip=true --cssGzip=true --jsBrotli=true --cssBrotli=true')}
${colors.bold('helper tasks:')}
${colors.primary('images')} Build images. Convert src *.png images to *.webp and *.jpg. Crop & resize tasks
${colors.bold('options:')}
${colors.yellow('--tag')} Set build version. ${colors.gray(`default: --tag="${tagVersion}" -> dest path: public/js/${tagVersion}`)}
${colors.yellow('--jsUglify')} Set js uglification. ${colors.gray('(true || false)')}
${colors.yellow('--jsSourcemaps')} Set js sourcemaps generation. ${colors.gray('(true || false)')}
${colors.yellow('--jsGzip')} Set js "gzip" compression mode. ${colors.gray('(true || false)')}
${colors.yellow('--jsBrotli')} Set js "brotli" compression mode. ${colors.gray('(true || false)')}
${colors.yellow('--cssSourcemaps')} Set CSS sourcemaps generation. ${colors.gray('(true || false)')}
${colors.yellow('--cssGzip')} Set CSS "gzip" compression mode. ${colors.gray('(true || false)')}
${colors.yellow('--cssBrotli')} Set CSS "brotli" compression mode. ${colors.gray('(true || false)')}
${colors.yellow('--imgActive')} Disables all IMG tasks. ${colors.gray('(true || false)')}
${colors.yellow('--debug')} Set debug mode (more output). ${colors.gray('(true || false)')}
${colors.divider(' ')}
`);
};
/**
* print JS summary table
*/
let printJsSummary = () => {
let tableHead = trackTable.cols;
let byteCols = [1,3,6,9,12];
let percentCols = [2, 5, 8, 10];
let sortCol = (CONF.JS.BROTLI || CONF.CSS.BROTLI || CONF.JS.GZIP || CONF.CSS.GZIP || CONF.JS.UGLIFY) ? 10 : CONF.JS.SOURCEMAPS ? 3 : 1;
let refAllCol = (CONF.JS.BROTLI || CONF.CSS.BROTLI) ? 9 : (CONF.JS.GZIP || CONF.CSS.GZIP) ? 6 : CONF.JS.UGLIFY ? 3 : CONF.JS.SOURCEMAPS ? 3 : 1;
let highLightSections = {src_percent: [], gzip_percent: [], brotli_percent: [], all_percent: []};
let highLightRow = {
success: JSON.parse(JSON.stringify(highLightSections)),
warning: JSON.parse(JSON.stringify(highLightSections)),
danger: JSON.parse(JSON.stringify(highLightSections))
};
let numFormatCols = byteCols.concat(percentCols);
let sumRow = [];
let widthCol = [35, 10, 8, 10, 35, 8, 10, 35, 8, 10, 8, 35, 10];
let table = new Table({
borderStyle: 2,
horizontalLine: true,
width: widthCol,
rightPadding: 0,
leftPadding: 0
});
// -- Table header --------------------------------------------------------
table.push(tableHead.map(label => label.replace(/^(.*?)_(percent?).*/i, '$1 %')));
// convert JSON to array
let tableData = [];
for (let fileName in trackedFiles) {
let rowData = trackedFiles[fileName];
rowData.unshift(fileName);
tableData.push(rowData);
}
let tableHeight = tableData.length + 1;
let tableWidth = tableHead.length;
tableData.sort((a,b) => a[sortCol] - b[sortCol]);
table.attr(0, sortCol, {
color: 'cyan'
});
// -- Table body ----------------------------------------------------------
let tmpMapping = {byteCols: byteCols, percentCols: percentCols};
// sum column data for footer
let sumCols = (arr, mapping) => arr.map((x, rowIdx) =>
x.map((y, i) => {
if(mapping.byteCols.includes(i)) {
sumRow[i] = (sumRow[i]) ? sumRow[i] + y : y;
}
return y;
})
);
// format table cell data
let formatCols = (arr, mapping) => arr.map((x, rowIdx) =>
x.map((y, i) => {
if(mapping.byteCols.includes(i)) {
return prettyBytes(y);
}else if(mapping.percentCols.includes(i)){
// 0.0% diff is "success" in case of Uglify is disabled
let isSuccess = (
y === 1 &&
!CONF.JS.UGLIFY &&
tableHead[i] === 'src_percent'
);
if(y < 0.3 || isSuccess) {
highLightRow.success[tableHead[i]].push(rowIdx);
}else if(y < 0.5 ){
highLightRow.warning[tableHead[i]].push(rowIdx);
}else{
highLightRow.danger[tableHead[i]].push(rowIdx);
}
return y ? (100 * (1 - y )).toFixed(1) + '%' : '';
}else{
return y;
}
})
);
tableData = sumCols(tableData, tmpMapping);
// -- Table footer --------------------------------------------------------
// percent cell src
sumRow[2] = ((sumRow[3] / sumRow[1]));
// percent cell gzip
sumRow[5] = (((sumRow[6] || 0) / sumRow[3]));
// percent cell brotli
sumRow[8] = (((sumRow[9] || 0) / sumRow[3]));
// percent cell all
sumRow[10] = (((sumRow[refAllCol] || 0) / sumRow[1]));
tableData.push(sumRow);
tableData = formatCols(tableData, tmpMapping);
// add rows
for(let i = 0; i < tableData.length; i++){
table.push(tableData[i]);
}
// -- Table format --------------------------------------------------------
for(let i = 0; i < numFormatCols.length; i++){
table.attrRange({row: [0], column: [numFormatCols[i], numFormatCols[i] + 1]}, {
align: 'right',
rightPadding: 1
});
}
for (let highLight in highLightRow) {
if (highLightRow.hasOwnProperty(highLight)){
for (let highLightSection in highLightRow[highLight]) {
for(let i = 0; i < highLightRow[highLight][highLightSection].length; i++){
let rowIdx = highLightRow[highLight][highLightSection][i];
let color = (highLight === 'success') ? 'green' : (highLight === 'warning') ? 'yellow' : 'red';
let colFrom = 0;
let colTo = 1;
switch(highLightSection){
case 'src_percent':
colTo = 1;
colTo = 4;
break;
case 'gzip_percent':
colFrom = 4;
colTo = 7;
break;
case 'brotli_percent':
colFrom = 7;
colTo = 10;
break;
case 'all_percent':
colFrom = 10;
colTo = 11;
break;
}
table.attrRange({row: [rowIdx + 1, rowIdx + 2], column: [colFrom, colTo]},{
color: color
});
}
}
}
}
// -- Remove irrelevant columns -------------------------------------------
if(!CONF.JS.SOURCEMAPS){
table.removeColumn(12);
table.removeColumn(11);
if(!CONF.JS.BROTLI && !CONF.CSS.BROTLI && !CONF.JS.GZIP && !CONF.CSS.GZIP){
table.removeColumn(10);
table.removeColumn(9);
table.removeColumn(8);
table.removeColumn(7);
table.removeColumn(6);
table.removeColumn(5);
table.removeColumn(4);
if(!CONF.JS.UGLIFY){
table.removeColumn(3);
table.removeColumn(2);
}
}
}
console.log(table.toString());
// reset tracked files for next run e.g. watch change
trackedFiles = {};
};
// == clean up tasks ==================================================================================================
/**
* clean temp JS dest dir
*/
gulp.task('task:cleanJsDestBuild', () => del([PATH.JS.DEST_BUILD]));
/**
* clean JS dest dir
*/
gulp.task('task:cleanJsDest', () => del([PATH.JS.DEST + '/' + CONF.TAG]));
/**
* clean CSS dest dir
*/
gulp.task('task:cleanCssDest', () => del([PATH.ASSETS.DEST + '/css/' + CONF.TAG]));
/**
* clean IMG dest dir
*/
gulp.task('task:cleanImgDest', () => del([PATH.ASSETS.DEST + '/img/' + CONF.TAG]));
// == Dev tasks (code analyses) =======================================================================================
gulp.task('task:hintJS', () => {
return gulp.src([PATH.JS.SRC, '!' + PATH.JS.SRC_LIBS])
.pipe(jshint(__dirname + PATH.JS_HINT.CONF))
.pipe(jshint.reporter(stylish));
});
// == JS build tasks ==================================================================================================
/**
* concat/build JS files by modules
*/
gulp.task('task:concatJS', () => {
let modules = ['login', 'mappage', 'setup', 'admin', 'pnotify.loader', 'datatables.loader', 'summernote.loader'];
let srcModules = ['./js/app/*(' + modules.join('|') + ').js'];
return gulp.src(srcModules, {base: 'js'})
.pipe(gulpif(CONF.JS.SOURCEMAPS, sourcemaps.init()))
.pipe(requirejsOptimize(function(file){
return {
name: file.stem,
baseUrl: 'js',
mainConfigFile: './js/app.js',
optimize: 'none',
inlineText: false,
removeCombined: true,
preserveLicenseComments: false, // required for sourcemaps
findNestedDependencies: false,
include: ['text'],
// excludeShallow: ['app'],
// excludeShallow: ['./js/app.js'],
// exclude: ['app.js'],
// path: {
// pp: './../js/app' // the main config file will not be build
// },
onModuleBundleComplete: function(data){
// collect all combined js files
combinedJsFiles = [...new Set(combinedJsFiles.concat(data.included))];
}
};
}))
.pipe(bytediff.start())
.pipe(gulpif(CONF.JS.UGLIFY, minify(uglifyJsOptions).on('warnings', log)))
.pipe(gulpif(CONF.JS.SOURCEMAPS, sourcemaps.write('.', {includeContent: false, sourceRoot: '/js'}))) // prod (minify)
.pipe(bytediff.stop(data => {
trackFile(data, {src: 'startSize', src_percent: 'percent', uglify: 'endSize'});
return colors.green('Build concat file "' + data.fileName + '"');
}))
.pipe(gulp.dest(PATH.JS.DEST_BUILD));
});
/**
* build standalone JS files
*/
gulp.task('task:diffJS', () => {
return gulp.src(PATH.JS.SRC, {base: 'js', since: gulp.lastRun('task:diffJS')})
.pipe(filter(file => {
return combinedJsFiles.indexOf(slash(file.path)) < 0;
}))
.pipe(debug({title: 'Copy JS src: ', showFiles: false}))
.pipe(bytediff.start())
.pipe(gulpif(CONF.JS.SOURCEMAPS, sourcemaps.init()))
.pipe(gulpif(CONF.JS.UGLIFY, minify(uglifyJsOptions)))
.pipe(gulpif(CONF.JS.SOURCEMAPS, sourcemaps.write('.', {includeContent: false, sourceRoot: '/js'})))
.pipe(bytediff.stop(data => {
trackFile(data, {src: 'startSize', src_percent: 'percent', uglify: 'endSize'});
return colors.iconSuccess(`build → ${colors.magenta(data.fileName)}`);
}))
.pipe(gulp.dest(PATH.JS.DEST_BUILD, {overwrite: false}));
});
/**
* get assets filter options e.g. for gZip or Brotli assets
* @param compression
* @param fileExt
* @returns {{fileFilter, srcModules: string[]}}
*/
let getAssetFilterOptions = (compression, fileExt) => {
return {
srcModules: [
PATH.ASSETS.DEST +'/**/*.' + fileExt,
'!' + PATH.ASSETS.DEST + '/js/' + CONF.TAG + '{,/**/*}'
],
fileFilter: filter(file => CONF[fileExt.toUpperCase()][compression.toUpperCase()])
};
};
/**
* build gZip assets
* @param config
* @param taskName
* @returns {*}
*/
let gzipAssets = (config, taskName) => {
return gulp.src(config.srcModules, {base: 'public', since: gulp.lastRun(taskName)})
.pipe(config.fileFilter)
.pipe(debug({title: 'Gzip asses dest: ', showFiles: false}))
.pipe(bytediff.start())
.pipe(gzip(gZipOptions))
.pipe(bytediff.stop(data => {
trackFile(data, {gzipFile: 'fileName', gzip: 'endSize'});
if(fileExtension(data.fileName) === gZipOptions.extension){
return colors.green('Gzip generate "' + data.fileName + '"');
}else{
return colors.gray('Gzip skip "' + data.fileName + '". Size < ' + gZipOptions.threshold + ' (threehold)');
}
}))
.pipe(gulp.dest(PATH.ASSETS.DEST));
};
/**
* build Brotli assets
* @param config
* @param taskName
* @returns {*}
*/
let brotliAssets = (config, taskName) => {
return gulp.src(config.srcModules, {base: 'public', since: gulp.lastRun(taskName)})
.pipe(config.fileFilter)
.pipe(debug({title: 'Brotli asses dest: ', showFiles: false}))
.pipe(bytediff.start())
.pipe(brotli(brotliOptions))
.pipe(bytediff.stop(data => {
trackFile(data, {brotliFile: 'fileName', brotli: 'endSize'});
if(fileExtension(data.fileName) === brotliOptions.extension){
return colors.green('Brotli generate "' + data.fileName + '"');
}else{
return colors.gray('Brotli skip "' + data.fileName + '"');
}
}))
.pipe(gulp.dest(PATH.ASSETS.DEST));
};
gulp.task('task:gzipJsAssets', () => {
return gzipAssets(getAssetFilterOptions('gzip', 'js'), 'task:gzipJsAssets');
});
gulp.task('task:gzipCssAssets', () => {
return gzipAssets(getAssetFilterOptions('gzip', 'css'), 'task:gzipCssAssets');
});
gulp.task('task:brotliJsAssets', () => {
return brotliAssets(getAssetFilterOptions('brotli', 'js'), 'task:brotliJsAssets');
});
gulp.task('task:brotliCssAssets', () => {
return brotliAssets(getAssetFilterOptions('brotli', 'css'), 'task:brotliCssAssets');
});
/**
* rename "temp" build JS folder to final dest folder
* (keep "old" build data as long as possible in case of build failure)
*/
gulp.task('task:renameJsDest', () => {
let fileExt = ['js', 'map'].concat(compressionExt);
return gulp.src( PATH.JS.DEST_BUILD + '/**/*.{' + fileExt.join(',') + '}', {base: PATH.JS.DEST_BUILD, since: gulp.lastRun('task:renameJsDest')})
.pipe(debug({title: 'Rename JS dest: ', showFiles: false}))
.pipe(gulp.dest(PATH.JS.DEST_BUILD + '/../' + CONF.TAG));
});
// == CSS build tasks =================================================================================================
/**
* build CSS rom SASS files
* -> 1. node-sass
* 2. autoprefixer
*/
gulp.task('task:sass', () => {
return gulp.src(PATH.CSS.SRC)
.pipe(gulpif(CONF.CSS.SOURCEMAPS, sourcemaps.init()))
.pipe(sass(sassOptions).on('error', sass.logError))
.pipe(bytediff.start())
.pipe(bytediff.stop(data => {
trackFile(data, {src: 'startSize', src_percent: 'percent', uglify: 'endSize'});
return colors.iconSuccess(`build → ${colors.magenta(data.fileName)}`);
}))
.pipe(gulpif(CONF.CSS.SOURCEMAPS, sourcemaps.write('.', {includeContent: false, sourceRoot: '../../../sass'})))
.pipe(gulp.dest(PATH.ASSETS.DEST + '/css/' + CONF.TAG))
.pipe(filter(['!*.map']))
.pipe(gulpif(CONF.CSS.SOURCEMAPS, sourcemaps.init({loadMaps: true})))
.pipe(autoprefixer(autoprefixerOptions))
.pipe(bytediff.start())
.pipe(bytediff.stop(data => {
trackFile(data, {src: 'startSize', src_percent: 'percent', uglify: 'endSize'});
return colors.green('Autoprefix CSS file "' + data.fileName + '"');
}))
.pipe(gulpif(CONF.CSS.SOURCEMAPS, sourcemaps.write('.', {includeContent: false})))
.pipe(gulp.dest(PATH.ASSETS.DEST + '/css/' + CONF.TAG));
});
/**
* css-clean can be used to "optimize" generated CSS [optional]
*/
gulp.task('task:cleanCss', () => {
return gulp.src('public/css/' + CONF.TAG + '/*.css')
.pipe(gulpif(CONF.CSS.SOURCEMAPS, sourcemaps.init({loadMaps: true})))
.pipe(cleanCSS(cleanCssOptions))
.pipe(gulpif(CONF.CSS.SOURCEMAPS, sourcemaps.write('.', {includeContent: false})))
.pipe(gulp.dest(PATH.ASSETS.DEST + '/css/' + CONF.TAG));
});
// == Image build tasks ===============================================================================================
let imgWebpHandler = (config, taskName) => {
return gulp.src(config.src, {base: config.base, since: gulp.lastRun(taskName)})
.pipe(imagemin([imageminWebp(config.options)], {verbose: true}))
.pipe(rename({extname: '.webp'}))
.pipe(gulp.dest(PATH.ASSETS.DEST + '/img/' + CONF.TAG));
};
gulp.task('task:imgHeadToWEBP', () => {
return imgWebpHandler({
src: PATH.ASSETS.DEST + '/img/' + CONF.TAG + '/header/**/*.png',
base: PATH.ASSETS.DEST + '/img/' + CONF.TAG,
options: imageminWebpOptions
}, 'task:imgHeadToWEBP');
});
gulp.task('task:imgGalleryToWEBP', () => {
return imgWebpHandler({
src: PATH.ASSETS.DEST + '/img/' + CONF.TAG + '/gallery/**/*.jpg',
base: PATH.ASSETS.DEST + '/img/' + CONF.TAG,
options: Object.assign({}, imageminWebpOptions, {quality: 90}) // unfortunately src jpg´s are already high compressed
}, 'task:imgGalleryToWEBP');
});
let imgResizeHandler = (config, taskName) => {
return gulp.src(config.src, {base: 'img', since: gulp.lastRun(taskName)})
.pipe(imageResize(config.options))
.pipe(gulpif(config.rename, rename(config.rename)))
.pipe(gulp.dest(PATH.ASSETS.DEST + '/img/' + CONF.TAG));
};
let imgResizeHeaderTasks = [];
[480, 780, 1200, 1600, 3840].forEach(size => {
['png', 'jpg'].forEach(format => {
let taskName = `task:imgHead${size}${format}`;
gulp.task(taskName, () => {
return imgResizeHandler({
src: PATH.IMG.SRC_HEADER,
options: Object.assign({}, imgResizeOptions, {format: format, width: size}),
rename: {suffix: `-${size}`}
}, taskName);
});
imgResizeHeaderTasks.push(taskName);
});
});
gulp.task('task:imgHeadResize', gulp.series(
gulp.parallel(...imgResizeHeaderTasks),
'task:imgHeadToWEBP'
));
gulp.task('task:imgGalleryCopy', () => {
return gulp.src(PATH.IMG.SRC_GALLERY, {base: 'img', since: gulp.lastRun('task:imgGalleryCopy')})
.pipe(gulp.dest(PATH.ASSETS.DEST + '/img/' + CONF.TAG));
});
gulp.task('task:imgGallery', gulp.series(
'task:imgGalleryCopy',
'task:imgGalleryToWEBP'
));
gulp.task('task:imgRest', () => {
return gulp.src([
PATH.IMG.SRC,
`!${PATH.IMG.SRC_HEADER}`,
`!${PATH.IMG.SRC_GALLERY}`,
`!${PATH.IMG.SRC_SVG}`
], {base: 'img', since: gulp.lastRun('task:imgRest')})
.pipe(debug({title: 'Copy img "rest" dest: ', showFiles: false}))
.pipe(gulp.dest(PATH.ASSETS.DEST + '/img/' + CONF.TAG));
});
gulp.task('task:imgSVG', () => {
return gulp.src(PATH.IMG.SRC_SVG, {base: 'img', since: gulp.lastRun('task:imgSVG')})
.pipe(debug({title: 'Copy img SVG dest: ', showFiles: false}))
.pipe(gulp.dest(PATH.ASSETS.DEST + '/img/' + CONF.TAG));
});
// == Helper tasks ====================================================================================================
/**
* print Gulp help information
*/
gulp.task('task:printHelp', done => {
printHelp();
done();
});
/**
* print JS build task summary as table (e.g. show saved file size)
*/
gulp.task('task:printJsSummary', done => {
printJsSummary();
done();
});
/**
* print task configuration (e.g. CLI parameters)
*/
gulp.task('task:printConfig', done => {
let error = colors.danger;
let columnLength = Math.round(cliBoxLength / 2);
log(colors.divider('config'));
let configFlat = flatten(CONF);
for (let key in configFlat) {
if (configFlat.hasOwnProperty(key)){
let value = configFlat[key];
let success = colors.success;
switch(key){
case 'TASK': success = colors.primary; break;
}
// format value
value = padEnd((typeof value === 'undefined') ? 'undefined': value, columnLength);
log(
colors.yellow(padEnd(key, columnLength)),
configFlat[key] ? success(value) : error(value)
);
}
}
log(colors.divider(' '));
done();
});
/**
* check CLI parameters and task config
*/
gulp.task('task:checkConfig', done => {
if(!CONF.TAG){
printError(`Missing TAG version. Add param: ${colors.yellow('--tag')}`, `$ npm run gulp default -- --tag="${tagVersion}"`);
process.exit(0);
}
done();
});
/**
* configure "develop" task
*/
gulp.task('task:configDevelop',
gulp.series(
done => {
let CONF_DEVELOP = {
JS: {
UGLIFY: false,
SOURCEMAPS: false,
GZIP: false,
BROTLI: false
},
CSS: {
SOURCEMAPS: true,
GZIP: false,
BROTLI: false
},
IMG: {
ACTIVE: true
}
};
CONF = mergeConf(CONF, CONF_DEVELOP);
done();
},
'task:printConfig',
'task:checkConfig'
)
);
/**
* configure "production" task
*/
gulp.task('task:configProduction',
gulp.series(
done => {
let CONF_PRODUCTION = {
JS: {
UGLIFY: true,
SOURCEMAPS: true,
GZIP: true,
BROTLI: true
},
CSS: {
SOURCEMAPS: true,
GZIP: true,
BROTLI: true
},
IMG: {
ACTIVE: true
}
};
CONF = mergeConf(CONF, CONF_PRODUCTION);
done();
},
'task:printConfig',
'task:checkConfig'
)
);
/**
* updates JS destination move to (final) dir
*/
gulp.task('task:updateJsDest', gulp.series(
gulp.parallel(
'task:gzipJsAssets',
'task:brotliJsAssets'
),
'task:renameJsDest',
// 'task:printJsSummary',
'task:cleanJsDestBuild'
));
/**
* build JS dest files (concat, uglify, sourcemaps)
*/
gulp.task('task:buildJs', gulp.series(
'task:concatJS',
'task:diffJS',
'task:cleanJsDest',
'task:updateJsDest'
));
/**
* build SCSS dest files
*/
gulp.task('task:buildCss', gulp.series(
'task:sass'
));
/**
* build IMG dest files
*/
gulp.task('task:buildImg', gulp.parallel(
'task:imgHeadResize',
'task:imgGallery',
'task:imgRest',
'task:imgSVG'
));
// == Notification tasks ==============================================================================================
/**
* JS Build done notification
*/
gulp.task('task:notifyJsDone', done => {
notifier.notify({
title: 'Done JS build',
message: 'JS build task finished',
icon: PATH.ASSETS.DEST + '/img/logo.png',
wait: false
});
done();
});
/**
* CSS Build done notification
*/
gulp.task('task:notifyCssDone', done => {
notifier.notify({
title: 'Done CSS build',
message: 'CSS build task finished',
icon: PATH.ASSETS.DEST + '/img/logo.png',
wait: false
});
done();
});
// == Watcher tasks ===================================================================================================
/**
* task for JS src file changes
*/
gulp.task(
'task:watchJsSrc',
gulp.series(
'task:hintJS',
'task:diffJS',
'task:updateJsDest',
'task:notifyJsDone'
)
);
/**
* task for JS src file changes
*/
gulp.task(
'task:watchCss',
gulp.series(
'task:buildCss',
// 'task:cleanCss',
gulp.parallel(
'task:gzipCssAssets',
'task:brotliCssAssets'
),
// 'task:printJsSummary',
'task:notifyCssDone'
)
);
gulp.task(
'task:watchImg',
gulp.series(
'task:buildImg'
)
);
/**
* watch files for changes
*/
gulp.task('task:setWatcherJs', () => gulp.watch(PATH.JS.SRC, gulp.series('task:watchJsSrc', 'task:printJsSummary')));
gulp.task('task:setWatcherCss', () => gulp.watch(PATH.CSS.SRC, gulp.series('task:watchCss', 'task:printJsSummary')));
gulp.task('task:setWatcherImg', () => gulp.watch(PATH.IMG.SRC, gulp.series('task:watchImg', 'task:printJsSummary')));
gulp.task('task:setWatcher',
gulp.parallel(
'task:setWatcherJs',
'task:setWatcherCss',
'task:setWatcherImg'
)
);
// == Default/Main tasks ==============================================================================================
gulp.task(
'help',
gulp.series(
'task:printHelp'
)
);
gulp.task(
'default',
gulp.series(
'task:configDevelop',
gulp.parallel(
gulp.series(
'task:cleanJsDestBuild',
'task:watchJsSrc'
),
gulp.series(
'task:cleanCssDest',
'task:watchCss'
),
gulp.series(
'task:cleanImgDest',
'task:watchImg'
)
),
'task:printJsSummary',
'task:setWatcher'
)
);
gulp.task(
'production',
gulp.series(
'task:configProduction',
gulp.parallel(
gulp.series(
'task:cleanJsDestBuild',
'task:buildJs'
),
gulp.series(
'task:cleanCssDest',
'task:watchCss'
),
gulp.series(
'task:cleanImgDest',
'task:watchImg'
)
),
'task:printJsSummary'
)
);