Gulp: секретное оружие веб-разработчика для увеличения скорости сайта

Опубликовано: 2022-03-11

Многим из нас приходится работать с веб-проектами, которые используются в производстве, которые предоставляют различные услуги населению. При работе с такими проектами важно иметь возможность быстро создавать и развертывать наш код. Быстрое выполнение часто приводит к ошибкам, особенно если процесс повторяется, поэтому рекомендуется максимально автоматизировать такой процесс.

Gulp: секретное оружие веб-разработчика для увеличения скорости сайта

Мои коллеги-разработчики: Нет оправдания тому, что вы загружаете мусор в свой браузер.
Твитнуть

В этом посте мы рассмотрим инструмент, который может быть частью того, что позволит нам добиться такой автоматизации. Этот инструмент представляет собой пакет npm под названием Gulp.js. Чтобы ознакомиться с базовой терминологией Gulp.js, используемой в этом посте, обратитесь к «Введению в автоматизацию JavaScript с помощью Gulp», который ранее был опубликован в блоге Антониосом Минасом, одним из наших коллег-разработчиков Toptal. Мы предполагаем, что знакомы со средой npm, так как в этом посте она широко используется для установки пакетов.

Обслуживание внешних ресурсов

Прежде чем мы продолжим, давайте вернемся на несколько шагов назад, чтобы получить общее представление о проблеме, которую Gulp.js может решить для нас. Многие веб-проекты содержат интерфейсные файлы JavaScript, которые передаются клиенту для обеспечения различных функций веб-страницы. Обычно также имеется набор таблиц стилей CSS, которые также предоставляются клиенту. Иногда при просмотре исходного кода веб-сайта или веб-приложения мы можем увидеть такой код:

 <link href="css/main.css" rel="stylesheet"> <link href="css/custom.css" rel="stylesheet"> <script src="js/jquery.min.js"></script> <script src="js/site.js"></script> <script src="js/module1.js"></script> <script src="js/module2.js"></script>

Есть несколько проблем с этим кодом. Он содержит ссылки на две отдельные таблицы стилей CSS и четыре отдельных файла JavaScript. Это означает, что сервер должен сделать в общей сложности шесть запросов к серверу, и каждый запрос должен отдельно загрузить ресурс, прежде чем страница будет готова. С HTTP/2 это менее проблематично, потому что HTTP/2 вводит параллелизм и сжатие заголовков, но это все еще проблема. Это увеличивает общий объем трафика, необходимого для загрузки этой страницы, и снижает качество взаимодействия с пользователем, поскольку загрузка файлов занимает больше времени. В случае HTTP 1.1 он также перегружает сеть и сокращает количество доступных каналов запросов. Было бы намного лучше объединить файлы CSS и JavaScript в один пакет для каждого из них. Таким образом, будет всего два запроса. Также было бы неплохо предоставлять уменьшенные версии этих файлов, которые обычно намного меньше оригиналов. Наше веб-приложение также может сломаться, если какой-либо из ресурсов кэшируется, и клиент получит устаревшую версию.

Перегрузка

Один из примитивных подходов к решению некоторых из этих проблем состоит в том, чтобы вручную объединить каждый тип ресурсов в пакет с помощью текстового редактора, а затем запустить результат через службу минификатора, такую ​​как http://jscompress.com/. Это оказывается очень утомительным делать постоянно в процессе разработки. Небольшим, но сомнительным улучшением будет размещение нашего собственного сервера минификатора с использованием одного из пакетов, доступных на GitHub. Затем мы могли бы сделать что-то похожее на следующее:

 <script src="min/f=js/site.js,js/module1.js"></script>

Это позволит отдавать минифицированные файлы нашему клиенту, но не решит проблему кэширования. Это также вызовет дополнительную нагрузку на сервер, поскольку нашему серверу, по сути, придется многократно объединять и минимизировать все исходные файлы при каждом запросе.

Автоматизация с помощью Gulp.js

Конечно, мы можем добиться большего успеха, чем любой из этих двух подходов. Чего мы действительно хотим, так это автоматизировать сборку и включить ее в фазу сборки нашего проекта. Мы хотим получить готовые наборы ресурсов, которые уже минимизированы и готовы к работе. Мы также хотим, чтобы клиент получал самые последние версии наших связанных ресурсов по каждому запросу, но мы по-прежнему хотим использовать кэширование, если это возможно. К счастью для нас, Gulp.js может с этим справиться. В оставшейся части статьи мы будем создавать решение, которое будет использовать возможности Gulp.js для объединения и минимизации файлов. Мы также будем использовать плагин для очистки кеша при наличии обновлений.

