Разбираемся со сборкой front end на Gulp. Приятная сборка frontend проекта15.02.2015 Джентльменский набор Front-end разработчика

сентябрь 24 , 2016

Будут рассмотрены такие вопросы: работа со стилями sass с использованием sourcemaps, склеивание и сжатие js-файлов, сборка requirejs с помощью rjs, препроцессинг html, очистка и копирование файлов, оптимизация изображений, поднятие локального веб-сервера и режим наблюдения - watch-таски. Добро пожаловать в статью, будет много интересного!
P.S. Материала много, поэтому статья будет разбита на 3 части: основы сборки и организация проекта, написание тестового приложения на Backbone + Require.js и собственно сборка с помощью gulp.

Зачем нужна сборка фронтенда.

Один из самых важных принципов в разработке - это модульность. Разбиение кода проекта на множество небольших, в идеале слабосвязанных между собой кусочков-модулей. Это касается далеко не только javascript-кода. Это относится и к стилям, и html-шаблонам, к различным библиотекам.

Примерно так может выглядеть структура простого приложения, написанного на Backbone + Require.

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

Разница хорошо заметна: вместо десятков файлов мы имеем один index.html, один css-файл, оптимизированные и сжатые изображения в отдельной папке, хотя на скриншоте этого и не видно:-)

А также самое интересное: в папке js мы получили всего 3 сжатых файла.
P.S. Почему три, а не один, расскажу позже.
Замечу, что это реальная структура тестового приложения, которое мы вскоре напишем.

Я описал лишь одну причину использования инструментов сборки, но она уже достаточна, чтобы начать использовать gulp, grunt, webpack или что-то подобное в своих проектах. Причем не имеет значения, пишем ли мы громадный сервис, небольшое SPA (как в нашей статье) или landing page. Принципы сборки одинаковы для всех проектов и различаются лишь разнообразием задач и подходов к их решению. В нашем примере мы создадим такую структуру, которая может сколь угодно расшириться в дальнейшем, но на выходе всегда будет аккуратная стопка файлов, готовая к выливке на ваш продакшен - боевой сайт.

Как правильно организовать проект.

Принцип такой: есть раздел с девелоперскими файлами, есть с собранными и все остальное, что это добро обслуживает. Создадим в корне проекта 2 папки: src и build. В src и только в src мы будем работать, создавать новые файлы, редактировать их и вообще развлекаться. На скриншоте выше, там, где пара десятков файлов, Вы видели именно содержимое папки src нашего тестового проекта. А чуть ниже несколько аккуратных файлов из папки build. Она формируется только автоматически, инструментами сборки, ничего самим там править не нужно. Все равно при каждой сборке ее содержимое затирается новыми файлами (а в режиме development папки build вообще нет - удаляется, дабы не мозолить глаза)

Кроме src и build в корне будут лежать файлы package.json, gulpfile.js, папка node_modules и опционально, .gitignore (если Вы работает с гитом). У меня еще можно заметить папку logs - это порождение apache и давняя привычка держать логи проекта в его же папке, конечно, исключив ее из репозитория гита:-)

Вот так выглядит структура проекта целиком:

По содержимому build, думаю, вопросов нет, по src поясню подробнее:

  • 1. html - index.html, корневой индексный файл проекта. Почему не сразу в корне src? Потому что он будет препроцесситься и создаваться gulp-ом. Каким образом, узнаем немного позже, когда вплотную займемся сборкой.
  • 2. img - изображения, не сжатые, обычные
  • 3. js - вся javascript-движуха проекта, модели и представления Backbone
  • 4. lib - сторонние библиотеки, вроде backbone.js, require.js, lodash.js и прочих
  • 5. scripts - js-скрипты, которые необходимы на боевом сайте, но не нужны в режиме разработки. Имеются в виду коды для аналитики, различных экспериментов и прочих маркетинговых штук.
  • 6. styles - sass-файлы со стилями. В эту же папку будет складываться собранный css-файл (только для режима девелопмента)
  • 7. tpl - html-шаблоны. Используются представлениями Backbone с помощью плагина require.js text

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

Какие задачи сборки мы решим.

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

Вот список того, чем мы займемся после написания тестового приложения:

  • 1. Очистка файлов и папок от результатов предыдущей сборки
  • 2. Сборка css из sass-файлов, со сжатием и без
  • 3. Подключение sourcemaps к стилям, заодно покажу на примере, зачем это нужно
  • 4. Сборка js-бандла с помощью requirejs
  • 5. Склеивание и сжатие отдельных js-файлов (аналитики)
  • 6. Препроцессинг html
  • 7. Оптимизация и сжатие изображений
  • 8. Поднятие локального веб-сервера
  • 9. Задачи наблюдения за файлами при работе в режиме девелопмента - watch-таски
  • 10. Сбор отдельных задач в кучу - итоговые таски для production-сборки и development-работы

Итак, мы порассуждали, зачем вообще нужна сборка фронтенда, определились со структурой проекта, подробно разобрали, что мы хотим от сборки и в общих чертах поговорили про тестовое приложение. В следующей части статьи мы займемся написанием простенького Backbone-приложения в связке с Require.js. Если Вы не знакомы с Backbone и/или Require.js, то ничего страшного нет. Собственно Backbone-овского кода в приложении мало. Вы легко можете вместо него использовать любимую библиотеку или просто писать код на javascript/jquery и пропустить раздел настройки requirejs.

Джентльменский набор Front-end разработчика

Ни для кого не секрет, что современный front-end разработчик обязан иметь в своем вооружении один из инструментов сборки проектов такие как Gulp или Grunt . До какого-то времени Grunt занимал монополию в этом вопросе, но отделившаяся от Grunt группа разработчиков решила создать свой легковесный и быстрый таск-менеджер Gulp.

В этой статье мы с нуля подготовим стартовый пакет для использования его в будущих проектах.

Какие мы используем технологии

  • Программная платформа: Node.js
  • CSS пре-процессор: Stylus
  • Таск-менеджер: Gulp

Зачем фронтендеру таск-менеджер

До недавнего времени я сам задавался вопросом, зачем мне нужно тратить время на настройку конфига таск-менеджера, если я и так неплохо справляюсь с версткой макетов, пока не начал использовать CSS пре-процессоры.

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

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

Установка Node.js

Если вы знаете как установить node.js на свою систему и пользуетесь ей, можете смело переходить к следующему заголовку.

Хотелось бы сразу предупредить, что все описанные действия актуальны для Mac OS X , но в целом применимы и для других Unix систем. Разработка через таск-менеджер и командную строку в Windows несколько более затруднительная и тут она описана не будет. Однако, если вы все же пользуетесь Windows и не готовы от него отказываться, то могу предложить вариант использования виртуальной машины с установленной Ubuntu , я использую этот вариант на своей домашней системе, что в целом довольно удобно.

Итак первым делом нам предстоит скачать и установить пакет node.js в свою систему для работы с нодой через консоль. Переходим на официальный сайт node.js и скачиваем свежую stable версию для вашей системы. После установки в вашей командной строке должна быть доступна команда node . Чтобы проверить что ваша node работает, введем в командной строке команду

в ответ должна появиться версия установленной node.js. Если все хорошо, идем дальше.

Структура директорий проекта

В наших проектах мы будем использовать унифицированный вариант структуры:

Develop - корневой каталог разработки └─start - каталог проекта ├─build - билд собраный таск-менеджером ├─resource - все файлы исходники для разработки (.psd и пр.) ├─src - каталог разработки │├─css - каталог разработки стилей ││├─images - все статичные изображения ││├─sprites - изображение собираемые в спрайт ││├─partial - пользовательские файлы стилей │││├─mixins.styl - пользовательские миксины │││└─styles.styl - пользовательские стили ││├─vendor - прочие внешние файлы стилей ││└─styles.styl - основной файл стилей │├─fonts - каталог шрифтов │├─img - каталог динамических изображений │├─js - каталог разработки JavaScript ││├─_*.js - побочные файлы js ││├─_main.js - основной пользовательский js ││└─main.js - основной файл js │├─.htaccess - конфиг для сервера │├─*.html - файлы разметки страницы │├─pages.html - файл со ссылками на все страницы шаблона │├─index.html - индексовый файл разметки страницы │└─include - каталог подключаемых файлов разметки │ └─*.html - подключаемые файлы разметки (header.html и пр.) ├─package.json - конфиг пакетного менеджера npm ├─gulpfile.js - конфиг Gulp ├─stylus.template.mustache - маска для чтения спрайтов ├─TODO - todo лист └─.gitignore - конфиг для Git

Установка

В консоли с помощью команды cd перейдем в корневой каталог разработки, создадим каталог нашего проекта mkdir start и перейдем в него.

Установим нашу структуру для проекта через консоль:

mkdir build resource src src/css src/css/images src/css/sprites src/css/partial src/css/vendor src/js src/template src/template/include src/img src/fonts

Создадим начальные файлы в структуре проекта:

touch gulpfile.js stylus.template.mustache .gitignore src/.htaccess src/TODO src/css/styles.styl src/css/partial/styles.styl src/css/partial/mixins.styl src/js/main.js src/js/_main.js src/template/pages.html src/template/index.html src/template/include/header.html src/template/include/footer.html

Создадим package.json

все всплывающие вопросы можно прощелкать через Enter, node установит им значения по-умолчанию, либо заполнить предлагаемые поля.

.gitignore

Говорим гиту какие каталоги игнорировать и не заливать в репозиторий:

/node_modules/ /build/ /resource/

Каталог node_modules появится позже после установки плагинов и будет содержать в себе все node-плагины проекта.

src/.htaccess

Устанавливаем для сервера дополнительное gzip сжатие и кэширование:

AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript # Serve gzip compressed CSS files if they exist # and the client accepts gzip. RewriteCond "%{HTTP:Accept-encoding}" "gzip" RewriteCond "%{REQUEST_FILENAME}\.gz" -s RewriteRule "^(.*)\.css" "$1\.css\.gz" # Serve gzip compressed JS files if they exist # and the client accepts gzip. RewriteCond "%{HTTP:Accept-encoding}" "gzip" RewriteCond "%{REQUEST_FILENAME}\.gz" -s RewriteRule "^(.*)\.js" "$1\.js\.gz" # Serve correct content types, and prevent mod_deflate double gzip. RewriteRule "\.css\.gz$" "-" RewriteRule "\.js\.gz$" "-" # Serve correct encoding type. Header append Content-Encoding gzip # Force proxies to cache gzipped & # non-gzipped css/js files separately. Header append Vary Accept-Encoding ExpiresActive on ExpiresByType application/javascript "access plus 1 months" ExpiresByType image/jpg "access plus 1 month" ExpiresByType image/jpeg "access plus 1 month" ExpiresByType image/gif "access plus 1 month" ExpiresByType image/png "access plus 1 month" ExpiresByType text/css "access plus 1 months"

src/css/styles.styl

Подключим пользовательские файлы стилей в основной файл стилей:

@import "partial/styles"

Обратите внимание, что для подключение.styl файлов, расширение не указывается, согласно семантике кода пре-процессора Stylus. Для подключения стилей в другом расширении, например.css, указание последнего обязательно.

TODO

Эта страница содержит в себе todo лист разработки. Более подробно о работе с данным файлом вы можете почитать на странице плагина PlainTasks для Sublime Text.

На этом установка структуры завершена.

Установка плагинов через пакетный менеджер npm

Node.js по-умолчанию включает в себя пакетный менеджер npm , в репозиториях которого собрано множество плагинов, с которыми нам и предстоит работать.

Установка плагина Gulp

Для начала нужно установить Gulp глобально (с ключем -g) на нашу систему

npm install gulp -g

Делать это нужно один раз , в дальнейшем глобальная установка не требуется.

Теперь нужно установить Gulp локально в каталог проекта

npm install gulp --save-dev

Ключ --save-dev говорит о том, что информация о плагине (имя в репозитории и его версия) будет добавлена в конфиг package.json и запомнит его для данного проекта. Поскольку мы не храним в гите тяжеловесную папку с плагинами node_modules , сохраненная в конфиге информация об установленных плагинах позволит всего одной командой npm i развернуть в проекте все нужные плагины.

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

В дальнейшем мы так же будем пользоваться сокращенным форматом команд.

Плагин Stylus для Gulp

В наших проектах мы пользуемся пре-процессором Stylus который отлично работает и компилируется на ноде.

Устанавливаем:

npm i gulp-stylus -D

Плагины обработки CSS

Автопрефиксер - автоматически подставляет префиксы -ms- -o- -moz- -webkit- в нужные свойства:

npm i gulp-autoprefixer -D

Минификация CSS - плагин минифицирует выходной CSS файл избавляя его от лишних пробелов и табуляций:

npm i gulp-minify-css -D

Плагины обработки изображений

Объединение картинок в спрайты - вам больше не нужно тратить часы драгоценного времени на объединение всех изображений в спрайты, а потом высчитывать их координаты, все это за вас автоматически сделает этот плагин:

npm i gulp.spritesmith -D

Добавим в ранее созданный файл stylus.template.mustache маску вычисления позиций в спрайтах:

