Приятная сборка frontend проекта

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

Мы будем использовать сборщик Gulp. Соответственно у вас в системе должен быть установлен Node js. Установку ноды под конкретную платформу мы рассматривать не будем, т.к. это гуглится за пару минут.И для начала отвечу на вопрос — почему Gulp? Из более или менее сносных альтернатив мы имеем Grunt и Brunch.Когда я только начал приобщаться к сборщикам — на рынке уже были и Grunt и Gulp. Первый появился раньше и по этому имеет более большое коммьюнити и разнообразие плагинов. По данным с npm: Grunt — 11171 пакетGulp — 4371 пакет

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

Приступим: Создадим папку под наш проект, например «habr». Откроем ее в консоли и выполним команду npm init Можно просто нажать Enter на все вопросы установщика, т.к. сейчас это не принципиально.В итоге в папке с проектом у нас сгенерируется файл package.json, примерно такого содержания { «name»: «habr», «version»:»1.0.0», «description»:», «main»: «index.js», «scripts»: { «test»: «echo \«Error: no test specified\» && exit 1» }, «author»:», «license»: «ISC» } Немного видоизменим его под наши нужды: { «name»: «habr», «version»:»1.0.0», «description»:», «author»:», «license»: «ISC», «devDependencies»: { «gulp»:»^3.8.1» } } в блоке devDependencies мы указали что нам нужен gulp и тут же будем прописывать все наши плагины.Плагины: gulp-autoprefixer — автоматически добавляет вендорные префиксы к CSS свойствам (пару лет назад я бы убил за такую тулзу :))gulp-cssmin — нужен для сжатия CSS кодаgulp-connect — с помощью этого плагина мы можем легко развернуть локальный dev сервер с блэкджеком и livereloadgulp-imagemin — для сжатия картинокimagemin-pngquant — дополнения к предыдущему плагину, для работы с PNGgulp-uglify — будет сжимать наш JSgulp-sass — для компиляции нашего SCSS кодаНе холивара ради Я очень долгое время использовал в своей работе LESS. Мне очень импонировал этот препроцессор за его скорость и простоту изучения. Даже делал доклад по нему на одном Ростовском хакатоне. И в частности в этом докладе я не очень лестно отзывался о SASS.Но прошло время, я стал старше и мудрее:) и теперь я приобщился к этому препроцессору.Основой моего недовольства SASS — было то что я не пишу на руби. И когда то для компиляции SASS/SCSS кода — надо было тащить в проект руби, с нужными бандлами — что меня очень огорчало.Но все изменилось с появлением такой штуки как LibSass. Это С/C++ порт компилятора для SASS. Плагин gulp-sass использует именно его. Теперь мы можем использовать SASS в нативном node окружении — что меня безгранично радует. gulp-sourcemaps — возьмем для генерации css sourscemaps, которые будут помогать нам при отладке кодаgulp-rigger — это просто киллер фича. Плагин позволяет импортировать один файл в другой простой конструкцией //= footer.html и эта строка при компиляции будет заменена на содержимое файла footer.htmlgulp-watch — Будет нужен для наблюдения за изменениями файлов. Знаю что в Gulp есть встроенный watch, но у меня возникли с ним некоторые проблемы, в частности он не видел вновь созданные файлы, и приходилось его перезапускать. Этот плагин решил проблему (надеюсь в следующих версиях gulp это поправят).opn — маленькая приятность, позволяющая открыть какую-нибудь ссылку в браузере командой из node jsrimraf — rm -rf для нодыПропишем все наши плагины в package.json

{ «name»: «habr», «version»:»1.0.0», «description»:», «author»:», «license»: «ISC», «devDependencies»: { «gulp»:»^3.8.1», «gulp-autoprefixer»:»*», «gulp-connect»:»*», «gulp-cssmin»:»*», «gulp-imagemin»:»*», «gulp-sass»:»*», «gulp-sourcemaps»:»*», «gulp-rigger»:»*», «gulp-uglify»:»*», «gulp-watch»:»*», «imagemin-pngquant»:»*», «opn»:»*», «rimraf»:»*» } } и запустим в консоли команду

