Skip to content

Instantly share code, notes, and snippets.

@juliankasimir
Last active February 2, 2023 10:28
Show Gist options
  • Save juliankasimir/dbb51c70befc761f638dc815e6e4d1ea to your computer and use it in GitHub Desktop.
Save juliankasimir/dbb51c70befc761f638dc815e6e4d1ea to your computer and use it in GitHub Desktop.
Gulpfile
/**
* @file This is my gulpfile
* @author Julian Kasimir <info@jolution.de>
* @copyright Jolution 2022
* @version 1.2 - 14.05.2022
* @description Install with:
* npm install gulp-if fs gulp-changed fancy-log gulp-postcss postcss-import gulp-js-import gulp-typescript gulp-eslint gulp-notify gulp-plumber gulp-sourcemaps gulp-htmlmin gulp-nunjucks gulp-webp gulp-dart-sass autoprefixer cssnano files-exist gulp-babel gulp-uglify gulp-rename del gulp-replace gulp-header-comment gulp-preprocess browser-sync minimist @babel/preset-env --save-dev
* npm install tailwindcss @fortawesome/fontawesome-free --save
* Start with:
* gulp --env development
* @license MIT
* @see {@link https://gist.github.com/juliankasimir/dbb51c70befc761f638dc815e6e4d1ea} Source (Github Gist)
* @see {@link https://gist.github.com/juliankasimir/15c24bd0d26175dc1890d4b9052ace55} variables.json (Github Gist)
* @see {@link https://gist.github.com/juliankasimir/d80326a48d901589ca12298c7ccaf1f6} tsconfig.json (Github Gist)
* @see {@link https://gist.github.com/juliankasimir/d683ea15a83cd1e229c2fbcbe3637c47} tailwind.config.js (Github Gist)
*/
/* jshint node: true */
'use strict';
/**
* Set the date options
* @tutorial https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleDateString
*/
const dateOptions = {
year: 'numeric',
month: 'numeric',
day: 'numeric'
};
const {
src,
dest,
parallel,
series,
watch
} = require("gulp");
const {Stream} = require("stream");
/**
* This file requires the following modules
*/
const gulpif = require("gulp-if"),
fs = require("fs"),
changed = require('gulp-changed'),
log = require("fancy-log"),
variablesFile = fs.readFileSync("variables.json"),
variables = JSON.parse(variablesFile),
postcss = require("gulp-postcss"),
tailwindcss = require('tailwindcss'),
atImport = require("postcss-import"),
jsImport = require("gulp-js-import"),
ts = require("gulp-typescript"),
tsconfig = require("./tsconfig.json"),
//eslint = require('gulp-eslint'),
exec = require("child_process").exec,
notify = require("gulp-notify"),
plumber = require("gulp-plumber"),
sourcemaps = require("gulp-sourcemaps"),
htmlmin = require("gulp-htmlmin"),
nunjucks = require("gulp-nunjucks"),
webp = require('gulp-webp'),
scss = require("gulp-dart-sass"),
autoprefixer = require("autoprefixer"),
cssnano = require("cssnano"),
filesExist = require("files-exist"),
babel = require("gulp-babel"),
uglify = require("gulp-uglify"),
rename = require("gulp-rename"),
//del = require("del"),
replace = require("gulp-replace"),
headerComment = require("gulp-header-comment"),
preprocess = require("gulp-preprocess"),
browsersync = require("browser-sync").create(),
minimist = require('minimist');
/**
* get Environment
* @summary If the flag "dev" was passed when starting Gulp, the variable isEnv defined as (production|development)
* @param {string} process.argv[2] - command line flag "--env development" or "--env production"
* @see {@link https://github.com/gulpjs/gulp/blob/master/docs/recipes/pass-arguments-from-cli.md}
*/
let knownOptions = {
string: 'env',
default: {env: process.env.NODE_ENV || variables.settings.isEnv || 'production'}
};
let envOptions = minimist(process.argv.slice(2), knownOptions);
let isEnv = envOptions.env,
isServer = variables.settings.isServer,
build;
/**
* Del Map assets
* @summary Remove map files from the folder as well as SourceMap entries. Only in Developer Mode
* @example
* // Remove CSS SourceMap Entries
* .pipe(replace(/\/\*# sourceMappingURL=.*\/gm, ''))
* @example
* // Remove JS SourceMap Entries
* .pipe(replace(/\/\/# sourceMappingURL=.*\/gm, ''))
* @param {callback} cb - The callback that handles the response.
*/
function clean(cb) {
if (isEnv === 'production') {
/*del([
`${variables.config.targetCSS}/map`,
`${variables.config.targetJS}/map`,
], {
force: true
});*/
let SRC = [variables.config.targetCSS + '**/*.css', variables.config.targetJS + '**/*.js'];
src(SRC, {
base: './'
})
.pipe(replace(/\/\*# sourceMappingURL=.*/gm, ''))
.pipe(replace(/\/\/# sourceMappingURL=.*/gm, ''))
.pipe(dest('./'));
log.info(`Delete
SourceMap entrys from
${SRC}`);
}
cb();
}
/**
* Output Git Branch name
* @summary Output as info message when running Gulp which Git-Branch we are currently on.
* @example
* // log "👨‍💻 Develop Branch"
* log.info("👨‍💻 Develop Branch");
* @example
* // log "🌎 Master Branch"
* log.info("🌎 Master Branch");
* @example
* // log "✨ Feature Branch"
* log.info("✨ Feature Branch");
* @see {@link https://www.npmjs.com/package/fancy-log} for further information.
* @param {callback} cb - The callback that handles the response.
*/
function getGitEnv(cb) {
exec("git rev-parse --abbrev-ref HEAD", function (err, stdout, stderr) {
const git__branch = stdout.replace(/(\r\n|\n|\r)/gm, ""),
regex__feature = new RegExp("feature/feature-*"),
regex__release = new RegExp("release\/v(.*)"),
regex__hotfix = new RegExp("hotfix/hotfix-*");
if (git__branch === "develop") {
log.info("👨‍💻 Develop Branch");
} else if (git__branch === "master") {
log.info("🌎 Master Branch");
} else if (regex__feature.test(git__branch) === true) {
log.info("✨ Feature Branch");
} else if (regex__release.test(git__branch) === true) {
log.info("🏷️ Release Branch");
} else if (regex__hotfix.test(git__branch) === true) {
log.info("👨‍🏭 Hotfix Branch");
} else {
/**
* @todo check for other branch names
*/
log.warn(`❓ Unknown Branch: ${git__branch}`);
}
if (isEnv === 'production') {
log.info("🌎 Production Environment");
} else {
log.info("👨‍💻 Development Environment");
}
if (stderr) log.error(stderr);
cb(err);
});
cb();
}
/**
* Error Handler
* @summary Notify error messages with sound
* @see {@link https://www.npmjs.com/package/gulp-notify} for further information.
* @param {any} err - error message
*/
let onError = function (err) {
notify.onError({
title: "Gulp",
subtitle: "Failure!",
message: "Error: <%= error.message %>",
sound: "Beep",
})(err);
this.emit("end");
};
/**
* Static server
* @summary Static server browserSyncServe
* @see {@link https://browsersync.io/docs/options} for further information.
* @param {callback} cb - The callback that handles the response.
*/
function browserSyncServe(cb) {
if (browsersync.active || isServer === false) {
log.info("BrowserSync: Off");
} else {
log.info("BrowserSync: On");
browsersync.init({
open: false,
//port: variables.data.port,
server: {
baseDir: ["../Resources/Public/", "../Resources/Public/html/", "./"],
},
});
}
cb();
}
/**
* Static server reload
* @summary Reload Static server browserSyncReload
* @see {@link https://browsersync.io/docs/options} for further information.
* @param {callback} cb - The callback that handles the response.
*/
function browserSyncReload(cb) {
if (isServer !== false) {
browsersync.reload();
}
cb();
}
/**
* Minify HTML-Files
* @summary Minify HTML-Files
* @see {@link https://www.npmjs.com/package/gulp-htmlmin} for further information.
* @tutorial https://gulpjs.com/docs/en/api/dest/
* @returns {Stream} - A stream that can be used in the middle or at the end of a pipeline to create files on the file system.
*/
function minifyHtml() {
let SRC = [
`${variables.config.sourceHTML}**/*.html`, //select all files
`!${variables.config.sourceHTML}**/partials/*.html`, //exclude files
`!${variables.config.sourceHTML}_old/*.html`, //exclude files
'!node_modules/**/*.html'
];
return (
src(SRC)
.pipe(
nunjucks.compile({
siteName: variables.data.siteName,
url: variables.data.url,
})
)
.pipe(
gulpif(
isEnv === 'production',
htmlmin({
sortAttributes: true,
sortClassName: true,
collapseWhitespace: true,
removeComments: true,
minifyCSS: true,
minifyJS: true,
minifyURLs: true,
})
)
)
.pipe(dest(variables.config.targetHTML))
.pipe(gulpif(isServer, browsersync.stream()))
);
}
/**
* SCSS compilation
* @summary SCSS compilation
* @see {@link https://www.npmjs.com/package/gulp-htmlmin} for further information.
* @tutorial https://gulpjs.com/docs/en/api/dest/
* @returns {Stream} - A stream that can be used in the middle or at the end of a pipeline to create files on the file system.
*/
function buildScss() {
let SRC = variables.config.sourceSCSS + '**/*.scss',
DEST = variables.config.targetCSS,
DEST__MAP = "./map/";
let plugins = [
tailwindcss("tailwind.config.js"),
autoprefixer(),
//cssnano({zindex: false}),
atImport(),
];
let plugins__minify = [
cssnano({
safe: true,
minifyFontValues: {
removeQuotes: false,
},
discardComments: {
removeAllButFirst: true,
},
zindex: false,
}),
];
if (isEnv === 'production') {
// Src: https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Operators/Spread_syntax
plugins.push(...plugins__minify);
}
return (
src(SRC)
.pipe(
gulpif(isEnv !== 'production', sourcemaps.init())
)
.pipe(
scss({
outputStyle: "expanded",
// includePaths doesn't work with dart-sass (https://github.com/webpack-contrib/sass-loader/issues/684)
includePaths: [
`${__dirname}/node_modules/@fortawesome`,
`${__dirname}/node_modules/`,
'~/Developer/node_modules/',
'node_modules'
]
}).on("error", scss.logError)
)
/*.pipe(uncss({
//html: ['src/html/** /*.html'],
//html: ['src/html/*.html'],
html: ['index.html'],
ignore: [
/\w\.in/,
'hover', 'click', 'focus', '.glyphicon', '.glyphicon-play', '.glyphicon-refresh', '.glyphicon-chevron-right',
".fade",
".fade.in",
".collapse",
".collapse.in",
".collapsing",
".alert-danger",
/\.open/
],
timeout: 4000
}))*/
.pipe(postcss(plugins))
.pipe(
plumber({
errorHandler: onError,
})
)
.pipe(
rename({
//prefix: "style-",
dirname: "/",
//suffix: ".min",
extname: ".css",
})
)
// replace placeholder date in scss file
// .pipe(replace('metadata__update-date', new Date().toLocaleString('de-DE', dateOptions)))
// Source: https://stackoverflow.com/a/34081095/14331711
.pipe(gulpif(isEnv === 'production', replace(/@lastmodified:.*\n/g, `lastmodified: ${new Date().toLocaleString('de-DE', dateOptions)}\n`)))
.pipe(gulpif(isEnv !== 'production', replace(/@lastmodified:.*\n/g, '')))
//.pipe(gulpif(isEnv !== 'production', headerComment(`Generated on: <%= moment().format('DD.MM.YY') %>`)))
.pipe(gulpif(isEnv !== 'production', sourcemaps.write(DEST__MAP)))
.pipe(dest(DEST))
.pipe(gulpif(isServer, browsersync.stream()))
);
}
/**
* Minify images
* @summary Minify images
* @tutorial https://github.com/vincentorback/WebP-images-with-htaccess
* @returns {Stream} - A stream that can be used in the middle or at the end of a pipeline to create files on the file system.
*/
function minifyImg() {
let SRC = `${variables.config.sourceFileadmin}user_upload/**/*.jpg`,
DEST = `${variables.config.sourceFileadmin}minified/webp/`;
return (
src(SRC)
.pipe(changed(DEST))
//.pipe(changed(DEST, { extension: `.${extension}` }))
.pipe(webp())
.pipe(dest(DEST))
);
}
/**
* Compile TS to JS
* @summary Compile TypeScript to JavaScript
* @tutorial https://www.npmjs.com/package/gulp-typescript
* @returns {Stream} - A stream that can be used in the middle or at the end of a pipeline to create files on the file system.
*/
function compileTs() {
let SRC = [
`${variables.config.sourceTS}**/*.ts`, //select all files
`!${variables.config.sourceTS}**/*.min.ts`, //exclude files ends with 'min.ts'
`!${variables.config.sourceTS}**/_*.ts`, //exclude files starting with '_'
`!${variables.config.sourceTS}**/__*.ts`, //exclude files starting with '__'
`!${variables.config.sourceJS}**/* copy.ts`, //exclude files ending with copy
];
let DEST = variables.config.targetJS,
DEST__MAP = "./map/";
return (
src(SRC)
//.pipe (changed (DEST))
.pipe(
gulpif(isEnv !== 'production', sourcemaps.init())
)
.pipe(ts(tsconfig.compilerOptions))
.pipe(
rename({
dirname: "/",
extname: ".min.js",
})
)
.pipe(gulpif(isEnv !== 'production', headerComment(`Generated on: <%= moment().format('DD.MM.YY') %>`)))
.pipe(gulpif(isEnv !== 'production', sourcemaps.write(DEST__MAP)))
.pipe(dest(DEST))
.pipe(gulpif(isServer, browsersync.stream()))
);
}
/**
* Minify JS
* @summary Minify JavaScript-Files
* @returns {Stream} - A stream that can be used in the middle or at the end of a pipeline to create files on the file system.
*/
function minifyJs() {
let SRC = [
`${variables.config.sourceJS}**/*.js`, //select all files
`!${variables.config.sourceJS}**/*.min.js`, //exclude files ends with 'min.js'
`!${variables.config.sourceJS}**/_*.js`, //exclude files starting with '_'
`!${variables.config.sourceJS}**/__*.js`, //exclude files starting with '__'
`!${variables.config.sourceJS}**/* copy.js`, //exclude files ending with copy
];
let DEST = variables.config.targetJS,
DEST__MAP = "./map/";
return (
src(
filesExist(SRC, {
exceptionMessage: "Please run `bower/npm install` to install missing library",
})
)
.pipe(
jsImport({
hideConsole: true,
})
)
.pipe(
gulpif(isEnv !== 'production', sourcemaps.init())
)
.pipe(
plumber({
errorHandler: onError,
})
)
.pipe(
preprocess({
context: variables.data,
/*context: {
NODE_ENV: 'production',
DEBUG: true,
}*/
})
)
.pipe(
gulpif(
isEnv === 'production',
babel({
presets: ["@babel/env"],
})
)
)
.pipe(
gulpif(
isEnv === 'production',
uglify({
mangle: true,
compress: {
drop_console: true,
},
})
)
)
.on("error", (err) => {
log(err.toString());
})
//.pipe(concat('scripts.js'))
.pipe(
rename({
dirname: "/",
extname: ".min.js",
})
)
.pipe(gulpif(isEnv !== 'production', headerComment(`Generated on: <%= moment().format('DD.MM.YY') %>`)))
.pipe(gulpif(isEnv !== 'production', sourcemaps.write(DEST__MAP)))
.pipe(dest(DEST))
.pipe(gulpif(isServer, browsersync.stream()))
);
}
/**
* WatchFiles
* @summary Check if some files changes
*/
function watchFiles(cb) {
if (isServer === true) {
watch(`${variables.config.sourceSCSS}**/*.scss`, series(buildScss, browserSyncReload));
watch(`${variables.config.sourceHTML}**/*.html`, series(minifyHtml, browserSyncReload));
} else {
watch(`${variables.config.sourceSCSS}**/*.scss`, buildScss);
watch(`${variables.config.sourceHTML}**/*.html`, minifyHtml);
}
// Watch TS Files
watch(
[
`${variables.config.sourceTS}**/*.ts`, //select all files
`!${variables.config.sourceTS}**/*.min.ts`, //exclude files ends with 'min.ts'
`!${variables.config.sourceTS}**/_*.ts`, //exclude files starting with '_'
`!${variables.config.sourceTS}**/__*.ts`, //exclude files starting with '__'
],
compileTs
);
// Watch JS Files
watch(
[
`${variables.config.sourceJS}**/*.js`, //select all files
`!${variables.config.sourceJS}**/*.min.js`, //exclude files ends with 'min.js'
`!${variables.config.sourceJS}**/_*.js`, //exclude files starting with '_'
`!${variables.config.sourceJS}**/__*.js`, //exclude files starting with '__'
],
minifyJs
);
cb()
}
// define complex tasks
if (isServer === true) {
build = series(getGitEnv, clean, browserSyncServe, parallel(buildScss, minifyJs, compileTs, minifyHtml, minifyImg, watchFiles));
exports.watch = parallel(watchFiles, browserSyncServe);
} else {
build = series(getGitEnv, clean, parallel(buildScss, minifyJs, compileTs, minifyHtml, minifyImg, watchFiles));
exports.watch = watchFiles;
}
// export tasks
exports.build = build;
exports.default = build;
exports.clean = clean;
exports.html = minifyHtml;
exports.ts = compileTs;
exports.js = minifyJs;
exports.img = minifyImg;
exports.css = buildScss;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment