---
title: "Cборка Gulp в 2026-м: актуальна ли?"
description: "Начинаю цикл статей про маленький SPA-скелет без фреймворка, который использую в нескольких своих проектах — дальше разберу EventBus, PageBase и PageManager, а сегодня — то,…"
url: "https://rowan-web.ru/blog/golaya-sborka-na-gulp-v-2026-m/"
date_modified: "2026-09-06T06:17:26+03:00"
language: "ru-RU"
---
Начинаю цикл статей про маленький SPA-скелет без фреймворка, который использую в нескольких своих проектах — дальше разберу EventBus, PageBase и PageManager, а сегодня — то, с чего всё начинается физически: сборка. Gulp в 2026 году — это уже немного не то, что было раньше… Прогресс давно шагнул вперед, но все же,конкретно для меня, есть в этом какое-то удобство. Стоит отметить, что года три-четыре назад, я держал себе для проектов абсолютно другую сборку: конвертер шрифтов, картинок, разные шаблоны, и много чего прочего… Но на сегодняшний день, все это сейчас абсолютно уже не нужно, как минимум мне…. Здесь нет ни единого лишнего пакета, просто gulp-сборка с минимально простыми функциями. Это про безотказность и стабильность и простоту.

Зачем это в 2026-м, если есть, например, Vite
---------------------------------------------

Не для замены Vite. У связки gulp + голые ES-модули есть узкая ниша: маленькое SPA без сборки в привычном смысле — на выходе не бандл, а те же файлы, что в src, только сжатые и с проставленной версией в путях. Никакого дерева зависимостей модулей, никакого code splitting, никакой HMR-магии — просто пять задач, которые можно прочитать за пять минут и понять целиком. Для внутреннего инструмента, лендинга с интерактивом или прототипа, где важнее скорость старта и прозрачность, чем миллисекунды сборки, это осознанный выбор, а не то, что «забыли обновить».

Пять задач и что каждая реально делает
--------------------------------------

Компиляция стилей — без сюрпризов, sass в сжатый css, с проливом через browser-sync.stream() — это не перезагрузка страницы, а инъекция обновлённого CSS без потери состояния (открытая модалка, скролл, значения в полях формы остаются на месте):

```
function compileSass() {
  return gulp.src(paths.scss)
    .pipe(sass({ outputStyle: 'compressed' }).on('error', sass.logError))
    .pipe(gulp.dest(`${paths.dist}/assets/css`))
    .pipe(browserSync.stream());
}
```

С JS чуть интереснее. babel тут не транспилирует import/export — в .babelrc стоит modules: false, поэтому модульная система остаётся нативной, браузер сам резолвит импорты по . babel только приводит новый синтаксис (приватные поля класса, опциональную цепочку) к виду, понятному чуть более старым браузерам, а terser минифицирует результат.

Кэш-бастинг через один build ID на всю сборку
---------------------------------------------

Вот ради чего вообще стоит читать эту статью, а не просто взять любой gulp-стартер. Проблема: браузер годами держит в кеше конкретный JS-модуль по его URL. Пересобрали файл — а браузер как открывал старую версию по /assets/js/pages/HomePage.js, так и продолжает, потому что сам путь не поменялся. Решение — вставить версию не только в тег , а буквально во ВСЕ относительные импорты внутри собранного JS, одним и тем же токеном на всю сессию сборки:

```
const BUILD_ID = Date.now();

function minifyJs() {
  return gulp.src(paths.js)
    .pipe(babel())
    .pipe(terser())
    // from "./x.js"     -> from "./x.js?v=..."
    // import "./x.js"   -> import "./x.js?v=..."
    // import("./x.js")  -> import("./x.js?v=...")
    .pipe(replace(/bfrom(s*)(['"])(.[^'"n]+?.js)2/g, `from$1$2$3?v=${BUILD_ID}$2`))
    .pipe(replace(/bimport(s+)(['"])(.[^'"n]+?.js)2/g, `import$1$2$3?v=${BUILD_ID}$2`))
    .pipe(replace(/bimport((s*)(['"])(.[^'"n]+?.js)2(s*))/g, `import($1$2$3?v=${BUILD_ID}$2$4)`))
    .pipe(gulp.dest(`${paths.dist}/assets/js`))
    .pipe(browserSync.stream());
}
```

Три регулярки — под три синтаксиса импорта: обычный from «./x.js», именованный import «./x.js» и динамический import(«./x.js»). BUILD\_ID один и тот же объект Date.now(), вычисленный один раз при запуске gulp, а не при каждом вызове функции — поэтому все файлы в рамках одной сборки получают одинаковую версию. Пересобрали — у всех модулей разом новый ?v=, у браузера не остаётся ни одного пути, по которому он может достать старую версию из кеша.

Та же версия проставляется и в HTML — на href/src тегов и :

```
function copyHtml() {
  return gulp.src(paths.html)
    .pipe(replace(/(href|src)="(assets/[^"]+.(?:css|js))"/g, `$1="$2?v=${BUILD_ID}"`))
    .pipe(gulp.dest(paths.dist));
}
```

SPA-фолбэк в дев-сервере
------------------------

Роутинг в этом скелете целиком на JS — PageManager переключает страницы внутри одного index.html, адресная строка не участвует. Значит, у сервера нет реального файла /about или /cabinet — только один index.html. Без специальной обработки прямой переход по такому адресу или обновление страницы по F5 упадёт в 404, потому что сервер честно ищет файл, которого нет. Фикс — на уровне middleware browser-sync: если путь не ведёт на конкретный файл (в URL нет точки), просто подменяем его на index.html:

```
function serve() {
  browserSync.init({
    server: {
      baseDir: paths.dist,
      middleware: (req, res, next) => {
        if (req.url !== '/' && !req.url.includes('.') && req.method === 'GET') {
          req.url = '/index.html';
        }
        next();
      }
    },
    port: 3000
  });

  gulp.watch(paths.scss, compileSass);
  gulp.watch(paths.js, minifyJs);
  gulp.watch(paths.html, copyHtml).on('change', browserSync.reload);
  gulp.watch(paths.images, copyImages).on('change', browserSync.reload);
  gulp.watch(paths.fonts, copyFonts).on('change', browserSync.reload);
}
```

Дальше — просто gulp.series, две сборки задач: build (копирование + компиляция, без сервера — то, что реально уходит в прод) и dev (build, а следом serve — то, чем пользуешься во время разработки):

```
const build = gulp.series(copyHtml, copyImages, copyFonts, compileSass, minifyJs);
const dev = gulp.series(build, serve);

gulp.task('build', build);
gulp.task('default', dev);
```

Что дальше
----------

На этой сборке держится маленький SPA-скелет без единого фреймворка — EventBus для связи между частями приложения, PageBase как аналог React-компонента без виртуального DOM, PageManager для переключения страниц. Про них — в следующих частях цикла.

[Полный список страниц сайта, читаемых AI](https://rowan-web.ru/llms.txt)