npm install Bower Я уже не мыслю своей работы без пакетного менеджера Bower и надеюсь вы тоже. Если нет, то почитать о том что это и с чем его едят можно тут.Давайте добавим его к нашему проекту. Для этого выполним в консоли команду bower init Можно так же Enter на все вопросы.В конце мы получаем примерно такой файл bower.json { «name»: «habr», «version»:»0.0.0», «authors»: [ «Insayt » ], «license»: «MIT», «ignore»: [ »**/.*», «node_modules», «bower_components», «test», «tests» ] } И модифицируем его до нужного нам состояния { «name»: «habr», «version»:»0.0.0», «authors»: [ «Insayt » ], «license»: «MIT», «ignore»: [ »**/.*», «node_modules», «bower_components», «test», «tests» ], «dependencies»: { «normalize.css»:»*», «jquery»:»2.*» } } В блоке dependencies мы будем указывать зависимости нашего проекта. Сейчас просто для теста это normalize и jQuery (хотя я уже не помню когда начинал проект без этих вещей).Ну и конечно установим их командой bower i Ну, а теперь самое интересное. Создадим структуру нашего проекта и настроим сборщик.Структура проекта: Это очень спорный момент. Конечно проекты бывают разные, так же как и предпочтения разработчиков. Стоит только взглянуть на сайт yeoman.io (кстати это очень классный инструмент, который предоставляет большое кол-во заготовленных основ для проекта со всякими плюшками. Однозначно стоит присмотреться к нему). Мы не будем ничего выдумывать и сделаем самую простую структуру.Для начала нам понадобится 2 папки. Одна (src) в которой мы собственно будем писать код, и вторая (build), в которую сборщик будет выплевывать готовые файлы. Добавим их в проект. Текущая структура у нас выглядит так: 4ca1b40e6a5e4c7c9035b1a6884e6c91.png

В папке src создадим типичную структуру среднестатистического проекта. Сделаем main файлы в папках js/ и style/ и создадим первую html страничку такого содержания.

index.html

Я собираю проекты как рок звезда

Header
Content
Структура папки src теперь будет выглядеть так: 43101b194c5e4abaa1d3a559bc04310d.pngТут все тривиальноfonts — шрифтыimg — картинкиjs — скрипты. В корне этой папки будет только файл main.js, который пригодится нам для сборки. Все свои js файлы — надо будет класть в папку partialsstyle — стили. Тут так же в корне только main.scss, а рабочие файлы в папке partialstemplate — тут будем хранить повторяющиеся куски html кодаВсе html страницы которые мы верстаем — будут лежать в корне src/Добавим в partilas первые js и scss файлы и напоследок — перейдем в корень нашего проекта и создадим там файл gulpfile.js. Вся папка проекта сейчас выглядит так: bbff4dd476b24349b6f1625e8d84767c.pngТеперь все готово к настройке нашего сборщика, так что let’s rock!

Gulpfile.js Вся магия будет заключена в этом файле. Для начала мы импортируем все наши плагины и сам gulpgulpfile.js

'use strict';

var gulp = require ('gulp'), watch = require ('gulp-watch'), prefixer = require ('gulp-autoprefixer'), uglify = require ('gulp-uglify'), cssmin = require ('gulp-cssmin'), sass = require ('gulp-sass'), sourcemaps = require ('gulp-sourcemaps'), rigger = require ('gulp-rigger'), imagemin = require ('gulp-imagemin'), pngquant = require ('imagemin-pngquant'), rimraf = require ('rimraf'), connect = require ('gulp-connect'), opn = require ('opn');

Конечно не обязательно делать это именно так. Существует плагин gulp-load-plugins который позволяет не писать всю эту лапшу из require. Но мне нравится когда я четко вижу что и где подключается, и при желании могу это отключить. По этому пишу по старинке.Так же создадим js объект в который пропишем все нужные нам пути, чтобы при необходимости легко в одном месте их редактировать

