templates/webpunk-templates/gulpfile.mjs
2025-06-15 00:02:46 +03:00

856 lines
24 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

import autoprefixer from 'autoprefixer'
import concat from 'gulp-concat'
import csso from 'gulp-csso'
import { deleteAsync } from 'del'
import gulp from 'gulp'
import gulpWebp from 'gulp-webp'
import imagemin, { mozjpeg, optipng, svgo } from 'gulp-imagemin'
import imageminAvif from 'imagemin-avif'
import order from 'gulp-order'
import plumber from 'gulp-plumber'
import postcss from 'gulp-postcss'
import pug from 'gulp-pug'
import rename from 'gulp-rename'
import uglify from 'gulp-uglify-es'
import * as dartSass from 'sass'
import gulpSass from 'gulp-sass'
import sourcemap from 'gulp-sourcemaps'
import svgstore from 'gulp-svgstore'
import sync from 'browser-sync'
import { nanoid } from 'nanoid'
import postcssUrl from 'postcss-url'
import inject from 'gulp-inject'
import newer from 'gulp-newer'
import twig from 'gulp-twig'
import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'
import { execSync } from 'child_process'
import dotenv from 'dotenv'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
// Загружаем переменные окружения из .env.local
const envPath = path.join(__dirname, '.env.local')
if (fs.existsSync(envPath)) {
dotenv.config({ path: envPath })
console.log('📝 Загружены настройки из .env.local')
} else {
console.log('⚠️ Файл .env.local не найден. Запустите: gulp configInit')
}
const baseUrl = '.'
const pathCss = '..'
const searchId = Date.now()
// Конфигурация для разных шаблонизаторов
const settings = {
templateEngine: process.env.TEMPLATE_ENGINE || 'pug', // 'pug' или 'twig'
dataPath: 'src/data', // путь к JSON файлам с данными
https: {
enabled: process.env.HTTPS === 'true',
keyPath: 'ssl/server.key',
certPath: 'ssl/server.crt'
},
server: {
port: process.env.PORT ? parseInt(process.env.PORT, 10) : 3000,
host: process.env.HOST || 'localhost',
open: process.env.OPEN_BROWSER === 'true',
notify: process.env.BROWSER_SYNC_NOTIFY === 'true'
}
}
// Выводим текущие настройки при запуске
console.log('⚙️ Текущие настройки:')
console.log(` Шаблонизатор: ${settings.templateEngine}`)
console.log(` HTTPS: ${settings.https.enabled ? 'включен' : 'выключен'}`)
console.log(` Порт: ${settings.server.port}`)
console.log(` Хост: ${settings.server.host}`)
console.log(` Автооткрытие браузера: ${settings.server.open ? 'да' : 'нет'}`)
console.log(` Уведомления: ${settings.server.notify ? 'да' : 'нет'}`)
// Функция для загрузки данных из JSON файлов
const loadData = () => {
const dataPath = path.join(__dirname, settings.dataPath)
let data = {}
if (fs.existsSync(dataPath)) {
const files = fs.readdirSync(dataPath).filter(file => file.endsWith('.json'))
files.forEach(file => {
const fileName = path.basename(file, '.json')
const filePath = path.join(dataPath, file)
try {
const fileContent = fs.readFileSync(filePath, 'utf8')
data[fileName] = JSON.parse(fileContent)
} catch (error) {
console.error(`Ошибка загрузки данных из ${file}:`, error.message)
}
})
} else {
console.log(`📁 Папка с данными не найдена: ${dataPath}`)
console.log('💡 Запустите: gulp data для создания примера данных')
}
return data
}
const getImageSrcset = (path, format, params = '') => {
return [1, 2]
.map((dpr) => `${baseUrl}${path}@${dpr}x.${format}${params} ${dpr}x`)
.join(', ')
}
const getImageMime = (format) => {
switch (format) {
case 'avif':
return 'image/avif'
case 'webp':
return 'image/webp'
case 'jpeg':
case 'jpg':
return 'image/jpeg'
case 'png':
return 'image/png'
default:
return null
}
}
const sass = gulpSass(dartSass)
const clean = async () => {
return await deleteAsync(['dist'])
}
const copy = () => {
return gulp
.src(
[
'src/fonts/**/*.{woff,woff2}',
'src/img/**/*.{webm,webp,avif,jpg,jpeg,png,svg}',
'src/favicon/**/*',
'src/favicon.ico',
'src/settings.js',
'src/urls.json',
'src/robots.txt'
],
{ base: 'src', encoding: false }
)
.pipe(gulp.dest('dist'))
}
const css = () => {
return gulp
.src('src/scss/index.scss')
.pipe(plumber())
.pipe(sourcemap.init())
.pipe(sass.sync().on('error', sass.logError))
.pipe(
postcss([
postcssUrl({
url: (asset) => {
if (asset.url.startsWith('/')) {
return `${pathCss}${asset.url}?v=${searchId}`
}
return asset.url
}
}),
autoprefixer({ remove: false })
])
)
.pipe(rename('style.css'))
.pipe(sourcemap.write('.'))
.pipe(gulp.dest('dist/css'))
.pipe(sync.stream())
.pipe(csso())
.pipe(rename('style.min.css'))
.pipe(gulp.dest('dist/css'))
}
const images = () => {
return gulp
.src('src/img/**/*.{png,jpg,jpeg,svg}', { base: 'src', encoding: false })
.pipe(newer('dist'))
.pipe(
imagemin(
[
optipng({ optimizationLevel: 5 }),
mozjpeg({ quality: 85, progressive: true }),
svgo({
plugins: [
{ name: 'removeViewBox', active: false },
{ name: 'removeXMLNS', active: false },
{ name: 'removeUnknownsAndDefaults', active: false }
]
})
],
{ silent: true }
)
)
.pipe(gulp.dest('dist'))
}
const webp = () => {
return gulp
.src('src/img/**/*.{png,jpg,jpeg}', { base: 'src', encoding: false })
.pipe(newer({ dest: 'dist', ext: '.webp' }))
.pipe(gulpWebp({ quality: 85 }))
.pipe(gulp.dest('dist'))
}
const avif = () => {
return gulp
.src('src/img/**/*.{png,jpg,jpeg}', { base: 'src', encoding: false })
.pipe(newer({ dest: 'dist', ext: '.avif' }))
.pipe(imagemin([imageminAvif({ quality: 60 })], { silent: true }))
.pipe(rename((path) => (path.extname = '.avif')))
.pipe(gulp.dest('dist'))
}
const processImages = gulp.parallel(images, webp, avif)
const sprite = () => {
return gulp
.src('src/icons/**/*.svg')
.pipe(svgstore({ inlineSvg: true }))
.pipe(rename('sprite.svg'))
.pipe(gulp.dest('dist/img'))
}
const injectSprite = () => {
const spritePath = 'dist/img/sprite.svg'
// Проверяем, существует ли файл спрайта
if (!fs.existsSync(spritePath)) {
console.log('⚠️ Спрайт не найден, пропускаем инъекцию')
return Promise.resolve()
}
return gulp
.src('dist/*.html')
.pipe(
inject(gulp.src(spritePath), {
transform: (_, file) => {
return file.contents.toString()
}
})
)
.pipe(gulp.dest('dist'))
}
// Инъекция спрайта только в конкретный файл
const injectSpriteToFile = (htmlFile) => {
const spritePath = 'dist/img/sprite.svg'
if (!fs.existsSync(spritePath) || !fs.existsSync(htmlFile)) {
return Promise.resolve()
}
return gulp
.src(htmlFile)
.pipe(
inject(gulp.src(spritePath), {
transform: (_, file) => {
return file.contents.toString()
}
})
)
.pipe(gulp.dest('dist'))
}
const jsCommon = () => {
return gulp
.src('src/js/common/*.js')
.pipe(
plumber({
errorHandler(err) {
console.error('JS Common Error:', err.toString())
this.emit('end')
}
})
)
.pipe(order(['_utils.js', '*.js']))
.pipe(sourcemap.init())
.pipe(concat('script.js'))
.pipe(sourcemap.write('.'))
.pipe(gulp.dest('dist/js'))
}
const jsCommonMin = () => {
return gulp
.src('src/js/common/*.js')
.pipe(
plumber({
errorHandler(err) {
console.error('JS Common Min Error:', err.toString())
this.emit('end')
}
})
)
.pipe(order(['_utils.js', '*.js']))
.pipe(concat('script.min.js'))
.pipe(uglify.default())
.pipe(gulp.dest('dist/js'))
}
const jsVendor = () => {
return gulp
.src('src/js/vendor/*.js')
.pipe(
plumber({
errorHandler(err) {
console.error('JS Vendor Error:', err.toString())
this.emit('end')
}
})
)
.pipe(concat('vendor.js'))
.pipe(gulp.dest('dist/js'))
}
const jsVendorMin = () => {
return gulp
.src('src/js/vendor/*.js')
.pipe(
plumber({
errorHandler(err) {
console.error('JS Vendor Min Error:', err.toString())
this.emit('end')
}
})
)
.pipe(concat('vendor.min.js'))
.pipe(uglify.default())
.pipe(gulp.dest('dist/js'))
}
const js = gulp.parallel(jsCommon, jsCommonMin, jsVendor, jsVendorMin)
// Простая обработка Pug шаблонов
const htmlPug = () => {
const data = loadData()
return gulp
.src('src/pug/pages/**/*.pug')
.pipe(plumber({
errorHandler(err) {
console.error('Pug Error:', err.toString())
this.emit('end')
}
}))
.pipe(
pug({
pretty: true,
basedir: 'src/pug',
locals: {
baseUrl,
searchId,
getSrcset: getImageSrcset,
getMime: getImageMime,
...data
}
})
)
.pipe(gulp.dest('dist'))
}
// Простая обработка Twig шаблонов
const htmlTwig = () => {
const data = loadData()
return gulp
.src('src/twig/pages/**/*.twig')
.pipe(plumber({
errorHandler(err) {
console.error('Twig Error:', err.toString())
this.emit('end')
}
}))
.pipe(
twig({
base: 'src/twig',
data: {
baseUrl,
searchId,
getSrcset: getImageSrcset,
getMime: getImageMime,
...data
},
functions: [
{
name: 'getSrcset',
func: getImageSrcset
},
{
name: 'getMime',
func: getImageMime
}
]
})
)
.pipe(gulp.dest('dist'))
}
// Pug с инкрементальной сборкой
const htmlPugIncremental = () => {
const data = loadData()
return gulp
.src('src/pug/pages/**/*.pug')
.pipe(plumber({
errorHandler(err) {
console.error('Pug Error:', err.toString())
this.emit('end')
}
}))
.pipe(newer({
dest: 'dist',
ext: '.html'
}))
.pipe(
pug({
pretty: true,
basedir: 'src/pug',
locals: {
baseUrl,
searchId,
getSrcset: getImageSrcset,
getMime: getImageMime,
...data
}
})
)
.pipe(gulp.dest('dist'))
.on('end', () => {
console.log('📄 Обработаны только измененные Pug файлы')
})
}
// Twig с инкрементальной сборкой
const htmlTwigIncremental = () => {
const data = loadData()
return gulp
.src('src/twig/pages/**/*.twig')
.pipe(plumber({
errorHandler(err) {
console.error('Twig Error:', err.toString())
this.emit('end')
}
}))
.pipe(newer({
dest: 'dist',
ext: '.html'
}))
.pipe(
twig({
base: 'src/twig',
data: {
baseUrl,
searchId,
getSrcset: getImageSrcset,
getMime: getImageMime,
...data
},
functions: [
{
name: 'getSrcset',
func: getImageSrcset
},
{
name: 'getMime',
func: getImageMime
}
]
})
)
.pipe(gulp.dest('dist'))
.on('end', () => {
console.log('📄 Обработаны только измененные Twig файлы')
})
}
// Универсальная функция для обработки HTML
const html = (incremental = false) => {
if (settings.templateEngine === 'twig') {
return incremental ? htmlTwigIncremental() : htmlTwig()
} else {
return incremental ? htmlPugIncremental() : htmlPug()
}
}
// Создание SSL сертификатов для разработки
const createSSLCerts = (done) => {
const sslDir = path.join(__dirname, 'ssl')
const keyPath = path.join(sslDir, 'server.key')
const certPath = path.join(sslDir, 'server.crt')
if (fs.existsSync(keyPath) && fs.existsSync(certPath)) {
console.log('✅ SSL сертификаты уже существуют')
done()
return
}
if (!fs.existsSync(sslDir)) {
fs.mkdirSync(sslDir, { recursive: true })
}
try {
console.log('🔐 Создание SSL сертификатов для разработки...')
execSync(`openssl genrsa -out ${keyPath} 2048`, { stdio: 'pipe' })
const certCommand = `openssl req -new -x509 -key ${keyPath} -out ${certPath} -days 365 -subj "/C=RU/ST=Local/L=Local/O=Development/OU=Dev/CN=localhost"`
execSync(certCommand, { stdio: 'pipe' })
console.log('✅ SSL сертификаты успешно созданы')
console.log('⚠️ Это сертификаты только для разработки!')
} catch (error) {
console.error('❌ Ошибка создания SSL сертификатов:')
console.error('Убедитесь что OpenSSL установлен в системе')
}
done()
}
// Создание конфигурационного файла
const createHTTPSConfig = (done) => {
const configPath = path.join(__dirname, '.env.local')
if (!fs.existsSync(configPath)) {
const envContent = `# Конфигурация для разработки
# Этот файл автоматически загружается при запуске gulp
# ===== ОСНОВНЫЕ НАСТРОЙКИ =====
# Включить HTTPS (true/false)
# После изменения нужно перезапустить gulp
HTTPS=false
# Шаблонизатор (pug/twig)
# После изменения нужно перезапустить gulp
TEMPLATE_ENGINE=pug
# ===== НАСТРОЙКИ СЕРВЕРА =====
# Порт для разработки
PORT=3000
# Хост для разработки (localhost, 127.0.0.1, 0.0.0.0 для доступа извне)
HOST=localhost
# Автоматически открывать браузер при запуске (true/false)
OPEN_BROWSER=false
# Показывать уведомления BrowserSync (true/false)
BROWSER_SYNC_NOTIFY=false
`
fs.writeFileSync(configPath, envContent)
console.log('✅ Создан файл конфигурации: .env.local')
console.log('📝 Теперь вы можете настроить параметры в .env.local')
} else {
console.log(' Файл .env.local уже существует')
}
done()
}
// Функция для перезагрузки конфигурации
const reloadConfig = (done) => {
const envPath = path.join(__dirname, '.env.local')
if (fs.existsSync(envPath)) {
// Очищаем старые значения
delete process.env.TEMPLATE_ENGINE
delete process.env.HTTPS
delete process.env.PORT
delete process.env.HOST
delete process.env.OPEN_BROWSER
delete process.env.BROWSER_SYNC_NOTIFY
// Загружаем новые
dotenv.config({ path: envPath, override: true })
// Обновляем настройки
settings.templateEngine = process.env.TEMPLATE_ENGINE || 'pug'
settings.https.enabled = process.env.HTTPS === 'true'
settings.server.port = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000
settings.server.host = process.env.HOST || 'localhost'
settings.server.open = process.env.OPEN_BROWSER === 'true'
settings.server.notify = process.env.BROWSER_SYNC_NOTIFY === 'true'
console.log('🔄 Конфигурация перезагружена!')
console.log('⚙️ Новые настройки:')
console.log(` Шаблонизатор: ${settings.templateEngine}`)
console.log(` HTTPS: ${settings.https.enabled ? 'включен' : 'выключен'}`)
console.log(` Порт: ${settings.server.port}`)
console.log(` Хост: ${settings.server.host}`)
console.log('⚠️ Для применения HTTPS и смены шаблонизатора нужен перезапуск!')
} else {
console.log('❌ Файл .env.local не найден')
}
done()
}
// Задача для создания примера структуры данных
const createDataExample = (done) => {
const dataDir = path.join(__dirname, 'src/data')
if (!fs.existsSync(dataDir)) {
fs.mkdirSync(dataDir, { recursive: true })
}
const exampleData = {
site: {
title: 'Мой сайт',
description: 'Описание сайта',
author: 'Автор'
},
navigation: [
{ title: 'Главная', url: '/' },
{ title: 'О нас', url: '/about' },
{ title: 'Контакты', url: '/contacts' }
]
}
fs.writeFileSync(
path.join(dataDir, 'global.json'),
JSON.stringify(exampleData, null, 2)
)
console.log('✅ Создан пример файла данных: src/data/global.json')
done()
}
const refresh = (done) => {
sync.reload()
done()
}
// Обработка конкретного Pug файла
const processSinglePugFile = (filePath) => {
const data = loadData()
const relativePath = path.relative('src/pug/pages', filePath)
const outputPath = path.join('dist', relativePath.replace('.pug', '.html'))
console.log(`📄 Обработка файла: ${relativePath}`)
return gulp
.src(filePath)
.pipe(plumber({
errorHandler(err) {
console.error('Pug Error:', err.toString())
this.emit('end')
}
}))
.pipe(
pug({
pretty: true,
basedir: 'src/pug',
locals: {
baseUrl,
searchId,
getSrcset: getImageSrcset,
getMime: getImageMime,
...data
}
})
)
.pipe(rename(path.basename(outputPath)))
.pipe(gulp.dest(path.dirname(outputPath)))
}
// Обработка конкретного Twig файла
const processSingleTwigFile = (filePath) => {
const data = loadData()
const relativePath = path.relative('src/twig/pages', filePath)
const outputPath = path.join('dist', relativePath.replace('.twig', '.html'))
console.log(`📄 Обработка файла: ${relativePath}`)
return gulp
.src(filePath)
.pipe(plumber({
errorHandler(err) {
console.error('Twig Error:', err.toString())
this.emit('end')
}
}))
.pipe(
twig({
base: 'src/twig',
data: {
baseUrl,
searchId,
getSrcset: getImageSrcset,
getMime: getImageMime,
...data
},
functions: [
{
name: 'getSrcset',
func: getImageSrcset
},
{
name: 'getMime',
func: getImageMime
}
]
})
)
.pipe(rename(path.basename(outputPath)))
.pipe(gulp.dest(path.dirname(outputPath)))
}
// Сервер с поддержкой HTTPS
const server = () => {
let serverConfig = {
server: 'dist/',
notify: settings.server.notify,
open: settings.server.open,
cors: true,
ui: false,
port: settings.server.port,
host: settings.server.host
}
if (settings.https.enabled) {
const keyPath = path.join(__dirname, settings.https.keyPath)
const certPath = path.join(__dirname, settings.https.certPath)
if (fs.existsSync(keyPath) && fs.existsSync(certPath)) {
serverConfig.https = {
key: keyPath,
cert: certPath
}
console.log('🔒 Запуск сервера с HTTPS')
console.log(`📍 https://${serverConfig.host}:${serverConfig.port}`)
} else {
console.log('⚠️ HTTPS включен, но сертификаты не найдены')
console.log('Запустите: gulp ssl для создания сертификатов')
}
} else {
console.log('🌐 Запуск HTTP сервера')
console.log(`📍 http://${serverConfig.host}:${serverConfig.port}`)
}
sync.init(serverConfig)
// Умное наблюдение за страницами
if (settings.templateEngine === 'twig') {
// Отслеживание изменений в страницах Twig
const twigWatcher = gulp.watch('src/twig/pages/**/*.twig')
twigWatcher.on('change', (filePath) => {
console.log(`🔄 Изменен файл: ${path.relative(__dirname, filePath)}`)
return gulp.series(
() => processSingleTwigFile(filePath),
refresh
)()
})
// Наблюдение за общими файлами - полная пересборка
gulp.watch(['src/twig/**/*.twig', '!src/twig/pages/**/*.twig'], gulp.series(() => html(false), injectSprite, refresh))
} else {
// Отслеживание изменений в страницах Pug
const pugWatcher = gulp.watch('src/pug/pages/**/*.pug')
pugWatcher.on('change', (filePath) => {
console.log(`🔄 Изменен файл: ${path.relative(__dirname, filePath)}`)
return gulp.series(
() => processSinglePugFile(filePath),
refresh
)()
})
// Наблюдение за общими файлами - полная пересборка
gulp.watch(['src/pug/**/*.pug', '!src/pug/pages/**/*.pug'], gulp.series(() => html(false), injectSprite, refresh))
}
// Наблюдение за данными - полная пересборка
gulp.watch('src/data/**/*.json', gulp.series(() => html(false), injectSprite, refresh))
// Наблюдение за спрайтами - полная пересборка с инъекцией
gulp.watch('src/icons/**/*.svg', gulp.series(sprite, () => html(false), injectSprite, refresh))
// Остальные файлы
gulp.watch('src/scss/**/*.scss', gulp.series(css))
gulp.watch('src/js/**/*.js', gulp.series(js, refresh))
gulp.watch('src/img/**/*.{png,jpg,jpeg,svg}', gulp.series(processImages, refresh))
}
// Экспорт задач
export const build = gulp.series(
clean,
copy,
css,
js,
processImages,
sprite,
() => html(false),
injectSprite
)
export const dev = gulp.series(build, server)
export const data = createDataExample
export const ssl = createSSLCerts
export const configInit = createHTTPSConfig
export const configReload = reloadConfig
// HTTPS команды
export const devSSL = gulp.series(
(done) => {
settings.https.enabled = true
done()
},
ssl,
build,
server
)
// Команды с выбором шаблонизатора
export const buildPug = gulp.series(
(done) => { settings.templateEngine = 'pug'; done() },
build
)
export const buildTwig = gulp.series(
(done) => { settings.templateEngine = 'twig'; done() },
build
)
export const devPug = gulp.series(
(done) => { settings.templateEngine = 'pug'; done() },
build,
server
)
export const devTwig = gulp.series(
(done) => { settings.templateEngine = 'twig'; done() },
build,
server
)
export const devPugSSL = gulp.series(
(done) => {
settings.templateEngine = 'pug'
settings.https.enabled = true
done()
},
ssl,
build,
server
)
export const devTwigSSL = gulp.series(
(done) => {
settings.templateEngine = 'twig'
settings.https.enabled = true
done()
},
ssl,
build,
server
)