Приятная сборка frontend проекта
В этой статье мы подробно разберем процесс сборки фронтенд проекта, который прижился в моей повседневной работе и очень облегчил рутину.Статья не претендует на истину в последней инстанции, так как сегодня существует большое количество различных сборщиков и подходов к сборке, и каждый выбирает по вкусу. Я лишь поделюсь своими мыслями по этой теме и покажу свой workflow.
Мы будем использовать сборщик 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
В папке src создадим типичную структуру среднестатистического проекта. Сделаем main файлы в папках js/ и style/ и создадим первую html страничку такого содержания.
index.html
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
gulp html: build После того как она отработает — идем в папку build и видим там наш файл index.html, который превратился в это:
/* * 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. обо всех ошибках, недочетах и косяках — пожалуйста пишите в личку