var path = { build: { //Тут мы укажем куда складывать готовые после сборки файлы html: 'build/', js: 'build/js/', css: 'build/css/', img: 'build/img/', fonts: 'build/fonts/' }, src: { //Пути откуда брать исходники html: 'src/*.html', //Синтаксис src/*.html говорит gulp что мы хотим взять все файлы с расширением .html js: 'src/js/main.js',//В стилях и скриптах нам понадобятся только main файлы style: 'src/style/main.scss', img: 'src/img/**/*.*', //Синтаксис img/**/*.* означает — взять все файлы всех расширений из папки и из вложенных каталогов fonts: 'src/fonts/**/*.*' }, watch: { //Тут мы укажем, за изменением каких файлов мы хотим наблюдать html: 'src/**/*.html', js: 'src/js/**/*.js', style: 'src/style/**/*.scss', img: 'src/img/**/*.*', fonts: 'src/fonts/**/*.*' }, clean: './build' }; Создадим переменную с настройками нашего dev сервера

var server = { host: 'localhost', port: '9000' }; Собираем 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 ()); //И перезагрузим наш сервер для обновлений }); Напомню, что rigger это наш плагин, позволяющий использовать такую конструкцию для импорта файлов //= template/footer.html Давай те же применим его в деле! В папке src/template/ — создадим файлы header.html и footer.html следующего содержанияheader.html

Header
footer.html

Footer
, а наш файл index.html изменим вот так:

Я собираю проекты как рок звезда //= template/header.html

Content
//= template/footer.html Осталось перейти в консоль и запустить наш таск командой

gulp html: build После того как она отработает — идем в папку build и видим там наш файл index.html, который превратился в это: Я собираю проекты как рок звезда

Header
Content
Это же просто восхитительно! Помню как много неудобств доставляло бегать по всем сверстанным страничкам и вносить изменения в какую-то повторяющуюся на них часть. Теперь это делается удобно в одном месте.Собираем javascript Таск по сборке скриптов будет выглядеть так: 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 (gulp.dest (path.build.js)) //Выплюнем готовый файл в build .pipe (connect.reload ()); //И перезагрузим сервер }); Помните наш файл main.js? Вся идея тут состоит в том, чтобы с помощью rigger’a инклюдить в него все нужные нам js файлы в нужном нам порядке. Именно ради контроля над порядком подключения — я и делаю это именно так, вместо того что бы попросить gulp найти все *.js файлы и склеить их.Часто, при поиске места ошибки я по очереди выключаю какие то файлы из сборки, что бы локализовать место проблемы. В случае если бездумно склеивать все .js — дебаг будет усложнен.Заполним наш main.js

/* * Third party */ //= …/…/bower_components/jquery/dist/jquery.js

/* * Custom */ //= partials/app.js Именно так я делаю на боевых проектах. Вверху этого файла всегда идет подключение зависимостей, ниже подключение моих собственных скриптов.Кстати, bower пакеты можно подключать через такой плагин как gulp-bower. Но я опять же не делаю этого, потому что хочу самостоятельно определять что, где и как будет подключаться.Осталось только запустить наш таск из консоли командой

gulp js: build И в папке build/js — мы увидим наш скомпилированный и сжатый файл.Собираем стили Напишем задачу для сборки нашего SCSS gulp.task ('style: build', function () { gulp.src (path.src.style) //Выберем наш main.scss .pipe (sourcemaps.init ()) //То же самое что и с js .pipe (sass ()) //Скомпилируем .pipe (prefixer ()) //Добавим вендорные префиксы .pipe (cssmin ()) //Сожмем .pipe (sourcemaps.write ()) .pipe (gulp.dest (path.build.css)) //И в build .pipe (connect.reload ()); }); Здесь все просто, но вас могут заинтересовать настройки автопрификсера. По умолчанию он пишет префиксы необходимые для последних двух версий браузеров. В моем случае этого достаточно, но если вам нужны другие настройки — вы можете найти их тут.Со стилями я поступаю так же как и с js, но только вместо rigger’a — использую встроенный в SCSS импорт.Наш main.scss будет выглядеть так:

/* * Third Party */ @import «CSS:…/…/bower_components/normalize.css/normalize.css»;

/* * Custom */ @import «partials/app»; Таким способом получается легко управлять порядком подключения стилей.Проверим наш таск, запустив gulp style: build Собираем картинки Таск по картинкам будет выглядеть так: gulp.task ('image: build', function () { gulp.src (path.src.img) //Выберем наши картинки .pipe (imagemin ({ //Сожмем их progressive: true, svgoPlugins: [{removeViewBox: false}], use: [pngquant ()], interlaced: true })) .pipe (gulp.dest (path.build.img)) //И бросим в build .pipe (connect.reload ()); }); Я использую дефолтные настройки imagemin, за исключением interlaced. Подробнее об API этого плагина можно прочесть тут.Теперь, если мы положим какую-нибудь картинку в src/img и запустим команду gulp image: build то увидим в build нашу оптимизированную картинку. Так же gulp любезно напишет в консоли сколько места он сэкономил для пользователей нашего сайта :)Шрифты Со шрифтами мне обычно не нужно проводить никаких манипуляций, но что бы не рушить парадигму «Работаем в src/ и собираем в build/» — я просто копирую файлы из src/fonts и вставляю в build/fonts. Вот таск gulp.task ('fonts: build', function () { gulp.src (path.src.fonts) .pipe (gulp.dest (path.build.fonts)) }); Теперь давайте определим таск с именем «build», который будет запускать все что мы с вами тут накодили

gulp.task ('build', [ 'html: build', 'js: build', 'style: build', 'fonts: build', 'image: build' ]); Изменения файлов Чтобы не лазить все время в консоль давайте попросим gulp каждый раз при изменении какого то файла запускать нужную задачу. Для этого напишет такой таск: gulp.task ('watch', function (){ watch ([path.watch.html], function (event, cb) { gulp.start ('html: build'); }); watch ([path.watch.style], function (event, cb) { gulp.start ('style: build'); }); watch ([path.watch.js], function (event, cb) { gulp.start ('js: build'); }); watch ([path.watch.img], function (event, cb) { gulp.start ('image: build'); }); watch ([path.watch.fonts], function (event, cb) { gulp.start ('fonts: build'); }); }); С понимаем не должно возникнуть проблем. Мы просто идем по нашим путям определенным в переменной path, и в функции вызывающейся при изменении файла — просим запустить нужный нам таск.Попробуйте запустить в консоли gulp watch и поменяйте разные файлы.Ну не круто ли?)Веб сервер Что бы насладиться чудом livereload — нам необходимо создать себе локальный веб-сервер. Для этого напишем следующий таск: gulp.task ('webserver', function () { connect.server ({ host: server.host, port: server.port, livereload: true }); }); Тут даже нечего комментировать. Мы просто запустим сервер с livereload на хосте и порте, которые мы определили в объекте server.Очистка Если вы добавите какую-нибудь картинку, потом запустите задачу image: build и потом картинку удалите — она останется в папке build. Так что было бы удобно — периодически подчищать ее. Создадим для этого простой таск gulp.task ('clean', function (cb) { rimraf (path.clean, cb); }); Теперь при запуске команды gulp clean просто будет удаляться папка build.И напоследок маленькая милость Этот таск не несет в себе критической функциональности, но он очень мне нравится :) gulp.task ('openbrowser', function () { opn ('http://' + server.host + ':' + server.port + '/build'); }); Когда нам будет нужно, мы запустим его — и у нас в браузере автоматически откроется вкладка с нашим проектом.Классно же:)Финальный аккорд Последним делом — мы определим дефолтный таск, который будет запускать всю нашу сборку. gulp.task ('default', ['build', 'webserver', 'watch', 'openbrowser']); Окончательно ваш gulpfile.js будет выглядеть примерно вот так.Теперь выполним в консоли

gulp И вуаля :) Заготовка для вашего проекта готова и ждет вас.Пара слов в заключение Эта статья задумывалась как способ еще раз освежить в памяти тонкости сборки frontend проектов, и для легкости передачи этого опыта новым разработчикам. Вам не обязательно использовать на своих проектах именно такой вариант сборки. Есть yeoman.io, на котором вы найдете генераторы почти под любые нужды.Я написал этот сборщик по 2ум причинам.— Мне нравится использовать rigger в своем html коде— Почти во всех сборках что я встречал — используется временная папка (обычно .tmp/), для записи промежуточных результатов сборки. Мне не нравится такой подход и я хотел избавится от временных папок.— И я хотел что бы все это было у меня из коробки :)Мою рабочую версию сборщика вы можете скачать на моем github.

Надеюсь статья оказалась полезной для вас :)

P.S. обо всех ошибках, недочетах и косяках — пожалуйста пишите в личку

© Habrahabr.ru