|
const { src, dest, watch, series, parallel } = require('gulp'); |
|
const sass = require('gulp-sass')(require('sass')); |
|
const autoprefixer = require('autoprefixer'); |
|
const postcss = require('gulp-postcss'); |
|
const sourcemaps = require('gulp-sourcemaps'); |
|
const cssnano = require('cssnano'); |
|
const concat = require('gulp-concat'); |
|
const terser = require('gulp-terser-js'); |
|
const rename = require('gulp-rename'); |
|
const cache = require('gulp-cache'); |
|
const notify = require('gulp-notify'); |
|
const sharp = require('sharp'); |
|
const path = require('path'); |
|
const plumber = require('gulp-plumber'); |
|
const through2 = require('through2'); |
|
|
|
// Rutas |
|
const paths = { |
|
scss: 'src/scss/**/*.scss', |
|
js: 'src/js/**/*.js', |
|
imagenes: 'src/img/**/*.{jpg,jpeg,png,webp}' |
|
}; |
|
|
|
// Compilar Sass |
|
function css() { |
|
return src(paths.scss) |
|
.pipe(sourcemaps.init()) |
|
.pipe( |
|
sass({ |
|
quietDeps: true, |
|
silenceDeprecations: ['import'] |
|
}).on('error', sass.logError) |
|
) |
|
.pipe(postcss([autoprefixer(), cssnano()])) |
|
.pipe(sourcemaps.write('.')) |
|
.pipe(dest('build/css')); |
|
} |
|
|
|
// Procesar JavaScript |
|
function javascript() { |
|
return src(paths.js) |
|
.pipe(sourcemaps.init()) |
|
.pipe(concat('bundle.js')) |
|
.pipe(terser()) |
|
.pipe(rename({ basename: 'bundle.min' })) |
|
.pipe(dest('build/js')) |
|
.pipe(rename({ extname: '.map' })) |
|
.pipe(dest('build/js')); |
|
} |
|
|
|
// Transform para usar Sharp dentro de Gulp |
|
function sharpTransform() { |
|
return through2.obj(async (file, _, cb) => { |
|
if (file.isNull()) return cb(null, file); |
|
if (file.isStream()) return cb(new Error('Streams no soportados')); |
|
|
|
const ext = path.extname(file.path).toLowerCase(); |
|
|
|
try { |
|
// Si ya es WebP, solo reoptimiza |
|
if (ext === '.webp') { |
|
const buffer = await sharp(file.contents) |
|
.webp({ quality: 80, effort: 4 }) |
|
.toBuffer(); |
|
file.contents = buffer; |
|
return cb(null, file); |
|
} |
|
|
|
// Para JPG/PNG/JPEG optimiza sin cambiar formato |
|
let pipeline = sharp(file.contents, { failOnError: false }); |
|
|
|
if (ext === '.jpg' || ext === '.jpeg') { |
|
pipeline = pipeline.jpeg({ |
|
quality: 80, |
|
progressive: true, |
|
mozjpeg: true |
|
}); |
|
} else if (ext === '.png') { |
|
pipeline = pipeline.png({ |
|
quality: 80, |
|
compressionLevel: 9, |
|
effort: 7, |
|
palette: true |
|
}); |
|
} |
|
|
|
const buffer = await pipeline.toBuffer(); |
|
file.contents = buffer; |
|
cb(null, file); |
|
} catch (err) { |
|
cb(err); |
|
} |
|
}); |
|
} |
|
|
|
// Optimizar imágenes originales con Sharp |
|
function imagenes() { |
|
return src('src/img/**/*.{jpg,jpeg,png,webp}', { allowEmpty: true }) |
|
.pipe(plumber()) |
|
.pipe(cache(sharpTransform())) |
|
.pipe(dest('build/img')) |
|
.pipe(notify({ message: 'Imagen optimizada: <%= file.relative %>' })); |
|
} |
|
|
|
// Función auxiliar: convertir a WebP |
|
async function convertirWebp(ruta) { |
|
const outputPath = ruta |
|
.replace('src/img', 'build/img') |
|
.replace(/\.(jpe?g|png)$/i, '.webp'); |
|
|
|
await sharp(ruta) |
|
.webp({ quality: 50, effort: 4 }) |
|
.toFile(outputPath); |
|
} |
|
|
|
// Función auxiliar: convertir a AVIF |
|
async function convertirAvif(ruta) { |
|
const outputPath = ruta |
|
.replace('src/img', 'build/img') |
|
.replace(/\.(jpe?g|png)$/i, '.avif'); |
|
|
|
await sharp(ruta) |
|
.avif({ quality: 50, effort: 4 }) |
|
.toFile(outputPath); |
|
} |
|
|
|
// Tarea: procesar imágenes y generar formatos modernos |
|
async function procesarImagenes(done) { |
|
await imagenes(); |
|
done(); |
|
} |
|
|
|
// Watch: vigila cambios y ejecuta tareas |
|
function watchArchivos() { |
|
watch(paths.scss, css); |
|
watch(paths.js, javascript); |
|
watch(paths.imagenes, procesarImagenes); |
|
|
|
// Vigila cambios en JPG/PNG para generar WebP y AVIF |
|
watch('src/img/**/*.{jpg,jpeg,png}', async (event) => { |
|
const ruta = event.path || event; |
|
await convertirWebp(ruta); |
|
await convertirAvif(ruta); |
|
console.log(`Formatos generados para: ${path.basename(ruta)}`); |
|
}); |
|
} |
|
|
|
// Exportar tareas |
|
exports.css = css; |
|
exports.javascript = javascript; |
|
exports.imagenes = imagenes; |
|
exports.procesarImagenes = procesarImagenes; |
|
exports.watchArchivos = watchArchivos; |
|
|
|
// Tarea por defecto |
|
exports.default = parallel(css, javascript, procesarImagenes, watchArchivos); |