{{#items}} ${{name}} = {{px.x}} {{px.y}} {{px.offset_x}} {{px.offset_y}} {{px.width}} {{px.height}} {{px.total_width}} {{px.total_height}} "{{{escaped_image}}}"; {{/items}}

Добавим специальные миксины в mixins.styl:

SpriteWidth($sprite) width $sprite spriteHeight($sprite) height $sprite spritePosition($sprite) background-position $sprite $sprite spriteImage($sprite) background-image url($sprite) sprite($sprite) if !match("hover", selector()) && !match("active", selector()) spriteImage($sprite) spritePosition($sprite) spriteWidth($sprite) spriteHeight($sprite)

Подключим миксины и генерируемый файл с координатами в основной файл стилей src/css/styles.styl:

@import "partial/sprite" @import "partial/mixins"

Обратите внимание что файлы спрайтов должны подключаться до пользовательских стилей @import "partial/styles"

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

npm i gulp-imagemin -D

Плагины обработки JavaScript

Минификация JS - плагин максимально минифицирует ваш JS код сокращая время его загрузки:

npm i gulp-uglify -D

Отслеживание ошибок JS - плагин досканально проверит ваш JS код на выявление всех несоответствий и выведет их в консоль:

npm i jshint gulp-jshint -D

Плагины обработки HTML

Подключаемые файлы - плагин позволяет хранить статичные части сайта, такие как header , footer , aside и т.д., в отдельных файлах и подключать их в любой части другого файла. Больше нет надобности, в случае мелких изменений в шапке, менять десятки, а то и сотни html страниц шаблона:

npm i gulp-rigger -D

Плагин так же совместим с JS.

Подключим пользовательский JS в основной файл JS src/js/main.js констуркцией:

//= _main.js

Подключим в index.html файлы header.html и footer.html

//= include/header.html //= include/footer.html

Прочие плагины

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

npm i gulp-connect -D

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

npm i gulp-plumber -D

Переименование файлов - самая обычная работа с именами файлов. Плагин позволяет полностью переименовывать файлы, изменять расширение, добавлять префиксы и постфиксы, например чтобы привести файл вида style.styl в style.min.css:

npm i gulp-rename -D

Клинер - иногда возникает потребность в полном очищении каталога build , тут нам на помощь приходит плагин:

Sourcemap - для того чтобы после минификации ваши файлы оставались читаемы через отладку браузером, нужно добавлять sourcemap в минифицируемые файлы:

npm i gulp-sourcemaps -D

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

npm i gulp-watch -D

Проверим package.json

После всех установленных плагинов, проверим наш package.json . Он должен иметь примерно следующий вид:

{ "name": "start", "version": "1.0.0", "description": "Start pack for Front-end develop", "author": "Ivan Ivanov", "license": "MIT", "dependencies": {}, "devDependencies": { "gulp": "latest", "gulp-autoprefixer": "latest", "gulp-connect": "latest", "gulp-imagemin": "latest", "jshint": "latest", "jshint-stylish": "latest", "gulp-jshint": "latest", "gulp-minify-css": "latest", "gulp-plumber": "latest", "gulp-rename": "latest", "gulp-rigger": "latest", "gulp-sourcemaps": "latest", "gulp-stylus": "latest", "gulp-uglify": "latest", "gulp-watch": "latest", "gulp.spritesmith": "latest", "rimraf": "latest" } }

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

В папке проекта также должен появиться каталог node_modules в котором хранятся все файлы плагинов ноды. Все нужные плагины установлены, можно переходить к настройки конфига Gulp.

Настройка gulpfile.js

gulpfile.js - это основной файл конфигурации нашего таск-менеджера, именно в нем мы будем хранить все настройки и команды.

Вся работа Gulp сводится к task (англ. задача). Задача - это отдельная самостоятельная функция с именем. Каждая задача может быть вызвана отдельно.

Режим совмстимости с современными стандартами

Во первых в начало файла подключим режим совместимости только по современным стандартам:

"use strict";

Подробнее про эту директиву можно .

Инициализируем плагин

Плагины инициализируются следующей конструкцией:

var initPlugin = require("plugin-name");

В соответствии с этой конструкцией инициализируем все наши плагины:

Var gulp = require("gulp"), //основной плагин gulp stylus = require("gulp-stylus"), //препроцессор stylus prefixer = require("gulp-autoprefixer"), //расставление автопрефиксов cssmin = require("gulp-minify-css"), //минификация css uglify = require("gulp-uglify"), //минификация js jshint = require("gulp-jshint"), //отслеживание ошибкок в js rigger = require("gulp-rigger"), //работа с инклюдами в html и js imagemin = require("gulp-imagemin"), //минимизация изображений spritesmith = require("gulp.spritesmith"), //объединение картинок в спрайты rimraf = require("rimraf"), //очистка sourcemaps = require("gulp-sourcemaps"), //sourcemaps rename = require("gulp-rename"), //переименвоание файлов plumber = require("gulp-plumber"), //предохранитель для остановки гальпа watch = require("gulp-watch"), //расширение возможностей watch connect = require("gulp-connect"); //livereload

Константы путей

Для удобства сразу определим все пути и маски:

Var path = { build: { //Тут мы укажем куда складывать готовые после сборки файлы html: "build/", js: "build/js/", css: "build/css/", img: "build/css/images/", fonts: "build/fonts/", htaccess: "build/", contentImg: "build/img/", sprites: "src/css/images/", spritesCss: "src/css/partial/" }, src: { //Пути откуда брать исходники html: "src/template/*.html", //Синтаксис src/template/*.html говорит gulp что мы хотим взять все файлы с расширением.html js: "src/js/[^_]*.js",//В стилях и скриптах нам понадобятся только main файлы jshint: "src/js/*.js", css: "src/css/styles.styl", cssVendor: "src/css/vendor/*.*", //Если мы хотим файлы библиотек отдельно хранить то раскоментить строчку img: "src/css/images/**/*.*", //Синтаксис img/**/*.* означает - взять все файлы всех расширений из папки и из вложенных каталогов fonts: "src/fonts/**/*.*", contentImg: "src/img/**/*.*", sprites: "src/css/sprites/*.png", htaccess: "src/.htaccess" }, watch: { //Тут мы укажем, за изменением каких файлов мы хотим наблюдать html: "src/template/**/*.html", js: "src/js/**/*.js", css: "src/css/**/*.*", img: "src/css/images/**/*.*", contentImg: "src/img/**/*.*", fonts: "src/fonts/**/*.*", htaccess: "src/.htaccess", sprites: "src/css/sprites/*.png" }, clean: "./build", //директории которые могут очищаться outputDir: "./build" //исходная корневая директория для запуска минисервера };

Обратите внимание, что мы можем использовать маски имен:

  • *.js - все файлы с расширением js
  • [^_]*.js - все файлы с расширением js, исключая те что начинаются с нижнего подчеркивания
  • *.* - любые файлы с любым расширением в пределах текущей дитректории
  • /**/*.html - все файлы с расширением.html в пределах текущей директории и всех дочерних директорий

Task (задачи)

Теперь, когда все константы прописаны, можно приступить к написанию тасков. Все задачи имеют следующую конструкцию:

Gulp.task("taskName", function(){ //some functions });

Мини-сервер и LiveReload

Первым делом мы настроим работу локального сервера и LiveReload:

// Локальный сервер для разработки gulp.task("connect", function(){ connect.server({ //настриваем конфиги сервера root: , //корневая директория запуска сервера port: 9999, //какой порт будем использовать livereload: true //инициализируем работу LiveReload }); });

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

Билд HTML

// таск для билдинга html gulp.task("html:build", function () { gulp.src(path.src.html) //Выберем файлы по нужному пути.pipe(rigger()) //Прогоним через rigger .pipe(gulp.dest(path.build.html)) //выгрузим их в папку build .pipe(connect.reload()) //И перезагрузим наш сервер для обновлений });

Билд JS

// проверка js на ошибки и вывод их в консоль gulp.task("jshint:build", function() { return gulp.src(path.src.jshint) //выберем файлы по нужному пути.pipe(jshint()) //прогоним через jshint .pipe(jshint.reporter("jshint-stylish")); //стилизуем вывод ошибок в консоль }); // билдинг яваскрипта gulp.task("js:build", function () { gulp.src(path.src.js) //Найдем наш main файл.pipe(rigger()) //Прогоним через rigger .pipe(sourcemaps.init()) //Инициализируем sourcemap .pipe(uglify()) //Сожмем наш js .pipe(sourcemaps.write()) //Пропишем карты.pipe(rename({suffix: ".min"})) //добавим суффикс.min к выходному файлу.pipe(gulp.dest(path.build.js)) //выгрузим готовый файл в build .pipe(connect.reload()) //И перезагрузим сервер });

Билд спрайтов

Все изображения для объединения в спрайты складываются в директорию src/css/sprites/ и после прогона через Gulp становятся единым спрайтовым изображением. В спрайты не стоит складывать логотипы и бэкграунды без четких размеров.

// билдим спрайты gulp.task("sprites:build", function () { var spriteData = gulp.src(path.src.sprites) //выберем откуда брать изображения для объединения в спрайт.pipe(spritesmith({ imgName: "sprite.png", //имя спрайтового изображения cssName: "sprite.styl", //имя стиля где храним позиции изображений в спрайте imgPath: "images/sprite.png", //путь где лежит спрайт cssFormat: "stylus", //формат в котором обрабатываем позиции cssTemplate: "stylus.template.mustache", //файл маски cssVarMap: function(sprite) { sprite.name = "s-" + sprite.name //имя каждого спрайта будет состоять из имени файла и конструкции "s-" в начале имени } })); spriteData.img.pipe(gulp.dest(path.build.sprites)); // путь, куда сохраняем картинку spriteData.css.pipe(gulp.dest(path.build.spritesCss)); // путь, куда сохраняем стили });

Для вывода спрайта достаточно воспользоваться миксином. Например, для файла lorem.png выборка из спрайта будет выглядеть следующим образом:

Lorem sprite($s-lorem)

Теперь объект с классом.lorem примет размеры изображения и само изображение в качестве фона.

Билд статичных изображений

Статичные изображения - это изображения используемые в шаблоне верстки.

// билдим статичные изображения gulp.task("image:build", function () { gulp.src(path.src.img) //Выберем наши картинки.pipe(imagemin({ //Сожмем их progressive: true, //сжатие.jpg svgoPlugins: [{removeViewBox: false}], //сжатие.svg interlaced: true, //сжатие.gif optimizationLevel: 3 //степень сжатия от 0 до 7 })) .pipe(gulp.dest(path.build.img)) //выгрузим в build .pipe(connect.reload()) //перезагрузим сервер });

Билд динамичных изображений

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

// билдим динамичные изображения gulp.task("imagescontent:build", function() { gulp.src(path.src.contentImg) .pipe(imagemin({ //Сожмем их progressive: true, //сжатие.jpg svgoPlugins: [{removeViewBox: false}], //сжатие.svg interlaced: true, //сжатие.gif optimizationLevel: 3 //степень сжатия от 0 до 7 })) .pipe(gulp.dest(path.build.contentImg)) //выгрузим в build .pipe(connect.reload()) //перезагрузим сервер });

Билдим CSS

// билдинг пользовательского css gulp.task("cssOwn:build", function () { gulp.src(path.src.css) //Выберем наш основной файл стилей.pipe(sourcemaps.init()) //инициализируем soucemap .pipe(stylus({ compress: true, "include css": true })) //Скомпилируем stylus .pipe(prefixer({ browser: ["last 3 version", "> 1%", "ie 8", "ie 7"] })) //Добавим вендорные префиксы.pipe(cssmin()) //Сожмем.pipe(sourcemaps.write()) //пропишем sourcemap .pipe(rename({suffix: ".min"})) //добавим суффикс.min к имени выходного файла.pipe(gulp.dest(path.build.css)) //вызгрузим в build .pipe(connect.reload()) //перезагрузим сервер });

Отдельный таск для внешних стилей:

// билдинг вендорного css gulp.task("cssVendor:build", function () { gulp.src(path.src.cssVendor) // Берем папку vendor .pipe(sourcemaps.init()) //инициализируем soucemap .pipe(cssmin()) //Сожмем.pipe(sourcemaps.write()) //пропишем sourcemap .pipe(gulp.dest(path.build.css)) //выгрузим в build .pipe(connect.reload()) //перезагрузим сервер });

Также добавим таск для билда общего CSS:

// билдим css целиком gulp.task("css:build", [ "cssOwn:build", // "cssVendor:build" ]);

В случае если требуется обработать внешние стили отдельно от домашних и выгрузить их отдельными файлами нужно раскомментировать строчку "cssVendor:build"

Билд шрифтов

// билдим шрифты gulp.task("fonts:build", function() { gulp.src(path.src.fonts) .pipe(gulp.dest(path.build.fonts)) //выгружаем в build });

Билд.htaccess

// билдим htaccess gulp.task("htaccess:build", function() { gulp.src(path.src.htaccess) .pipe(gulp.dest(path.build.htaccess)) //выгружаем в build });

Общий билд

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

// билдим все gulp.task("build", [ "html:build", "jshint:build", "js:build", "sprites:build", "css:build", "fonts:build", "htaccess:build", "image:build", "imagescontent:build" ]);

Очистка билда

Иногда требуется полностью очистить каталог build . Здесь нам на помощь придет следующий таск:

// чистим папку билда gulp.task("clean", function (cb) { rimraf(path.clean, cb); });

Watch или отслеживание изменений в реальном времени

Одной из самых важных и полезных функций Gulp является функция watch , которая позволяет отслеживать в реальном времени все изменения с производимыми файлами и в зависимости от этого выполнять конкретные действия:

// watch gulp.task("watch", function(){ //билдим html в случае изменения watch(, function(event, cb) { gulp.start("html:build"); }); //билдим спрайты в случае изменения watch(, function(event, cb) { gulp.start("sprites:build"); }); //билдим контекстные изрображения в случае изменения watch(, function(event, cb) { gulp.start("imagescontent:build"); }); //билдим css в случае изменения watch(, function(event, cb) { gulp.start("css:build"); }); //проверяем js в случае изменения watch(, ["jshint"]); //билдим js в случае изменения watch(, function(event, cb) { gulp.start("js:build"); }); //билдим статичные изображения в случае изменения watch(, function(event, cb) { gulp.start("image:build"); }); //билдим шрифты в случае изменения watch(, function(event, cb) { gulp.start("fonts:build"); }); //билдим htaccess в случае изменения watch(, function(event, cb) { gulp.start("htaccess:build"); }); });

Действия по-умолчанию

Действия по-умолчанию - это то какие задачи будет выполнять таск-менеджер при вводе команды gulp в консоль:

// действия по умолчанию gulp.task("default", ["build", "watch", "connect"]);

В нашем случае по-умончанию мы сбилдим наш проект, включим режим watch и запустим сервер.

Команды для командной строки

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

  • gulp - основная команда, запускает таск default
  • gulp build - билдим всё
  • gulp watch - запуск watch
  • gulp clean - очистка каталога build
  • gulp connect - запуск сервер
  • gulp html:build - билд HTML
  • gulp jshint:build - проверяем JS на ошибки
  • gulp js:build - билд JS
  • gulp sprites:build - билд спрайта
  • gulp image:build - билд статичных изображений
  • gulp imagecontent:build - билд динамичных изображений
  • gulp cssOwn:build - билд пользовательского CSS
  • gulp cssVendor:build - билд внешних CSS
  • gulp css:build - общий билд CSS
  • gulp fonts:build - билд шрифтов
  • gulp htaccess:build - билд.htaccess

На данном этапе настройка gulpfile.js закончена.

Копируем стартовый пакет в проект

Для начала зайдем через консоль в папке где мы ведем разработку, например cd develop/example и скопируем все из директории стартового пакета в наш проект cp -a ~/develop/start/. ~/develop/example/

Такой способ копирования наиболее удобен, т.к. точно скопирует все, в том числе и скрытые файлы.gitignore и пр.

Заключение

Используя данное руководство мы подготовили стартовый пакет для использования Gulp в своих проектах для Front-end разработки.

Так же данный пакет доступен на GitHub

Post Scriptum

Данная статья не является заключительной и будет обновляться в зависимости от изменений и улучшений.

В те времена, когда сайты были небольшими, необходимости в отдельной сборке фронтенда не было. Однако объем и сложность CSS и JS все увеличивались, и вид, в котором удобно разрабатывать, стал сильно отличаться от вида, в котором нужно показывать результат пользователю. Появились такие задачи, как конкатенация (склеивание) файлов, минимизация кода и даже предварительная компиляция. Результатом этого стали специализированные системы сборки фронтенда, о которых мы и расскажем.

Разумеется, как только необходимость в сборке стала ощутима, тут же на фронтенд начали переползать инструменты, использовавшиеся бэкендом. Основная их проблема и причина того, что в данный момент их все меньше используют для фронтенда, - они совершенно не заточены под его специфику, так как структура проекта, используемые технологии и цикл разработки очень сильно зависят от задач проекта и могут значительно отличаться. Тот же Ant, например, обладает многословным синтаксисом и не особо умеет делать нужные для фронтенда вещи: встроенных задач совсем немного, а расширяется он плохо. Если говорить о GNU make, то он гораздо более универсальный, поскольку оперирует shell-командами. Из недостатков нужно упомянуть об особом синтаксисе, который надо дополнительно изучать, необходимости хорошо знать shell, а также тенденции к быстрому усложнению Makefile при росте требований к сборке.

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

/libs/ jquery.min.js underscore.min.js /js/ common.js carousel.js popups.js ....

Система сборки обычно делает следующее:

  • конкатенирует все JS-файлы в один (в нужном порядке, мы же не хотим загрузить наши скрипты раньше, чем jQuery);
  • проверяет JS-код на валидность (например, с помощью JSHint);
  • минимизирует код, по необходимости его обфусцирует (то есть делает непонятным);
  • конкатенирует CSS-файлы (порядок тут тоже важен, так как свойства часто переопределяются);
  • минимизирует CSS;
  • складывает файлики в отдельную директорию, из которой ты и подключаешь их в своем HTML.

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

Все эти задачи, и даже больше, умеют решать современные инструменты сборки. Будем рассматривать самые популярные решения, которые работают на платформе Node.js. Общим их преимуществом является понятный язык, который знают (или считают, что знают) все фронтенд-разработчики, изначальная направленность на решение задач фронтенда, а также понятное Node.js окружение, в котором ты, может быть, уже разрабатываешь свое приложение.

Grunt

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

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

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

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

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

Gulp

Gulp - активно развивающаяся в данный момент система сборки. В основе ее архитектуры лежит использование потоков в Node.js, что позволяет не записывать на диск временные файлы и папки. Основные достоинства Gulp - скорость и краткость конфига. Причем если первое бесспорно, то краткость по сравнению с Grunt достигается просто за счет другой его структуры. Если в конфиге Grunt ты по отдельности оперируешь плагинами, настраивая каждый из них, то в конфиге Gulp нужно описывать процесс, который должен пройти каждый файл (или набор файлов), чтобы быть собранным. Вот жизненный пример компиляции SASS:

Gulp.task("styles", function() { return gulp.src("styles/*.scss") .pipe(sass({ style: "expanded" })) .pipe(rename({suffix: ".min"})) .pipe(minifycss()) .pipe(gulp.dest("build/styles/css")); });

В первой строчке мы регистрируем задачу для Gulp с именем styles . Затем последовательно описываем, что же нужно сделать с каждым из файлов, подходящим под маску styles/*.scss: компилируем SASS, добавляем.min к имени файла, минимизируем, кладем в конечную директорию. Если с этим файлом понадобится делать что-то еще, мы просто добавим соответствующую команду, например.pipe(добавить комментарий с ASCII-единорогом в начало файла) (надеюсь, для этой повседневной задачи сделают наконец плагин). Мне такой подход к написанию конфига нравится больше: он лучше описывает, что же реально происходит с твоими файлами.

Конечно, пока Gulp проигрывает Grunt по количеству плагинов, но для множества задач плагины есть. Скорее всего, тебе хватит существующих плагинов, а если будет чего-то очень не хватать, всегда можно написать свой (шутка). Кстати, есть пакет gulp-grunt, который позволяет запускать задачи Grunt из Gulp, если прям очень надо.

Если тебе нравится подобный подход к сборке, важна скорость и не нужно выполнять специфические задачи, плагин для которых есть только для Grunt, то Gulp может стать отличным выбором. На настоящий момент Gulp остается самым серьезным конкурентом Grunt.

Broccolli

Самый молодой из рассматриваемых инструментов сборки, находится сейчас, по сути, в стадии разработки. Разработчики Broccolli не скрывают, что вдохновлялись Gulp, однако считают некоторые концепции, лежащие в его основе, ошибочными. Например, они выбрали кеширование всех промежуточных результатов сборки (причем реализуемое каждым из плагинов) для ее ускорения вместо частичной пересборки только требуемых файлов. Также им не понравилось, что Gulp лучше всего оперирует с трансформацией одного файла в один итоговый, то есть один к одному. Чтобы улучшить выполнение операций вида «много к одному», в данный момент Gulp разрабатывает сложную схему с созданием виртуальной файловой системы, что разработчикам Broccolli кажется лишним усложнением и проявлением слабости изначальных концепций Gulp. Broccolli изначально оперирует понятиями деревьев вместо файлов и совершает только трансформации деревьев в другие деревья (пусть даже вырожденные и из одной вершины).

Очень разумный теоретический подход к проблеме сборки не снимает проблемы количества расширений к Broccolli. К сожалению, их около двух десятков и выполняют они только самые базовые задачи. Если хочется попробовать что-то новое, посмотри на Broccolli, он достаточно быстр, активно разрабатывается, но, наверное, для применения на серьезных проектах еще сыроват.

Brunch

Brunch создавался с той же задачей - уделать Grunt по всем фронтам, но подошел к ней совсем с другой стороны. Разработчики Brunch решили взять хорошим пониманием предметной области, то есть сделать менее универсальный инструмент, который будет заточен именно под задачи фронтенда, например без всяких настроек понимать, что *.js - это файл со скриптами, *.coffee - CoffeeScript и так далее. Brunch довольно быстрый, гораздо быстрее Grunt, но чуть медленнее Gulp. К безусловным достоинствам Brunch стоит отнести также действительно компактный конфиг, меньше, чем у Grunt и Gulp, в разы. Вот, например, простой конфиг Brunch:

Exports.config = files: javascripts: joinTo: "javascripts/app.js": /^app/ "javascripts/vendor.js": /^(bower_components|vendor)/ stylesheets: joinTo: "stylesheets/app.css" order: after: ["vendor/styles/helpers.css"] templates: joinTo: "javascripts/app.js"

Заметь, что конфиг можно писать как на CoffeeScript (как в данном случае), так и на обычном JS. Мы создаем обычный модуль, который возвращает JSON с настройками сборки.

Обрати внимание на ключи joinTo и order. Это и есть знание предметной области, о котором я упоминал, - на уровне конфига Brunch знает, что ты, скорее всего, захочешь склеить файлы, причем некоторые раньше остальных. Именно это позволяет заменить 400 строк конфига Grunt на 20–30 строчек Brunch.

Плюс к этому экосистема Brunch гораздо меньше, чем у Grunt и даже чем у Gulp. Плагинов около 50 (сравни с 450+ у Gulp, например), развитие идет не очень быстро, в общем, здесь все довольно печально.

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

ENB

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

Вкратце, в чем суть. В ENB мы оперируем понятием цели, то есть конечного файла, который нужно собрать, или ноды (папки, в общем случае страницы), для которой нужно собрать некоторый набор файлов. Для того чтобы собрать целевой файл, мы используем некоторое количество технологий (грубо говоря, плагинов в терминах Grunt, хотя технологии меньше по размерам и более специализированы). Первым делом ENB определяет исходный набор файлов, которые нужны для сборки целей (этим занимаются несколько базовых технологий, по умолчанию работающие с методологией BEM, то есть они ищут *.bemdecl -файл, в котором прописаны зависимости данной ноды от разных блоков), полностью разворачивает это дерево зависимостей (когда блок, от которого зависит твоя страница, сам зависит от другого, подключаются оба в нужном порядке), а затем находит файлы, необходимые для каждой зарегистрированной технологии. Затем ENB придерживается описанной в конфиге последовательности трансформаций файлов (тут можно проследить некоторую аналогию с Gulp). Несмотря на некоторые отличия от стандартного подхода систем сборки, разобравшись с основными понятиями, дальше работать с ENB довольно легко.

Основные преимущества ENB: скорость сборки, за счет гибкой системы кеширования и возможности обмениваться промежуточными данными между разными технологиями, параллелизации независимых процессов, и выделении самых тяжелых технологий в отдельные субпроцессы. К ENB необычайно просто писать новые технологии, если тебя чем-то не устраивает поведение стандартных.

К недостаткам можно отнести то, что конфиг ENB достаточно многословный, так как есть возможность управлять абсолютно всеми этапами сборки. Плюс ENB все-таки писался для BEM-методологии, и прикрутить его к проекту с совершенно другой структурой потребует лишних телодвижений. Для ENB не так много написанных технологий (порядка 60), но с большинством задач BEM-проектов он справляется на ура.

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

Данный мануал содержит описание полезных и наиболее часто используемых front-end инструментов. Вы сможете узнать процесс установки инструментов и основные моменты работы с ними.

NPM

Введение

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

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

Добавление сторонних библиотек с использованием пакетного менеджера заменяется парой команд в терминале.

Одним из пакетных менеджеров используемых во frontend-проектах является NPM.

npm (Node.js Package Manager) - менеджер пакетов, входящий в состав Node.js. Используется для скачивания пакетов из облачного сервера npm, либо для загрузки пакетов на этот сервер.

Официальный сайт

Начало работы

Для установки npm необходимо скачать и установить NodeJS (npm будет автоматически установлен): https://nodejs.org/en/.

Использование

Установка пакетов

Пакетом называется один или несколько JavaScript-файлов, представляющих собой какую-то библиотеку или инструмент. Для установки пакета с помощью npm необходимо выполнить следующую команду:

Npm install <название пакета>

Для глобальной установки пакета используется ключ -g. После установки пакет вместе с исходниками находится в директории node_modules/.

Проверка версии

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

Настройка файла конфигурации

Файл package.json содержит в себе информацию о вашем приложении: название, версия, зависимости и тому подобное. Любая директория, в которой есть этот файл, интерпретируется как Node.js-пакет.

Для создания файла package.json необходимо выполнить команду:

Npm init

После этого необходимо будет заполнить некоторую информацию о проекте.

В данном файле будут храниться наименования и версии всех пакетов необходимых в проекте. С помощью команды npm install можно подгрузить все пакеты, которые находятся в package.json .

Для установки некоторого пакета и автоматическим сохранением в файле package.json используется команда:

Npm install<название пакета> --save-dev

Альтернативы

Yarn

Особенности

  • Создание веб-сервера и автоматическая перезагрузка страницы в браузере при сохранении кода, слежение за изменениями в файлах проекта.
  • Использование различных JavaScript, CSS и HTML препроцессоров (CoffeeScript, Less, Sass, Stylus, Jade и т.д.).
  • Минификация CSS и JS кода, а также, оптимизация и конкатенация отдельных файлов проекта в один.
  • Автоматическое создание вендорных префиксов (приставок к названию CSS свойства, которые добавляют производители браузеров для нестандартных свойств) для CSS.
  • Управление файлами и папками в рамках проекта – создание, удаление, переименование.
  • Запуск и контроль выполнения внешних команд операционной системы.
    Работа с изображениями – сжатие, создание спрайтов, изменение размеров (png, jpg, svg и др.).
  • Деплой (отправка на внешний сервер) проекта по FTP, SFTP и т. д.
    Подключение и использование в проекте безгранично большого количества Node.js и Gulp утилит, программ и плагинов.
  • Создание различных карт проекта и автоматизация другого ручного труда.

Начало работы

В системе должны быть установлены NodeJS и npm.

Шаг 1: Для глобальной установки GulpJS с помощью пакетного менеджера npm необходимо выполнить команду:

Npm install gulp -g

Шаг 2: Необходимо установить его для приложения:

Npm install --save-dev gulp

Подгрузка дополнительных плагинов, которые могут быть использованы при сборке проекта, также осуществляется c помощью npm следующей командой:

Npm install <название плагина> --save-dev

Все установленные плагины находятся в директории node_modules/ .

Использование

Шаг 1: Сперва нужно подключить gulp к проекту. Для этого в файле gulpfile.js прописываем строчку:

Var gulp = require("gulp");

Функция require() позволяет подключать плагины из папки node_modules/.

Шаг 2: С помощью переменной gulp можно создавать таски для сборки проекта:

Gulp.task("exampleTask", function() {});

Метод task принимает два параметра: название и функцию с телом таски.
Данную инструкцию уже можно выполнить. Для этого в консоли прописываем:

Gulp exampleTask

Основные команды

Ниже представлен более сложный пример инструкции:

Gulp.task("exampleTask", function () { return gulp.src("source-files") .pipe(plugin()) .pipe(gulp.dest("folder")); });

Разберем команды использованные в данном примере:

  • gulp.src – выборка исходных файлов проекта для обработки плагином;
  • .pipe(plugin()) – вызов Gulp плагина для обработки файла;
  • .pipe(gulp.dest(‘folder’)) – вывод результирующего файла в папку назначения.

Маски файлов

Функция gulp.src принимает в качестве параметра маску файлов. Примеры масок:

  • ./ – текущая директория;
  • ../ – родительская директория;
  • js/index.js – файл index.js в папке js;
  • js/*.js – все файлы с расширением js в папке js;
  • js/**/*.js – все файлы с расширением js в папке js и в ее подкаталогах;
  • !js/*.js – исключение файлов с расширением js в папке js.

Потоки

Использование потоков является одним из важнейших преимуществ GulpJS.

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

Функция gulp.src() создает поток объектов, представляющих файлы, которые переданы ей в качестве параметра. Далее с помощью функций pipe выстраивается конвейер, по которому передается поток объектов. Данной функции в качестве параметра передается плагин, который каким-либо способом обрабатывает поток объектов.

Ниже представлен пример использования потоков. В данном примере использованы сторонние плагины gulp-jshint и gulp-concat, которые необходимо установить и подключить в gulpfile.js.

Функция gulp.src берет файлы по маске js/*.js . Запускает JSHint и выводит результат. Затем производит конкатенацию файлов и в конце сохраняет полученный файл после конкатенации в директории dist/ .

Gulp.task("example", function () { return gulp.src("js/*.js") .pipe(jshint()) .pipe(concat("index.js")) .pipe(gulp.dest("dist")); });

Сторонние плагины

Рассмотрим пример использования сторонних плагинов. Для этого создадим инструкцию конкатенации файлов js:

Шаг 1: Сперва необходимо подключить плагин командой require:

Var concat = require("gulp-concat");

Шаг 2: Затем нужно создать таск для конкатенации файлов с расширением js находящихся в директории js/ . В конце получившийся файл помещается в директорию dist/js:

Gulp.task("concat", function () { return gulp.src("js/*.js") .pipe(concat("index.js")) .pipe(gulp.dest("dist/js")); });

Gulp concat

Дополнительная информация

Также можно определить таск, который будет вызывать выполнение других тасков.

Gulp.task("build", ["html", "css"]);

Кроме этого существует метод watch для наблюдения изменений в файлах:

Gulp.watch("маска файлов для наблюдения", ["название таски, которая выполнится при изменении файлов"]);

В gulpfile.js можно создать дефолтный таск:

Gulp.task("default", ["task1", "task2"]);

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

Основные плагины

  • gulp-autoprefixer – автоматически расставляет префиксы к CSS свойствам;
  • gulp-browser-sync – создает подключение, после чего производит автообновление страницы при изменении клиентских или даже серверных файлов;
  • gulp-uncss – оптимизация CSS файлов. Плагин анализирует HTML код и находит все неиспользуемые и продублированные стили;
  • gulp-csso – CSS минификатор;
  • gulp-htmlmin – простой HTML минификатор;
  • gulp-htmlhint – HTML валидатор;
  • gulp-uglify – JavaScript минификатор;
  • gulp-concat – конкатенация файлов;
  • gulp-webserver – позволяет создать и запустить сервер;
  • gulp-jshint – проверка качества JS кода;
  • gulp-jasmine – запуск jasmine тестов;
  • gulp-jsdoc – генерация JSDoc документации.

С полным списком Gulp-плагинов вы можете ознакомиться по ссылке:
http://gulpjs.com/plugins/

Альтернативы

GruntJS

Особенности

  • Поддержка асинхронного тестирования.
  • Возможность выставлять наблюдателей (observer) на разные объекты.

Начало работы

Для подключения Jasmine к своему проекту необходимо скачать библиотеку и подключить следующие файлы на главную HTML-страницу:

  • lib/jasmine-*/jasmine.js - сам фреймворк;
  • lib/jasmine-*/jasmine-html.js - оформление результатов в виде HTML;
  • lib/jasmine-*/jasmine.css - внешний вид результата выполнения тестов;
  • SpecRunner.html - файл, который следует открыть в браузере для запуска тестов.

Синхронизация с инструментами

GulpJS

Jasmine можно подключить в сборку проекта на GulpJS:

Шаг 1: Вначале необходимо установить плагин gulp-jasmine:

Npm install gulp-jasmine --save-dev

Шаг 2: Затем нужно подключить плагин в файле сборки и создать таску запуска тестов:

Var jasmine = require("gulp-jasmine"); gulp.task("jasmine", function() { gulp.src("файлы тестов") .pipe(jasmine()); });

KarmaJS

(в конце статьи подробнее описана работа с этим инструментом)

Для подключения Jasmine в KarmaJS необходимо:

Шаг 1: Установить KarmaJS:

Npm install -g karma-cli

Шаг 2: Установить плагины необходимые для запуска тестов написанных на Jasmine в браузерах Chrome и PhantomJS:

Npm install karma-jasmine karma-chrome-launcher karma-phantomjs-launcher

Шаг 3: Установить сам Jasmine:

Npm install -g jasmine

Шаг 4: В файле конфигурации karma подключить плагины и прописать путь к тестам.

Использование

Ключевые слова

  • describe – определение набора тестов;
  • it – определение теста;
  • expect – определение ожиданий, которые проверяются в тесте.

Функции describe и it принимают два параметра: первый – название, второй – функция с кодом.

Пример базового теста

describe(“название набора тестов”, function () { it(“название теста”, function () { expect(2+2).toBe(4); }); });

Методы проверки результатов

  • expect().toBe() – проверка переменных на равенство (‘===’);
  • expect().not.toBe() – проверка переменных на равенство (‘!==’);
  • expect().toEqual() – проверка на равенство переменных и объектов, включая содержимое;
  • expect().toBeDefined() – проверка на существование;
  • expect().toBeUndefined() – проверка на несуществование;
  • expect().toBeNull() – проверка значения переменной на null;
  • expect().toBeTruthy() – проверка на истинность;
  • expect().toBeFalsy() – проверка на ложность;
  • expect().toBeLessThan() – проверка на то, что значение должно быть меньше чем;
  • expect().toBeGreaterThan() – проверка на то, что значение должно быть больше чем;
  • expect().toBeCloseTo() – проверка на то, что значение должно быть близко к числу;
  • expect().toMatch() – проверка на соответствие регулярному выражению;
  • expect().toContain() – проверка на содержание в массиве;
  • expect().toThrow() – проверка вызова исключения;
  • expect().toHaveBeenCalled() – проверка вызова функции.

Дополнительные функции

Во избежание копирования какой-либо логики, используемой в тестах, используются функции beforeEach/afterEach . Они запускаются соответственно перед/после каждого теста.

Для тестирования асинхронных вызовов используются функции runs и waitsFor .

  • runs – принимает асинхронную функцию для выполнения;
  • waitsFor – принимает три параметра: первый – функция, которая должна вернуть true, если асинхронный вызов в функции runs был выполнен, второй – сообщение об ошибке, третий – ожидание в миллисекундах.
describe(“пример тестирования асинхронного вызова”, function () { var result = 0; function asyncFunc() { setTimeout(function() { result = result + 2; }, 500); } it(“название теста”, function () { runs(function () { asyncFunc(); }); waitsFor(function() { return result === 2; }, “значение не изменилось”, 2000); }); });

Наблюдатели

Возможность отслеживания вызова функций производится с помощью spyOn . Данная функция принимает два параметра: первый – объект, для которого осуществляется вызов функции, второй – имя функции, которую необходимо отслеживать.

If(“проверка вызова функции”, function () { spyOn(exampleObject, "exampleFunction"); exampleObject.exampleFunction(); expect(exampleObject.exampleFunction).toHaveBeenCalled(); });

При тестировании с использованием spyOn можно отслеживать количество вызовов, их параметры и каждый вызов в отдельности.

Для создания функции без реализации можно воспользоваться createSpy . Функция createSpy принимает имя функции для идентификации.

If(“проверка вызова функции”, function () { var example = createSpy("EXAMPLE"); example(“param1”, “param2”); expect(example.identify).toHaveBeenCalledWith(“param1”, “param2”); expect(example.calls.length).toEqual(1); });

Создание объекта заглушки осуществляется с помощью createSpyObj . В качестве параметров createSpyObj принимает имя объекта и массив строк, являющийся списком методов объекта заглушки.

Альтернативы

Mocha

Использование

Генерация документации производится из комментариев исходного кода.

Таск-раннеры и системы сборки сильно ускоряют работу, автоматизируя компиляцию, тестирование и другие рутинные задачи. Как и в любой другой области, на этом рынке существует сильная конкуренция. До 2014 года среди них главенствовал таск-раннер grunt, но позже из состава проекта выделилась небольшая команда, которая решила делать альтернативный инструмент, gulp, ориентированный на сборку проекта.

Чтобы помочь вам определиться с выбором, в рамках статьи рассмотрим основные таск-менеджеры:

  • grunt

а также коснемся других средств и способов сборки.

Забегая немного вперед, скажем, что мы в WaveAccess пользуемся именно gulp. Внедрить инструмент оказалось очень легко: у семейства продуктов JetBrains (IDEA, WebStorm, ReSharper), которые мы используем уже много лет, есть отличные плагины для работы с gulp/grunt и npm/nodejs.

Таск-менеджер vs. система сборки проекта: в чем разница?

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

  • Задачи для деплоя (zip проекта, загрузка проекта на удаленный сервер и тп)
  • Задачи по сборке проекта (минификация, оптимизация, проверка кода на валидность и тп)
  • Задачи для миграции данных и т.д.

Примеры таких инструментов - grunt и gulp.

Система сборки - это инструмент, который решает только одну типовую задачу сборки проекта на java script, в которую входят:

  • конкатенация,
  • проверка кода на валидность,
  • минификация кода, и тд.

К подобным инструментам относятся Webpack, Broccoli, Brunch, Browserify и другие.

Все подобные frontend-задачи можно автоматически выполнять при помощи других средств: к примеру, с помощью npm run, о котором мы также поговорим в статье.

Пример

Рассмотрим gulp-файл для сборки проекта:

Const gulp = require (‘gulp’); const coffee = require (‘gulp-coffee’); const concat = require (‘gulp-concat’); const uglify = require (‘gulp-uglify’); const imagemin = require (‘gulp-imagemin’); const sourcemaps = require (‘gulp-sourcemaps’); const del = require (‘del’); }

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

Var gulp = require("gulp"); var zip = require("gulp-zip"); var del = require("del"); var install = require("gulp-install"); var runSequence = require("run-sequence"); var awsLambda = require("node-aws-lambda"); gulp.task("clean", function(cb) { del(["./dist", "./dist.zip"], cb); }); gulp.task("copy", function() { return gulp.src("index.js") .pipe(gulp.dest("dist/")); }); gulp.task("node-mods", function() { return gulp.src("./package.json") .pipe(gulp.dest("dist/")) .pipe(install({production: true})); }); // Clean up all aws-sdk directories from node_modules. We don"t // need to upload them since the Lambda instance will already // have it available globally. gulp.task("clean-aws-sdk", function(callback) { del(["dist/node_modules/**/aws-sdk"], callback); }); gulp.task("zip", function() { return gulp.src(["dist/**/*", "!dist/package.json"]) .pipe(zip("dist.zip")) .pipe(gulp.dest("./")); }); gulp.task("upload", function(callback) { awsLambda.deploy("./dist.zip", require("./lambda-config.js"), callback); }); gulp.task("deploy", function(callback) { return runSequence(["clean"], ["copy"], ["node-mods"], ["clean-aws-sdk"], ["zip"], ["upload"], callback); });

A можно описывать новые задачи как комбинации уже существующих:

Gulp.task(‘deploy’, gulp.series (‘clean’, ‘copy’, ‘node-mods’, ‘clean-aws-sdk’, ‘zip’, ‘upload’));

В этом и заключается отличие. Теперь рассмотрим основные инструменты.

gulp vs. grunt

Итак, перед нами два таск-раннера: gulp и grunt. Оба используют node.js и npm, а задачи им ставят, используя javascript.

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

Потоковая передача данных

Перед нами грант-файл, который осуществляет сборку и обработку CSS.

Из него видно, что grunt при запуске каждого процесса:

    открывает файл;

    запускает процесс;

    сохраняет изменения;

    закрывает обработанный файл, чтобы предотвратить вмешательство в него следующего процесса;

    записывает файл в итоговую папку.

То есть, цепочка включает в себя создание нескольких временных папок и сохранение промежуточных файлов:

Плагины пишут разные авторы. Чтобы каждый плагин мог работать с файлами, обходя сохранение, файлы нужно представить в виде объектов. В gulp эту задачу выполняет виртуальная файловая система Vynyl-FS. И gulp сразу передает файл следующему процессу без создания временных файлов и без сохранения на диск.

Та же самая конфигурация для gulp уже компактнее:

Его общий механизм работы - потоковая обработка файлов без записи на диск:

Последовательность выполнения задач

Есть и другое отличие: gulp по умолчанию асинхронно выполняет таски. В этом есть и плюсы, и минусы. В том же конфигурационном файле мы даем команду считать файлы из директории dev/*scss и отправить их в SASS.

Потоки отправляют результат в.pipe. Метод.pipe позволяет собирать результат в буфер по частям, а когда он заполнен, сразу отправлять информацию в поток для чтения, еще не закончив получать содержимое директории.

Последовательное выполнение задач делает gulp быстрым и мощным, но изредка возникает необходимость все же выполнить задачи синхронно, как в grunt. Проблему можно решить через обратный вызов, возвращение потока или Promise . Более подробно задача разобрана на Хабре . Есть и альтернативный вариант на самом сайте npm.js

Если вы пользуетесь grunt, но вас привлекает потоковая передача данных -- тот же модуль Vynyl-FS можно использовать для ее реализации в grunt.

Лаконичное API gulp имеет всего 5 методов:

    Task(name, fn). Регистрирует функцию с именем. Можно указать зависимость от других тасков, если нужно их выполнить сначала.

    Run(tasks...). Выполняет задачи.

    Watch(glob, fn). Выполняет функцию, если файл на месте glob меняется.

    Src(glob). В качестве параметра принимает маску файлов и возвращает поток, представляющий эти файлы. Затем поток может быть передан на вход плагинам.

    Dest(folder). Сохраняет файлы в указанную папку.

Особенно хотелось бы отметить наличие.watch() в “родном” API проекта, ведь слежение за постоянными изменениями файлов является важнейшей составляющей сборки. Краткость API дает возможность этому таск-менеджеру сфокусироваться на своей основной задаче – сборке проектов.

Альтернативы gulp и grunt

Несмотря на популярность gulp (больше 130 к скачиваний в день) и grunt (более 86 к скачиваний в день согласно npmjs.com), разработчики видят в этих системах и свои недостатки: к примеру, зависимость от плагинов, неполная документация, неудобный дебаггинг. В качестве альтернативы можно рассмотреть системы сборки проектов (такие как Broccoli и Webpack) или npm-скрипты.

Системы сборки проектов

Рассмотрим несколько альтернативных решений на платформе Node.js. Для сборки проекта они могут заменить gulp и grunt.

Эта система, как и gulp, возникла как конкурент таск-раннеру grunt, однако разработчики изначально задумывали ее именно как помощник для сборки со всеми преимуществами и недостатками. Он без настроек “поймет”, что *.js - это файл со скриптами, *.coffee - это CoffeeScript; его конфиг более компактен. Однако никаких произвольных действий на этапе сборки он совершить не сможет.

Вот конфиг-файл Brunch. Он написан на CoffeeScript (можно также писать на JS):

Exports.config = files: javascripts: joinTo: "javascripts/app.js": /^app/ "javascripts/vendor.js": /^(bower_components|vendor)/ stylesheets: joinTo: "stylesheets/app.css" order: after: ["vendor/styles/helpers.css"] templates: joinTo: "javascripts/app.js"

Здесь хочется обратить внимание на операторы joinTo и order. На уровне конфига Brunch понимает, что придется собирать файлы в нужном порядке. В результате, конфиг занимает 20-30 строк.

Broccoli

Инди-инструмент, который находится на стадии разработки. Его разработчики хотели создать конкуренцию уже gulp.

По сравнению с gulp, инструмент Broccoli использует другие принципы:

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

    Деревья вместо файлов. Gulp лучше всего трансформирует один файл в один итоговый. Broccolli по умолчанию использует только деревья, а не файлы, и их трансформирует в другие деревья (вырожденные из одной вершины).

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

Webpack - гибкая модульная система сборки. Она обладает непривычным синтаксисом, но сама воспринимает любые синтаксисы модулей.

Понимая, что придется конкурировать с такими гигантами как gulp, создатели решили облегчить нам жизнь при разработке больших проектов. И добавили в утилиту:

    Умение автоматически строить дерево зависимостей и ресурсов.

    Удобные средства для реализации динамической подгрузки.

    Совместимость с практически любыми модулями (AMD, UMD, ES 2015, Common JS, сторонние модули на их основе).

    Совместимость с препроцессорами (SASS, Babel, Coffee Script, Type Script и т.д.).

    Live Reload (технологию асинхронной загрузки, при которой браузер обновляет не страницы целиком, а отдельные приложения).

    Возможность делить код и генерировать множество bundle-файлов, избегая создания одного тяжелого bundle.js.

    Умение оптимизировать код.

Отдельно можно отметить гибкий подход к зависимостям. Модулем может стать JS, CSS и HTML-файл, и даже JPEG с PNG. Можно использовать require(“myJSfile.js”) и require(“myCSSfile.css”), делить и использовать части артефакта повторно.

Подробнее о возможностях, конфигурации инструмента, плагинах можно найти на Github, в презентации с Fronttalks: глубокое погружение в Webpack .

npm скрипты

Задачи по сборке можно решить и при помощи npm-скриптов. Многих отпугивает эта идея: мало возможностей, скрипты недостаточно быстрые в сравнении с gulp или Webpack. Тем не менее, эти недостатки преувеличены.

Возможности npm-скриптов

Npm-скрипты решают довольно много задач. Так, например, можно реализовать скрипты ловушек:

{ "name": "npm-scripts-example", "version": "1.0.0", "description": "npm scripts example", "scripts": { "prebuild": "echo I run before the build script", "build": "cross-env NODE_ENV=production webpack" "postbuild": "echo I run after the build script" } }

Сценарии будут загружаться по порядку согласно префиксам: prebuild, например, стартует перед build, потому что у него есть префикс pre. Соответственно, postbuild будет загружен последним. Команда npm run build запустит их в нужном порядке.

Можно вызывать один скрипт из другого, чтобы декомпозировать сложные задачи. Например, здесь задача prebuild вызывает задачу clean.

{ "name": "npm-scripts-example", "version": "1.0.0", "description": "npm scripts example", "scripts": { "clean": "rimraf ./dist && mkdir dist", "prebuild": "npm run clean", "build": "cross-env NODE_ENV=production webpack" } }

Если задача становится слишком сложной, всегда можно вызвать отдельный файл:

{ "name": "npm-scripts-example", "version": "1.0.0", "description": "npm scripts example", "scripts": { "build": "node build.js" } }

За счет стриминга gulp для задач сборки стал гораздо удобнее, чем grunt. Но его можно реализовать и через npm. В Windows и Unix стриминг делается по умолчанию, без сохранения промежуточных файлов.

К примеру, в Unix можно сделать grep содержимого файла и направить его в новый файл:

Grep ‘My Name’ bigFile.txt > linesThatHaveMyName.txt

Редирект(>)направляет нужные строки в конечный файл. Задача выполняется без сохранения промежуточных файлов.

Но есть и неудобства: нельзя оставлять комментарии в package.json. Выходом может стать создание коротких скриптов с понятными названиями, нацеленных на какую-то одну небольшую задачу. Более подробно вопрос замены таск-раннеров npm-скриптами хорошо освещен в англоязычной статье Why I Left Gulp and Grunt for npm Scripts.

Итог

На рынке существует большая конкуренция инструментов для автоматизации рутинных задач (например, gulp и grunt), а также инструментов для автоматизации сборки проекта (Webpack, Broccoli, Medusa, Browserify и т.д.).

Если смотреть на таск-раннеры, то gulp по сравнению с grunt более прост, понятен и производителен: выигрывает за счет экономии на дисковых операциях. Но у grunt больше плагинов (например, есть плагин для тестирования). Из-за этого у него остается много поклонников.

Если же говорить только о сборке, то у gulp есть все преимущества перед grunt:

    Поточная архитектура для передачи файлов по цепочке, которую обеспечивает модуль Vynyl-FS.

    По умолчанию - асинхронное выполнение задач.

    Лаконичное API всего из 5 функций.

В то же время, для сборки Webpack является не менее интересным инструментом. В нем предусмотрена технология Live Reload, ускоряющая обновление браузера. Это огромный плюс: технология экономит время на нажатие кнопки обновления, которую разработчикам приходится нажимать постоянно. В gulp также есть Live Reload, но Webpack сложно сравнивать с gulp или grunt, так как он “заточен” только под билд и не “умеет” решать произвольные задачи.

Все эти решения прекрасно интегрируются с семейством продуктов от JetBrains, однако мы в WaveAccess предпочли именно grunt за широкие возможности и для верстальщиков, и для frontend-специалистов.

Если у Вас возникли вопросы и вам необходима разработка web-проекта, пишите нам на [email protected]

  • Frontend
  • Grunt
  • Gulp
  • Task runners