import gulp from 'gulp'; import twig from 'gulp-twig'; import * as dartSass from 'sass'; import gulpSass from 'gulp-sass'; import autoprefixer from 'gulp-autoprefixer'; import browserSync from 'browser-sync'; import babel from 'gulp-babel'; import concat from 'gulp-concat'; import uglify from 'gulp-uglify'; import imagemin from 'gulp-imagemin'; import cleanCss from 'gulp-clean-css'; import { deleteAsync } from 'del'; import sourcemaps from 'gulp-sourcemaps'; import fs from 'fs'; import plumber from 'gulp-plumber'; import svgstore from 'gulp-svgstore'; import cheerio from 'gulp-cheerio'; import through2 from 'through2'; import data from 'gulp-data'; import path from 'path'; import webp from 'gulp-webp'; import rev from 'gulp-rev'; import revReplace from 'gulp-rev-replace'; import cached from 'gulp-cached'; import remember from 'gulp-remember'; import { exec } from 'child_process'; import puppeteer from 'puppeteer'; import postcss from 'gulp-postcss'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const sass = gulpSass(dartSass); browserSync.create(); const cleanTask = () => { return deleteAsync(['dist']); }; const lintJsTask = (cb) => { exec('npx @biomejs/biome check src/js', (err, stdout, stderr) => { console.log(stdout); console.error(stderr); if (err) { console.error('Biome check failed!', err); return cb(err); } cb(); }); }; const formatJsTask = (cb) => { exec('npx @biomejs/biome format --write src/js', (err, stdout, stderr) => { console.log(stdout); console.error(stderr); if (err) { console.error('Biome format failed!', err); return cb(err); } cb(); }); }; export const svgSpriteTask = (cb) => { let svgContent = ''; return gulp.src('src/icons/**/*.svg') .pipe(cached('svgIcons')) // Cache SVG icons .pipe(plumber()) .pipe(cheerio({ run: function ($) { $('[fill]').removeAttr('fill'); $('[stroke]').removeAttr('stroke'); $('[style]').removeAttr('style'); }, parserOptions: { xmlMode: true } })) .pipe(svgstore({ inlineSvg: true })) .pipe(remember('svgIcons')) // Remember all SVG icons .pipe(through2.obj(function (file, enc, cb2) { svgContent = file.contents.toString(); cb2(null, file); })) .pipe(gulp.dest('dist')) // Still write to dist for consistency .on('end', () => { // Ensure the file is written before calling the callback fs.promises.writeFile('./dist/svg-sprite.svg', svgContent) .then(cb) .catch(cb); }); }; const twigTask = () => { const globalData = JSON.parse(fs.readFileSync('./src/data/global.json')); const svgSprite = fs.readFileSync('./dist/svg-sprite.svg', 'utf8'); return gulp.src('src/templates/*.twig') // Process only top-level twig files .pipe(cached('twigTemplates')) // Cache twig templates .pipe(plumber()) .pipe(data(function(file) { const fileName = path.basename(file.path, '.twig'); const dataFilePath = `./src/data/${fileName}.json`; let pageData = {}; if (fs.existsSync(dataFilePath)) { pageData = JSON.parse(fs.readFileSync(dataFilePath)); } return { ...globalData, ...pageData, svgSprite: svgSprite }; })) .pipe(twig()) .pipe(remember('twigTemplates')) // Remember all twig templates .pipe(gulp.dest('dist')) .pipe(browserSync.stream()); }; const scssTask = () => { return gulp.src('src/scss/main.scss') .pipe(cached('scss')) // Cache SCSS files .pipe(plumber()) .pipe(sourcemaps.init()) .pipe(sass().on('error', sass.logError)) .pipe(autoprefixer()) .pipe(sourcemaps.write('.')) .pipe(remember('scss')) // Remember all SCSS files .pipe(gulp.dest('dist')) .pipe(browserSync.stream()); }; const scssProdTask = () => { return gulp.src('src/scss/main.scss') .pipe(cached('scssProd')) // Cache SCSS files for prod .pipe(plumber()) .pipe(sass().on('error', sass.logError)) .pipe(autoprefixer()) .pipe(cleanCss()) .pipe(remember('scssProd')) // Remember all SCSS files for prod .pipe(gulp.dest('dist')); }; const jsDevTask = () => { return gulp.src('src/js/**/*.js') .pipe(cached('jsDev')) // Cache JS files for dev .pipe(plumber()) .pipe(babel({ presets: ['@babel/env'] })) .pipe(concat('main.js')) .pipe(sourcemaps.write('.')) .pipe(remember('jsDev')) // Remember all JS files for dev .pipe(gulp.dest('dist/js')) .pipe(browserSync.stream()); }; const jsMinTask = () => { return gulp.src(['src/js/**/*.js', '!src/js/**/*.test.js']) .pipe(cached('jsProd')) // Cache JS files for prod .pipe(plumber()) .pipe(sourcemaps.init()) .pipe(babel({ presets: ['@babel/env'] })) .pipe(concat('main.min.js')) .pipe(uglify()) .pipe(sourcemaps.write('.')) .pipe(remember('jsProd')) // Remember all JS files for prod .pipe(gulp.dest('dist/js')) .pipe(browserSync.stream()); }; const jsTask = () => { return gulp.src(['src/js/**/*.js', '!src/js/**/*.test.js']) .pipe(cached('jsProd')) // Cache JS files for prod .pipe(plumber()) .pipe(sourcemaps.init()) .pipe(babel({ presets: ['@babel/env'] })) .pipe(concat('main.js')) .pipe(sourcemaps.write('.')) .pipe(remember('jsProd')) // Remember all JS files for prod .pipe(gulp.dest('dist/js')) .pipe(browserSync.stream()); }; const imagesTask = () => { return gulp.src('src/images/*') .pipe(cached('images')) // Cache images .pipe(plumber()) .pipe(imagemin()) .pipe(remember('images')) // Remember all images .pipe(gulp.dest('dist/images')) .pipe(browserSync.stream()); }; const imagesProdTask = () => { return gulp.src('src/images/*') .pipe(cached('imagesProd')) // Cache images for prod .pipe(plumber()) .pipe(imagemin()) .pipe(gulp.dest('dist/images')) .pipe(webp()) .pipe(gulp.dest('dist/images')); }; const copyAssetsTask = () => { return gulp.src('src/assets/**/*') .pipe(cached('assets')) // Cache assets .pipe(plumber()) .pipe(remember('assets')) // Remember all assets .pipe(gulp.dest('dist/assets')) .pipe(browserSync.stream()); }; const copyFaviconTask = () => { return gulp.src('src/favicon.ico') .pipe(cached('favicon')) // Cache favicon .pipe(plumber()) .pipe(remember('favicon')) // Remember favicon .pipe(gulp.dest('dist')) .pipe(browserSync.stream()); }; const copyFontsTask = () => { return gulp.src('src/fonts/**/*') .pipe(cached('fonts')) // Cache fonts .pipe(plumber()) .pipe(remember('fonts')) // Remember all fonts .pipe(gulp.dest('dist/fonts')) .pipe(browserSync.stream()); }; const revTask = () => { return gulp.src(['dist/**/*.css', 'dist/**/*.js'], { base: 'dist' }) .pipe(gulp.dest('dist')) // write original assets to dist .pipe(rev()) .pipe(gulp.dest('dist')) // write rev'd assets to dist .pipe(rev.manifest()) .pipe(gulp.dest('dist')); // write manifest to dist }; const revReplaceTask = () => { const manifest = gulp.src('dist/rev-manifest.json'); return gulp.src('dist/**/*.html') .pipe(revReplace({ manifest: manifest })) .pipe(gulp.dest('dist')); }; const screenshotsTask = async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); const resolutions = [ { width: 1920, height: 1080, name: 'desktop' }, { width: 1366, height: 768, name: 'laptop' }, { width: 768, height: 1024, name: 'tablet' }, { width: 375, height: 667, name: 'mobile' }, ]; // Ensure the screenshots directory exists if (!fs.existsSync('dist/screenshots')) { fs.mkdirSync('dist/screenshots'); } for (const res of resolutions) { await page.setViewport({ width: res.width, height: res.height }); await page.goto('file://' + path.resolve(__dirname, 'dist/index.html')); await page.screenshot({ path: `dist/screenshots/screenshot-${res.name}-${res.width}x${res.height}.png` }); console.log(`Screenshot captured for ${res.name} (${res.width}x${res.height})`); } await browser.close(); }; const watchTask = () => { browserSync.init({ server: { baseDir: './dist' }, https: true // Enable HTTPS }); gulp.watch('src/templates/**/*.twig', twigTask); gulp.watch('src/scss/**/*.scss', scssTask); gulp.watch('src/js/**/*.js', gulp.series(lintJsTask, jsDevTask)); gulp.watch('src/images/*', imagesTask); gulp.watch('src/data/**/*.json', twigTask); gulp.watch('src/assets/**/*', copyAssetsTask); gulp.watch('src/favicon.ico', copyFaviconTask); gulp.watch('src/icons/**/*.svg', gulp.series(svgSpriteTask, twigTask)); // Watch for changes in SVG icons gulp.watch('src/fonts/**/*', copyFontsTask); // Watch for changes in fonts }; const devBuild = gulp.series(cleanTask, svgSpriteTask, gulp.parallel(scssTask, jsDevTask, imagesTask, copyAssetsTask, copyFaviconTask, copyFontsTask), twigTask); export const build = gulp.series(cleanTask, lintJsTask, svgSpriteTask, twigTask, gulp.parallel(scssTask, jsDevTask, imagesTask, copyAssetsTask, copyFaviconTask, copyFontsTask)); export const buildProd = gulp.series(cleanTask, lintJsTask, svgSpriteTask, twigTask, gulp.parallel(scssProdTask, jsTask, jsMinTask, imagesProdTask, copyAssetsTask, copyFaviconTask, copyFontsTask), revTask, revReplaceTask, screenshotsTask); export const screenshots = screenshotsTask; export default gulp.series(devBuild, watchTask);