Files
Fuchs_Intranet/Fuchs/gulpfile.js
T

378 lines
12 KiB
JavaScript

"use strict";
const gulp = require("gulp");
const sass = require("gulp-sass")(require("sass"));
const less = require("gulp-less");
const concat = require("gulp-concat");
const cleanCSS = require("gulp-clean-css");
const htmlmin = require("gulp-htmlmin");
const terser = require("gulp-terser");
const gulpif = require("gulp-if");
const merge2 = require("merge2");
const copyconfig = require("./copyconfig.json");
const path = require("path");
const fs = require("fs");
const { Transform, Writable } = require("stream");
// Node pipeline (promise-based)
let pipeline;
try {
({ pipeline } = require("stream/promises"));
} catch {
pipeline = require("stream").promises.pipeline;
}
// --------------------------
// Config loaders
// --------------------------
function loadBundleConfig() {
delete require.cache[require.resolve("./bdlconfig.json")];
return require("./bdlconfig.json");
}
const terserOptions = {
parse: { ecma: 2020 },
compress: { ecma: 2020 },
mangle: true,
output: { ecma: 2020 },
};
const regex = {
css: /\.css$/i,
html: /\.(html|htm)$/i,
js: /\.js$/i,
};
// --------------------------
// Helpers
// --------------------------
function safeUnlink(filePath) {
try {
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
console.log("Deleted:", filePath);
}
} catch (err) {
console.warn("Could not delete file:", filePath, "-", err.message);
}
}
function handleTerserError(err) {
console.error("========================================");
console.error("TERSER ERROR:", err && err.message ? err.message : err);
if (err && (err.filename || err.fileName)) console.error("File:", err.filename || err.fileName);
if (err && (err.line != null || err.col != null)) console.error("Line:", err.line, "Col:", err.col);
console.error(String(err));
console.error("========================================");
this.emit("end");
}
/**
* Safe, non-hanging replacement for gulp-preservetime.
* Use AFTER gulp.dest().
*/
function preservetimeSafe() {
return new Transform({
objectMode: true,
transform(file, _enc, cb) {
try {
if (!file || !file.path || !file.stat) return cb(null, file);
if (typeof file.isDirectory === "function" && file.isDirectory()) return cb(null, file);
const atime =
file.stat.atime instanceof Date ? file.stat.atime : new Date(file.stat.atimeMs ?? Date.now());
const mtime =
file.stat.mtime instanceof Date ? file.stat.mtime : new Date(file.stat.mtimeMs ?? Date.now());
fs.utimes(file.path, atime, mtime, () => cb(null, file));
} catch (_e) {
cb(null, file);
}
},
});
}
function logSink(prefix) {
return new Writable({
objectMode: true,
write(file, _enc, cb) {
try {
if (file && file.path) {
const rel = path.relative(process.cwd(), file.path);
console.log(prefix + rel);
}
} catch (_e) { }
cb();
},
});
}
function getBundles(regexPattern) {
const bundleconfig = loadBundleConfig();
return bundleconfig.filter((bundle) => regexPattern.test(bundle.outputFileName));
}
function normalizeRel(p) {
return String(p || "").replace(/\//g, "|").replace(/\\/g, "|");
}
// Normalize slashes for Windows friendliness (outputFileName uses forward slashes)
function normalizePath(p) {
return String(p || "").replace(/\//g, path.sep);
}
// Make watch list more complete for SCSS: include likely include roots.
// (Safe: only affects watch mode, not build output.)
function inferScssWatchGlobs(bundle) {
const inputs = (bundle.inputFiles || []).slice();
const scss = inputs.filter((f) => /\.s[ac]ss$/i.test(f));
if (scss.length === 0) return [];
const dirSet = new Set();
for (const f of scss) {
const d = path.dirname(normalizePath(f));
if (d && d !== ".") dirSet.add(d);
}
// Watch all scss/sass partials and imports under those folders.
// This catches changes to partials that aren't explicitly listed in bdlconfig.json.
return Array.from(dirSet).map((d) => path.join(d, "**", "*.s[ac]ss"));
}
// --------------------------
// COPY (separate task)
// --------------------------
gulp.task("copy", async () => {
const jobs = [];
for (const cpy of copyconfig) {
jobs.push(
pipeline(gulp.src(cpy.src, { allowEmpty: true }), gulp.dest(cpy.dest), preservetimeSafe())
);
}
await Promise.all(jobs);
});
// --------------------------
// JS
// --------------------------
gulp.task("min:js", async () => {
for (const bundle of getBundles(regex.js)) {
const minify = typeof (bundle.minify || {}).enabled === "boolean" ? bundle.minify.enabled : true;
const inputs = (bundle.inputFiles || []).slice();
const toMinifyFiles = (bundle.inputFiles_tominify || []).slice();
const outNorm = normalizePath(bundle.outputFileName);
const outputDir = path.dirname(outNorm);
const outputFile = path.basename(outNorm);
console.log("Bundle:", bundle.outputFileName, "| Minify:", minify, "| Inputs:", inputs.length + toMinifyFiles.length);
if (minify === true) {
const allInputs = inputs.concat(toMinifyFiles);
const nonMinFile = outputFile.includes(".min.") ? outputFile.replace(".min.", ".") : outputFile;
const minFile = outputFile.includes(".min.") ? outputFile : outputFile.replace(/\.js$/i, ".min.js");
safeUnlink(path.join(outputDir, nonMinFile));
safeUnlink(path.join(outputDir, minFile));
await pipeline(
gulp.src(allInputs, { base: ".", allowEmpty: false }),
concat(nonMinFile),
gulp.dest(outputDir),
logSink("Non-Minified: ")
);
await pipeline(
gulp.src(allInputs, { base: ".", allowEmpty: false }),
concat(minFile),
terser(terserOptions).on("error", handleTerserError),
gulp.dest(outputDir),
logSink("Minified: ")
);
} else {
safeUnlink(path.join(outputDir, outputFile));
// Build source streams separately to avoid anymatch index confusion
// when negated globs and singular paths are mixed in one gulp.src call
const srcStreams = [];
if (inputs.length > 0) {
srcStreams.push(gulp.src(inputs, { base: ".", allowEmpty: false }));
}
if (toMinifyFiles.length > 0) {
srcStreams.push(
gulp.src(toMinifyFiles, { base: ".", allowEmpty: false })
.pipe(terser(terserOptions).on("error", handleTerserError))
);
}
const merged = srcStreams.length === 1 ? srcStreams[0] : merge2(srcStreams);
await pipeline(
merged,
concat(outputFile),
gulp.dest(outputDir),
logSink("Bundled: ")
);
}
}
});
// --------------------------
// CSS source builder
// IMPORTANT: the intermediate concat names MUST NOT start with "_" (gulp-sass ignores those)
// --------------------------
function getCssSourceAndSteps(bundle) {
const inputs = (bundle.inputFiles || []).slice();
const scssFiles = inputs.filter((f) => /\.s[ac]ss$/i.test(f));
const lessFiles = inputs.filter((f) => /\.less$/i.test(f));
const cssFiles = inputs.filter((f) => /\.css$/i.test(f) && !/\.s[ac]ss$/i.test(f));
const srcOpts = { base: ".", allowEmpty: false };
// Make Sass FAIL the build (no sass.logError swallowing)
if (scssFiles.length && !lessFiles.length && !cssFiles.length) {
return {
src: gulp.src(scssFiles, srcOpts),
steps: [concat("bundle.scss"), sass()],
};
}
if (lessFiles.length && !scssFiles.length && !cssFiles.length) {
return {
src: gulp.src(lessFiles, srcOpts),
steps: [concat("bundle.less"), less()],
};
}
if (cssFiles.length && !scssFiles.length && !lessFiles.length) {
return {
src: gulp.src(cssFiles, srcOpts),
steps: [],
};
}
throw new Error(`Mixed CSS types in bundle ${bundle.outputFileName}. Split into separate bundles.`);
}
// --------------------------
// CSS (min:css + min:scss alias)
// --------------------------
async function minCssImpl() {
for (const bundle of getBundles(regex.css)) {
const minify = typeof (bundle.minify || {}).enabled === "boolean" ? bundle.minify.enabled : true;
const outNorm = normalizePath(bundle.outputFileName);
const outputDir = path.dirname(outNorm);
const outputBase = path.basename(outNorm);
console.log(
"Bundle:",
bundle.outputFileName,
"| Minify:",
minify,
"| Inputs:",
(bundle.inputFiles || []).length
);
if (minify === true) {
const nonMinFile = outputBase.includes(".min.") ? outputBase.replace(".min.", ".") : outputBase;
const minFile = outputBase.includes(".min.")
? outputBase
: outputBase.replace(/\.css$/i, ".min.css");
safeUnlink(path.join(outputDir, nonMinFile));
safeUnlink(path.join(outputDir, minFile));
// Non-minified
{
const spec = getCssSourceAndSteps(bundle);
await pipeline(
spec.src,
...spec.steps,
concat(nonMinFile),
gulp.dest(outputDir),
logSink("Non-Minified: ")
);
}
// Minified (fresh stream)
{
const spec = getCssSourceAndSteps(bundle);
await pipeline(
spec.src,
...spec.steps,
concat(minFile),
// cleanCSS level 2 + single-line stats
cleanCSS({ level: 2, debug: true }, (details) => {
const name = details.name || path.basename(minFile);
console.log(`${name}: ${details.stats.originalSize} -> ${details.stats.minifiedSize}`);
}),
gulp.dest(outputDir),
logSink("Minified: ")
);
}
} else {
safeUnlink(path.join(outputDir, outputBase));
const spec = getCssSourceAndSteps(bundle);
await pipeline(
spec.src,
...spec.steps,
concat(outputBase),
gulp.dest(outputDir),
logSink("Bundled: ")
);
}
}
}
gulp.task("min:css", minCssImpl);
gulp.task("min:scss", minCssImpl);
// --------------------------
// HTML
// --------------------------
gulp.task("min:html", async () => {
for (const bundle of getBundles(regex.html)) {
const minify = typeof (bundle.minify || {}).enabled === "boolean" ? bundle.minify.enabled : true;
safeUnlink(bundle.outputFileName);
await pipeline(
gulp.src(bundle.inputFiles || [], { base: ".", allowEmpty: false }),
concat(bundle.outputFileName),
htmlmin({ collapseWhitespace: true, minifyCSS: minify, minifyJS: minify }),
gulp.dest("."),
logSink("Bundled: ")
);
}
});
// --------------------------
// Aggregate / Watch
// --------------------------
gulp.task("min", gulp.parallel("min:js", "min:css", "min:html"));
gulp.task("all", gulp.series("copy", "min"));
gulp.task("watch", function () {
getBundles(regex.js).forEach((bundle) => {
const watchFiles = []
.concat(bundle.inputFiles || [])
.concat(bundle.inputFiles_tominify || []);
gulp.watch(watchFiles, gulp.series("min:js"));
});
getBundles(regex.css).forEach((bundle) => {
const watchFiles = [].concat(bundle.inputFiles || []);
const extraScssGlobs = inferScssWatchGlobs(bundle);
gulp.watch(watchFiles.concat(extraScssGlobs), gulp.series("min:css"));
});
getBundles(regex.html).forEach((bundle) => {
gulp.watch(bundle.inputFiles || [], gulp.series("min:html"));
});
});