В нашем примере мы создадим следующую структуру каталогов и файлов:

 public/ |- build/ |- js/ |- bundle-{hash}.js |- css/ |- stylesheet-{hash}.css assets/ |- js/ |- vendor/ |- jquery.js |- site.js |- module1.js |- module2.js |- css/ |- main.css |- custom.css gulpfile.js package.json
npm делает управление пакетами в проектах Node.js блаженством. Gulp обеспечивает потрясающую расширяемость, используя простой подход к упаковке npm для создания модульных и мощных плагинов.

В файле gulpfile.js мы определим задачи, которые Gulp будет выполнять для нас. package.json используется npm для определения пакета нашего приложения и отслеживания зависимостей, которые мы будем устанавливать. Публичный каталог — это то, что должно быть настроено для выхода в Интернет. В каталоге assets мы будем хранить наши исходные файлы. Чтобы использовать Gulp в проекте, нам нужно установить его через npm и сохранить как зависимость разработчика для проекта. Мы также хотим начать с подключаемого модуля concat для Gulp, который позволит нам объединять несколько файлов в один.

Чтобы установить эти два элемента, мы выполним следующую команду:

 npm install --save-dev gulp gulp-concat

Далее мы хотим начать писать содержимое gulpfile.js.

 var gulp = require('gulp'); var concat = require('gulp-concat'); gulp.task('pack-js', function () { return gulp.src(['assets/js/vendor/*.js', 'assets/js/main.js', 'assets/js/module*.js']) .pipe(concat('bundle.js')) .pipe(gulp.dest('public/build/js')); }); gulp.task('pack-css', function () { return gulp.src(['assets/css/main.css', 'assets/css/custom.css']) .pipe(concat('stylesheet.css')) .pipe(gulp.dest('public/build/css')); }); gulp.task('default', ['pack-js', 'pack-css']);

Здесь мы загружаем библиотеку gulp и ее плагин concat. Затем мы определяем три задачи.

Загрузка библиотеки gulp и ее подключаемого модуля concat

Первая задача ( pack-js ) определяет процедуру сжатия нескольких исходных файлов JavaScript в один пакет. Мы перечисляем исходные файлы, которые будут объединены, прочитаны и объединены в указанном порядке. Мы передаем это в плагин concat, чтобы получить последний файл с именем bundle.js . Наконец, мы говорим gulp записать файл в public/build/js .

Вторая задача ( pack-css ) делает то же самое, что и выше, но для таблиц стилей CSS. Он указывает Gulp сохранять объединенный вывод как stylesheet.css в public/build/css .

Третья задача ( default ) — это та, которую запускает Gulp, когда мы вызываем ее без аргументов. Во втором параметре мы передаем список других задач, которые нужно выполнить при запуске задачи по умолчанию.

Давайте вставим этот код в gulpfile.js с помощью любого редактора исходного кода, который мы обычно используем, а затем сохраним файл в корневом каталоге приложения.

Далее мы откроем командную строку и запустим:

 gulp

Если мы посмотрим на наши файлы после запуска этой команды, мы найдем два новых файла: public/build/js/bundle.js и public/build/css/stylesheet.css . Они представляют собой конкатенацию наших исходных файлов, что решает часть исходной проблемы. Однако они не минифицированы, и очистки кеша пока нет. Добавим автоматическую минификацию.

Оптимизация встроенных активов

Нам понадобятся два новых плагина. Чтобы добавить их, мы выполним следующую команду:

 npm install --save-dev gulp-clean-css gulp-minify

Первый плагин предназначен для минимизации CSS, а второй — для минимизации JavaScript. Первый использует пакет clean-css, а второй использует пакет UglifyJS2. Сначала мы загрузим эти два пакета в наш gulpfile.js:

 var minify = require('gulp-minify'); var cleanCss = require('gulp-clean-css');

Затем нам нужно будет использовать их в наших задачах непосредственно перед записью вывода на диск:

 .pipe(minify()) .pipe(cleanCss())

Теперь gulpfile.js должен выглядеть так:

 var gulp = require('gulp'); var concat = require('gulp-concat'); var minify = require('gulp-minify'); var cleanCss = require('gulp-clean-css'); gulp.task('pack-js', function () { return gulp.src(['assets/js/vendor/*.js', 'assets/js/main.js', 'assets/js/module*.js']) .pipe(concat('bundle.js')) .pipe(minify()) .pipe(gulp.dest('public/build/js')); }); gulp.task('pack-css', function () { return gulp.src(['assets/css/main.css', 'assets/css/custom.css']) .pipe(concat('stylesheet.css')) .pipe(cleanCss()) .pipe(gulp.dest('public/build/css')); }); gulp.task('default', ['pack-js', 'pack-css']);

Давайте снова запустим gulp. Мы увидим, что файл stylesheet.css сохранен в минимизированном формате, а файл bundle.js по-прежнему сохраняется как есть. Мы заметим, что теперь у нас также есть минимизированный bundle-min.js. Нам нужен только уменьшенный файл, и мы хотим, чтобы он был сохранен как bundle.js , поэтому мы изменим наш код с дополнительными параметрами:

 .pipe(minify({ ext:{ min:'.js' }, noSource: true }))

Согласно документации плагина gulp-minify (https://www.npmjs.com/package/gulp-minify), это установит желаемое имя для минифицированной версии и сообщит плагину не создавать версию, содержащую исходный код. Если мы удалим содержимое каталога сборки и снова запустим gulp из командной строки, мы получим всего два мини-файла. Мы только что закончили реализацию этапа минификации нашего процесса сборки.

Очистка кеша

Далее мы хотим добавить очистку кеша, и для этого нам нужно будет установить плагин:

 npm install --save-dev gulp-rev

И требуем его в нашем файле gulp:

 var rev = require('gulp-rev');

Использование плагина немного сложно. Сначала мы должны передать минимизированный вывод через плагин. Затем нам нужно снова вызвать плагин после того, как мы запишем результаты на диск. Плагин переименовывает файлы, чтобы они были помечены уникальным хешем, а также создает файл манифеста. Файл манифеста — это карта, которую наше приложение может использовать для определения последних имен файлов, на которые мы должны ссылаться в нашем HTML-коде. После того, как мы изменим файл gulp, он должен выглядеть следующим образом:

 var gulp = require('gulp'); var concat = require('gulp-concat'); var minify = require('gulp-minify'); var cleanCss = require('gulp-clean-css'); var rev = require('gulp-rev'); gulp.task('pack-js', function () { return gulp.src(['assets/js/vendor/*.js', 'assets/js/main.js', 'assets/js/module*.js']) .pipe(concat('bundle.js')) .pipe(minify({ ext:{ min:'.js' }, noSource: true })) .pipe(rev()) .pipe(gulp.dest('public/build/js')) .pipe(rev.manifest()) .pipe(gulp.dest('public/build')); }); gulp.task('pack-css', function () { return gulp.src(['assets/css/main.css', 'assets/css/custom.css']) .pipe(concat('stylesheet.css')) .pipe(cleanCss()) .pipe(rev()) .pipe(gulp.dest('public/build/css')) .pipe(rev.manifest()) .pipe(gulp.dest('public/build')); }); gulp.task('default', ['pack-js', 'pack-css']);
С правильной очисткой кеша вы можете сходить с ума от длительного срока действия ваших файлов JS и CSS и надежно заменять их более новыми версиями, когда это необходимо.

Давайте удалим содержимое нашего каталога сборки и снова запустим gulp. Мы обнаружим, что теперь у нас есть два файла с хэштегами, прикрепленными к каждому из имен файлов, и manifest.json, сохраненный в public/build . Если мы откроем файл манифеста, мы увидим, что он содержит ссылку только на один из наших минифицированных и помеченных файлов. Происходит следующее: каждая задача записывает отдельный файл манифеста, и в итоге один из них перезаписывает другой. Нам нужно будет изменить задачи с дополнительными параметрами, которые сообщат им, что нужно искать существующий файл манифеста и объединять в него новые данные, если он существует. Синтаксис для этого немного сложен, поэтому давайте посмотрим, как должен выглядеть код, а затем пройдемся по нему:

 var gulp = require('gulp'); var concat = require('gulp-concat'); var minify = require('gulp-minify'); var cleanCss = require('gulp-clean-css'); var rev = require('gulp-rev'); gulp.task('pack-js', function () { return gulp.src(['assets/js/vendor/*.js', 'assets/js/main.js', 'assets/js/module*.js']) .pipe(concat('bundle.js')) .pipe(minify({ ext:{ min:'.js' }, noSource: true })) .pipe(rev()) .pipe(gulp.dest('public/build/js')) .pipe(rev.manifest('public/build/rev-manifest.json', { merge: true })) .pipe(gulp.dest('')); }); gulp.task('pack-css', function () { return gulp.src(['assets/css/main.css', 'assets/css/custom.css']) .pipe(concat('stylesheet.css')) .pipe(cleanCss()) .pipe(rev()) .pipe(gulp.dest('public/build/css')) .pipe(rev.manifest('public/build/rev-manifest.json', { merge: true })) .pipe(gulp.dest('')); }); gulp.task('default', ['pack-js', 'pack-css']);

Сначала мы передаем вывод в rev.manifest() . Это создает файлы с тегами вместо файлов, которые у нас были раньше. Мы указываем желаемый путь нашего rev-manifest.json и говорим rev.manifest() объединиться с существующим файлом, если он существует. Затем мы говорим gulp записать манифест в текущий каталог, который в этот момент будет public/build. Проблема с путем связана с ошибкой, которая более подробно обсуждается на GitHub.

Теперь у нас есть автоматическая минификация, файлы с тегами и файл манифеста. Все это позволит нам быстрее доставлять файлы пользователю и очищать их кеш всякий раз, когда мы вносим свои изменения. Однако остаются только две проблемы.

Первая проблема заключается в том, что если мы внесем какие-либо изменения в наши исходные файлы, мы получим новые тегированные файлы, но старые также останутся там. Нам нужен способ автоматического удаления старых минифицированных файлов. Давайте решим эту проблему с помощью плагина, который позволит нам удалять файлы:

 npm install --save-dev del

Мы потребуем это в нашем коде и определим две новые задачи, по одной для каждого типа исходного файла:

 var del = require('del'); gulp.task('clean-js', function () { return del([ 'public/build/js/*.js' ]); }); gulp.task('clean-css', function () { return del([ 'public/build/css/*.css' ]); });

Затем мы позаботимся о том, чтобы новая задача завершилась раньше наших двух основных задач:

 gulp.task('pack-js', ['clean-js'], function () { gulp.task('pack-css', ['clean-css'], function () {

Если мы снова запустим gulp после этой модификации, у нас будут только последние минифицированные файлы.

Вторая проблема заключается в том, что мы не хотим запускать gulp каждый раз, когда вносим изменения. Чтобы решить эту проблему, нам нужно определить задачу наблюдателя:

 gulp.task('watch', function() { gulp.watch('assets/js/**/*.js', ['pack-js']); gulp.watch('assets/css/**/*.css', ['pack-css']); });

Мы также изменим определение нашей задачи по умолчанию:

 gulp.task('default', ['watch']);

Если мы теперь запустим gulp из командной строки, мы обнаружим, что он больше ничего не создает при вызове. Это связано с тем, что теперь она вызывает задачу-наблюдатель, которая будет следить за нашими исходными файлами на предмет любых изменений и выполнять сборку только тогда, когда обнаружит изменение. Если мы попытаемся изменить любой из наших исходных файлов, а затем снова посмотрим на нашу консоль, мы увидим, что задачи pack-js и pack-css запускаются автоматически вместе со своими зависимостями.

Теперь все, что нам нужно сделать, это загрузить файл manifest.json в наше приложение и получить из него имена файлов с тегами. То, как мы это делаем, зависит от нашего конкретного внутреннего языка и стека технологий, и реализовать его будет довольно тривиально, поэтому мы не будем подробно останавливаться на этом. Однако общая идея заключается в том, что мы можем загрузить манифест в массив или объект, а затем определить вспомогательную функцию, которая позволит нам вызывать версионные активы из наших шаблонов примерно так:

 gulp('bundle.js')

Как только мы это сделаем, нам больше не придется беспокоиться об измененных тегах в именах файлов, и мы сможем сосредоточиться на написании высококачественного кода.

Окончательный исходный код для этой статьи вместе с несколькими примерами ресурсов можно найти в этом репозитории GitHub.

Заключение

В этой статье мы рассмотрели, как реализовать автоматизацию на основе Gulp для нашего процесса сборки. Я надеюсь, что это окажется полезным для вас и позволит вам разрабатывать более сложные процессы сборки в ваших собственных приложениях.

Пожалуйста, имейте в виду, что Gulp — это только один из инструментов, которые можно использовать для этой цели, и есть много других, таких как Grunt, Browserify и Webpack. Они различаются по своему назначению и по объему задач, которые могут решить. Некоторые могут решить проблемы, которые не может решить Gulp, например, связывание модулей JavaScript с зависимостями, которые можно загружать по запросу. Это называется «разделением кода» и представляет собой улучшение по сравнению с идеей предоставления одного большого файла со всеми частями нашей программы на каждой странице. Эти инструменты довольно сложны, но могут быть рассмотрены в будущем. В следующем посте мы рассмотрим, как автоматизировать развертывание нашего приложения.

Связанный: Gulp Under the Hood: Создание инструмента автоматизации задач на основе потоков