Сборка фронтенда - часть 3. Сборка gulp
В предыдущих частях статьи про сборку фронтенда мы поговорили про основы сборки и написали простое тестовое приложение на backbone + require.js.
Теперь самое интересное - собственно сборка gulp. По ходу статьи мы напишем десяток gulp-задач для различных нужд сборки и подробно разберем, зачем те или иные задачи нужны вообще.
Внимание, это не статья по основам основ gulp! Предполагаю, что Вы знаете, что такое nodejs и npm. Если не знаете, то лучше предварительно погуглите, что это такое и как это все поставить. Установка nodejs, npm и gulp займет у Вас 5-10 минут. А теперь к самой статье.
Мое рабочее окружение
По некоторым причинам мне приходится работать со старой версией nodejs. Поэтому версии плагинов gulp из моего package.json могут отличаться от Ваших. Советую не лениться и не запускать npm install с моим package.json из исходников, а устанавливать все плагины руками, благо, что нужные инструкции в статье будут.
Все версии плагинов, указанные в исходниках, работают на таком окружении:
- os - windows7
- nodejs - 0.10.35
- npm - 1.4.28
- gulp - 3.9.1
package.json и gulpfile.js
Это первое, с чего мы начинаем сборку фронтенда - создадим package.json в корне проекта.
{
"name": "Simple_Backbone_Application",
"version": "1.0.0",
"author": "webdevkin",
"license": "ISC"
}
Стандартная заготовка, можно ничего не менять. После этого ставим gulp. Если он еще не установлен глобально, то делаем npm install -g gulp
Затем устанавливаем gulp как локальную зависимость для нашего проекта - npm install gulp --save-dev. Опция --save-dev обновит package.json, добавив в него секцию
"devDependencies": {
"gulp": "^3.9.1"
}
Теперь создадим пока что пустой файл gulpfile.js, тоже в корне проекта, и приступим к задачам сборки. Начнем с базового - с очистки результатов предыдущей сборки.
gulp-clean - удаление файлов и папок
Как мы помним, собранные файлы будут складываться в папку build в корне проекта (на самом деле мы наследим и в src, но зачем и где - чуть позже). Поэтому перед каждым запуском сборки нужно очищать результаты предыдущей - то есть просто сносить папку build.
Ставим плагин gulp-clean - npm install gulp-clean --save-dev. А в gulpfile.js пишем такой код
'use strict';
// Подключение плагинов
var gulp = require('gulp'),
clean = require('gulp-clean');
// Пути для сборки
var path = {
build: {},
src: {},
watch: {},
clean: 'build'
};
// Очистка папок и файлов
gulp.task('clean', function() {
return gulp.src(path.clean, {read: false})
.pipe(clean());
});
Сначала мы объявляем переменные-модули, выполняющие всю работу. Пока это сам gulp и clean, но с каждой задачей раздел будет расширяться.
Зачем нужен объект path? Дело в том, что задач у нас будет много, также будет много путей к файлам и папкам. И их удобнее хранить в одном месте, в конфиге, а не разбрасывать по отдельным gulp-таскам. Сейчас объект небольшой, да еще и с пустыми полями, но по мере написания каждой задачи мы будем добавлять в него новые пути и он очень быстро разрастется. Как Вы уже догадались, в build будут пути к стилям, скриптам и картинкам, уже собранным для продакшена, в src - для разработки, в watch - тоже для разработки, файлы для наблюдения за ними, чтобы запускать задачи сборки в режиме онлайн. Но обо всем по порядку.
Сама gulp-таска в конце собственно и очищает папку build. Попробуйте сейчас создать эту папку, кинуть туда пару файлов и запустить в консоли gulp clean. Убедитесь, что после отработки задачи папки исчезла - то, что нужно.
gulp-sass - собираем стили
Ставим плагин gulp-sass: npm install gulp-sass --save-dev. Теперь сама задача. Давайте договоримся, что пока будем собирать стили для режима девелопера. Как мы помним, в предыдущем уроке мы так и не подключили стили к проекту, хотя и написали sass-модули. Пора это исправить. Дополним раздел var вот так:
var gulp = require('gulp'),
clean = require('gulp-clean'),
sass = require('gulp-sass');
Добавим пути к нашему конфигу:
...
src: {
sass: 'src/styles/main.scss',
css: 'src/styles/'
},
...
Все просто: указываем, что начальный файл main.scss, который уже подключает в себе все дополнительные sass-модули. А также скажем, что положить выходной файл css мы хотим в папку src/styles. Называться он будет так же, как и sass-файл, только с нужным расширением - main.css.
И сама gulp-таска sass
// Компиляция sass, сборка стилей
gulp.task('sass', function() {
return gulp.src(path.src.sass)
.pipe(sass())
.pipe(gulp.dest(path.src.css));
});
Теперь запускайте в консоли gulp sass, убедитесь, что main.css создался и обновите страничку проекта в браузере. Если все сделали правильно, то стили применятся, появятся отступы по бокам страницы и поправятся шрифты - все хорошо.
Так как main.css у нас собирается автоматически, то нет нужды держать его в проекте, нужно удалять его при запуске gulp clean. Поэтому обновим конфиг:
...
clean: ['build', 'src/styles/main.css']
Теперь запустите снова gulp clean и убедитесь, что удаляется не только папка build, но и файл main.css.
gulp-sourcemaps - sourcemaps для стилей
Вы могли заметить, что при сборке стилей мы не сжимали выходной файл. Это мы сделаем позже, когда займемся сборкой для продакшена. В режиме девелопера нам ни к чему сжатые стили, для отладки удобнее работать так. Вместо этого сейчас мы настроим sourcemaps.
Что это такое? Мы пишем стили для проекта в sass-файлах. Когда мы захотим отладить верстку, то откроем браузерный dev tools, поправим стили, как нужно и захотим их сохранить в проекте. Вот здесь-то нас и подстерегает неудобство. Стили собраны в один-единственный файл. Хорошо нам с тестовым приложением, где 3 строчки стилей лежат в двух файлах, а как быть со стилями, которые собираются из хотя бы полусотни sass-файлов? Для этого и нужны sourcemaps - эти файлы-"карты ресурсов" "запоминают", какие стили в каких исходных sass-файлах находятся. Что конечно же значительно облегчит работу с версткой.
Чтобы было совсем наглядно, вот два скриншота, как мы ищем, где лежат стили для тега h1.
Вот здесь мы собрали стили не зная о sourcemaps:
Заметьте, мы ничего не знаем о том, где стили для h1 искать в нашем проекте.
А вот как выглядит dev tools, когда мы подключим sourcemaps
Заметили разницу? Видно, что стили для h1 лежат в первой строке файла _page.scss.
А ниже указаны для body - файл _common.scss - удобно.
Надеюсь, если Вы до этого не знали про sourcemaps, то теперь точно захотите ими пользоваться :-)
К тому же создавать их с помощью gulp очень просто. Ставим плагин npm install gulp-sourcemaps --save-dev. Пара изменений в gulpfile.js
// Подключение плагинов
var gulp = require('gulp'),
clean = require('gulp-clean'),
sass = require('gulp-sass'),
// подключили плагин
sourcemaps = require('gulp-sourcemaps');
...
// Компиляция sass, сборка стилей
gulp.task('sass', function() {
return gulp.src(path.src.sass)
// раз новая строчка
.pipe(sourcemaps.init())
.pipe(sass())
// два новая строчка
.pipe(sourcemaps.write())
.pipe(gulp.dest(path.src.css));
});
На заметку: sourcemaps с таким же успехом применяются и при склеивании/сжатии js-файлов, так что пользуйтесь этим инструментом на полную. А мы переходим к сборке модулей requirejs в один файл. Если Вы не знакомы с подходом requirejs, то можете просто пропустить следующий раздел.
gulp-requirejs - сборка require.js
Начиная с этого раздела мы наконец-то вспомним, что сборка фронтенда в первую очередь задумана для боевого окружения, поэтому займемся amd-модулями requirejs. Все модули нужно собрать в один файл да еще сжать его и выкинуть в папку build/js с названием build.js. Здесь долго пояснять не буду, сборку мы разобрали в предыдущей части, когда писали тестовое приложение.
Ставим плагин gulp-requirejs npm install gulp-requirejs --save-dev. Подключаем его:
var ...
...
rjs = require('gulp-requirejs');
Прописываем новые пути в path
var path = {
build: {
js: 'build/js/',
rjs: '../../build/js/build.js'
},
src: {
js: 'src/js/**/*.js',
rjs: 'src/js/',
sass: 'src/styles/main.scss',
css: 'src/styles/',
lib: '../lib/',
tpl: '../tpl/'
},
watch: {},
clean: ['build', 'src/styles/main.css']
};
И самое хитрое - задача gulp-requirejs и конфиг для нее
// Сборка requirejs
gulp.task('requirejs:build', function() {
rjs({
baseUrl: path.src.rjs,
name: 'main',
out: path.build.rjs,
paths: {
jquery: path.src.lib + 'jquery',
lodash: path.src.lib + 'lodash',
backbone: path.src.lib + 'backbone',
text: path.src.lib + 'text',
tpl: path.src.tpl
},
shim: {
lodash: {
exports: '_'
},
backbone: {
deps: [
'lodash',
'jquery'
],
exports: 'Backbone'
}
},
map: {
'*': {
'underscore': 'lodash'
}
}
}).pipe(gulp.dest(path.build.js));
});
Подробно объяснить здесь сложно, нужно знать принципы подключения requirejs и внимательно следить за путями к файлам и папкам. Если Вам лень в этом разбираться, то можете просто использовать этот готовый конфиг и по мере надобности понемногу в нем ковыряться.
Почему requirejs:build? Потому что для подключения собранного файла нам нужно будет сначала подключить библиотеку require.js в index.html, и поэтому мы сейчас напишем таску requirejs:copy, которая копирует нужную нам либу в папку build - готовит все для продакшена. Но пока запустите gulp requirejs:build и убедитесь, что сгенерировался файл build/js/build.js.
gulp-contrib-copy - копирование файлов
Теперь нам нужно скопировать файл src/lib.require.js в папку build/js, чтобы все было готово для подключения javascript на странице. npm install gulp-contrib-copy --save-dev Обновляем gulpfile.js
var ...
...
copy = require('gulp-contrib-copy');
...
// Пути для сборки
var path = {
build: {...},
src: {
...
requirejsLib: 'src/lib/require.js'
...
},
watch: {...},
clean: ...
}
...
// Копирование библиотеки require.js для production
gulp.task('requirejs:copy_lib', function() {
gulp.src(path.src.requirejsLib)
.pipe(copy())
.pipe(gulp.dest(path.build.js));
});
Запустим gulp requirejs:copy_lib и увидим, что в папке build/js/ рядом с build.js появился require.js - значит, все получилось! Идем дальше.
gulp-uglify - сжатие js-файлов
В двух последних задачах requirejs:build и requirejs:copy_lib мы подготовили два файла, предназначенных для боевого окружения. Но хорошим тоном считается сжатие файлов для уменьшения трафика пользователей и ускорения загрузки. В этом нам поможет плагин gulp-uglify.
Ставим. npm install gulp-uglify --save-dev. Сам по себе плагин не используется, он действует совместно с другими задачами, в нашем случае это будет и сборка requirejs:build, и копирование библиотечки requirejs:copy_lib. Поправим код
var ...
...
uglify = require('gulp-uglify');
...
// Сборка requirejs
gulp.task('requirejs', function() {
...
.pipe(uglify())
.pipe(gulp.dest(path.build.js));
...
// Копирование библиотеки require.js для production
gulp.task('requirejs:copy_lib', function() {
gulp.src(path.src.requirejsLib)
.pipe(copy())
.pipe(uglify())
.pipe(gulp.dest(path.build.js));
});
Посмотрим, что мы изменили. Кроме стандартного подключения, мы добавили всего лишь одну строку .pipe(uglify()) к нашим задачам непосредственно перед формированием выходных файлов. Теперь запустите обе gulp-таски снова и проверьте, что в build/js/ лежат те же файлы, только уже минифицированные - и это пригодно для продакшена. Переходим к последней на сегодня задаче сборки javascript - посмотрим, как работает плагин gulp-concat
gulp-concat - склеивание файлов
Что и зачем мы собрались клеить? Несмотря на то, что весь js-код мы разбили на amd-модули и собрали с помощью gulp-requirejs, очень часто на проекте возникает необходимость подключить какие-то сторонние скрипты, которые не хочется мешать с основным кодом. Это как правило, скрипты вроде inspectlet и яндекс-метрики, которые в больших количествах любят притаскивать аналитики и маркетологи.
Сложим эти файлы в папке src/scripts, чтобы они не мозолили нам глаза, и будем склеивать их все в один, сколько бы их там ни было, и выкидывать в папку build/js/
под названием scripts.js, предварительно сжав. Ух, какое длинное предложение получилось!
Наши действия: создадим папку src/scripts и создадим в ней для примера 3 файла, google-analytics.js, inspectlet.js и shoppyboom.js.
Их содержимое незатейливо:
// google-analytics.js
console.log('init google analytics');
// inspectlet.js
console.log('init inspectlet');
// shoppyboom.js
console.log('init shoppyboom');
Установим плагин npm install gulp-concat --save-dev. И теперь gulpfile.js
var ...
...
concat = require('gulp-concat');
...
var path: {
...
src: {
...
scripts: 'src/scripts/**/*.js'
...
}
...
}
...
// Склеивание сторонних скриптов
gulp.task('scripts', function() {
return gulp.src(path.src.scripts)
.pipe(concat('scripts.js'))
.pipe(uglify())
.pipe(gulp.dest(path.build.js));
});
Все достаточно просто. Указываем исходную папку, из которой будут браться все js-файлы, склеиваться в один под названием scripts.js, сжиматься и выкидываться в папку build/js/ уже третьим и последним по счету js-файлом для продакшена. И конечно же, запускайте gulp scripts и проверяйте, что файл действительно есть с содержимым
console.log("init google analytics"),console.log("init inspectlet"),console.log("init shoppyboom");
Мы вплотную подошли к тому, чтобы запустить наше приложение в production-режиме. Но до этого осталось сделать еще одну важную задачу - оптимизацию и сжатие изображений.
gulp-imagemin и imagemin-pngquant - оптимизация и сжатие изображений
Эти 2 плагина мы используем для обработки наших изображений, и неважно, что у нас всего одна картинка. Один раз настроим задачу сборки и забудем про изображения в идеале навсегда.
Установка npm install gulp-imagemin --save-dev и npm install imagemin-pngquant --save-dev. Но здесь хочу обратить Ваше внимание. Именно с этими плагинами у меня прошло не все так гладко. С моей старой версией ноды 0.10.35 согласились работать плагины только с версиями 2.1.0 и 4.1.2 соответственно.
Пробуйте сначала ставить их, как обычно, последние версии по умолчанию. Если Вы столкнетесь проблемой сборки, в консоли будут сыпаться gulp ошибки при сборке или даже при установке. И тогда придется с гуглом наперевес искать подходящие под Вашу ноду версии оных плагинов. Ставить их придется с указанием конкретной версии, вот так npm install gulp-imagemin@2.1.0 --save-dev. Второй плагин аналогично. При этом в package.json имеет смысл убрать ^ из версии плагинов, то есть
// было
"gulp-imagemin": "^2.1.0",
"imagemin-pngquant": "^4.1.2"
// стало
"gulp-imagemin": "2.1.0",
"imagemin-pngquant": "4.1.2"
Иначе когда Вы захотите обновить версии всех плагинов и запустите npm install, то рискуете обновиться и поломать сборку картинок. А затем долго и нудно возвращать старые версии. Впрочем, будем надеяться, что с Вашей адекватной версии ноды все прекрасно установится и по умолчанию. Редактируем gulpfile.js
var ...
...
imagemin = require('gulp-imagemin'),
pngquant = require('imagemin-pngquant');
...
// добавили пути
var path: {
build: {
...
img: 'build/img/'
},
src: {
...
img: 'src/img/**/*.*'
}
}
...
// Оптимизация изображений
gulp.task('img', function () {
gulp.src(path.src.img)
.pipe(imagemin({
progressive: true,
svgoPlugins: [{removeViewBox: false}],
use: [pngquant()],
interlaced: true
}))
.pipe(gulp.dest(path.build.img));
});
Если с плагинами все хорошо и все сделали правильно, то запуск gulp img соберет нам все картинки (всю одну) и выкинет в build/img/ в сжатом виде. Проверяйте. А мы меж тем переходим к самой интересной на мой взгляд теме - препроцессинга html и окончательному формированию production-окружению.
gulp-preprocess - препроцессинг html
На самом деле, плагин умеет препроцессить не только html, но и js, что тоже бывает очень полезно.
Но в нашем случае мы рассмотрим только html.
Давайте поставим плагин, дабы не забыть про него, и затем разберемся, а что же такое препроцессинг и зачем он нужен.
npm install gulp-preprocess --save-dev
Представим, что у нас настроены задачи для генерации двух вариантов css-файлов: сжатых (для production) и обычных с sourcemaps (для development). А еще мы в режиме разработки не хотим подключать упомянутые ранее js-файлы скриптов, но хотим их на production. А если мы разрабатываем SPA и общаемся с сервером через api, то адреса серверов могут быть различными. Например, на боевом сайте мы обращаемся к серверу по запросам вида webdevkin.ru/api/v1/, но параллельно работаем над новой версией api и тестируем ее на test.webdevkin.ru/api/v2/.
Все эти варианты сводятся к тому, что например, главный html-файл в разных режимах будет пусть и парой строк кода, но различаться. Каждый раз помнить об этом и руками менять нужные строки скучно и чревато ошибками, потому и придумали препроцессинг. Мы готовим шаблон-заготовку html-файла, прогоняем его через задачу препроцессинга и на выходе получаем файл с нужными настройками. Смотрим на примере.
Создадим папку html в src и перенесем в нее файл index.html. Это будет заготовка html, в которой мы чуть поправим содержимое body.
Ничего сложного нет, для режима разработки development мы подключаем require.js из папки lib, а для production - из js. Напомню, что src и build у нас - это корневые папки проектов. И файлы js в них располагаются в разных местах. Плюс для production подключим дополнительно js/scripts.js, которые не нужны нам в разработке.
Надеюсь, ситуация и полезность такого подхода немного проясняется.
Осталось сделать так, чтобы эта html-заготовка превратилась в валидный html-файл.
В этом нам поможет плагин gulp-preprocess.
npm install gulp-preprocess --save-dev
Добавляем задачу в gulpfile.js
var ...
...
preprocess = require('gulp-preprocess');
...
var path: {
build: {
root: 'build/',
...
},
src: {
root: 'src/',
html: 'src/html/**/*.html',
...
},
watch: {...},
clean: ['build', 'src/styles/main.css', 'src/index.html']
}
...
// Препроцессинг html
// development
gulp.task('html:dev', function() {
gulp.src(path.src.html)
.pipe(preprocess({context: {NODE_ENV: 'development', DEBUG: true}}))
.pipe(gulp.dest(path.src.root))
});
// production
gulp.task('html:prod', function() {
gulp.src(path.src.html)
.pipe(preprocess({context: {NODE_ENV: 'production', DEBUG: true}}))
.pipe(gulp.dest(path.build.root))
});
Что мы сделали? Добавили пути в конфиг, указали корневые папки обоих проектов, для src еще указали, где хранится исходный html-шаблон. Не забыли включить генерируемый src/index.html в задачу очистки. build/index.html специально не включаем, папка build и так удаляется целиком. И написали 2 задачи препроцессинга: html:dev и html:prod.
Эти 2 задачи отличаются друг от друга только установкой окружения NODE_ENV и выходными путями, куда складывается преобразованная html. Запустите по очереди gulp html:dev и gulp html:prod и увидите, что в папках src и build соответственно появились index.html с разным, нужным нам содержимым.
Итак, js-скрипты подключаются так, как нам нужно. Но не будем забывать про стили, ибо собранный файл main.css нужно подключать из разных папок: src/styles и build/css. Заменим строку подключения стилей в srс/html/index.html на
Пути к стилям мы указали правильно, но у нас есть только одна задача sass, которая собирает несжатый main.css в src/styles да еще и генерирует sourcemaps. Это отлично для разработки и никуда не годится для боевого сайта. Давайте это исправим. Сначала добавим путь к стилям в build
var path = {
build: {
...
css: 'build/css/'
},
...
}
А затем вместо задачи sass создадим две: sass:dev и sass:prod.
// Компиляция sass, сборка стилей
// development
gulp.task('sass:dev', function() {
return gulp.src(path.src.sass)
.pipe(sourcemaps.init())
.pipe(sass())
.pipe(sourcemaps.write())
.pipe(gulp.dest(path.src.css));
});
// production
gulp.task('sass:prod', function() {
return gulp.src(path.src.sass)
.pipe(sass({outputStyle: 'compressed'}))
.pipe(gulp.dest(path.build.css));
});
Видим, что для сборки development ничего не поменялось, кроме названия. А вот dev:prod существенно отличается: мы не генерируем в ней sourcemaps, зато сжимаем выходной файл опцией {outputStyle: 'compressed'}. Как обычно, проверяйте, что все работает, gulp sass:dev и gulp sass:prod.
Теперь когда мы научились препроцессить html, собирать стили, скрипты и requirejs-модули, копировать файлы и оптимизировать изображения, мы переходим к кульминации статьи - собираем все написанные задачи вместе.
gulp develop и gulp production - запускаем сборку одной командой
Мы написали уже с десяток задач под разные окружения, но не собираемся же каждый раз вбивать все их руками. Не для этого же придуманы инструменты сборки. Нет, нам понадобятся всего две задачи, gulp develop и gulp production, которые сами разберутся, какие задачи нам нужно выполнить для нужного окружения. Привожу сразу код и все встанет на свои места.
// Режим разработки
gulp.task('develop', [
'clean',
'html:dev',
'sass:dev'
]);
// Режим production
gulp.task('production', [
'clean',
'html:prod',
'sass:prod',
'requirejs:build',
'requirejs:copy_lib',
'img',
'scripts'
]);
Все! По запуску одной команды мы сгенерируем все нужные файлы для разработки или деплоя на продакшен. В режиме development нам достаточно только препроцессить html и собрать стили. В production делаем все - и препроцессинг, и стили, собираем модули requirejs в один файл, еще и копируем библиотечку require, а также оптимизируем изображения и склеиваем и копируем файлы scripts.
gulp-sync - запускаем задачи синхронно
Это небольшое отступление от темы. Мы только что написали задачи develop и production, которые запускают множество мелких задач. Природа gulp-a такова, что в таких случаях все задачи запускаются асинхронно. И это здорово, положительно сказывается на скорости работы, если бы не одно но.
Есть задачи, которые должны выполняться только последовательно.
В нашем случае, в первую очередь должна отработать очистка предыдущих результатов, и только потом запускаться все остальные таски.
gulp умеет делать это нативными средствами, но не очень удобочитаемыми, поэтому воспользуемся сторонним плагином gulp-sync.
npm install gulp-sync --save-dev
И поправим код gulpfile-a
var ...
...
gulpsync = require('gulp-sync')(gulp)
...
// Режим разработки
gulp.task('develop', gulpsync.sync([
'clean',
[
'html:dev',
'sass:dev'
]
]));
// Режим production
gulp.task('production', gulpsync.sync([
'clean',
[
'html:prod',
'sass:prod',
'requirejs:build',
'requirejs:copy_lib',
'img',
'scripts'
]
]));
Немного поправили задачи develop и production. И теперь мы уверены, что clean не очистит нам уже возможно сгенерированные другими задачами файлы.
gulp-watch - собираем в режиме онлайн
Написанные задачи отлично подходят для запуска сборки в режиме production. Но для разработки все же чего-то не хватает, а именно. Когда мы поменяем пару строк в sass-файлах или в index.html, нам придется запускать gulp sass:dev, gulp html:dev или gulp develop. Каждый раз врукопашную это делать совсем не интересно. Для этой рутинной работы существует замечательный плагин gulp-watch. Он следит за изменяемыми нами файлами и автоматически запускает нужные задачи сборки. Посмотрим, как это работает.
npm install gulp-watch --save-dev
Правим код
var ...
...
watch = require('gulp-watch');
...
var path = {
build: {...},
src: {...},
watch: {
html: 'src/html/**/*.html',
sass: 'src/styles/**/*.scss'
},
clean: ...
}
...
// watch-таска
gulp.task('watch', function(){
watch([path.watch.html], function(event, cb) {
gulp.start('html:dev');
});
watch([path.watch.sass], function(event, cb) {
gulp.start('sass:dev');
});
});
...
// Режим разработки
gulp.task('develop', gulpsync.sync([
'clean',
[
'html:dev',
'sass:dev'
],
'watch'
]));
Казалось бы, сложная задача, которая сэкономит нам уйму времени, но так легко и быстро настраивается. Всего лишь указали, за какими файлами мы хотим следить и какие задачи запускать при их изменении. Обратите внимание, мы добавили задачу watch в develop. Теперь gulp develop не только соберет стили и html для разработки, но и запустить задачу их автоматической пересборки - отлично!
Для проверки запускаем gulp develop и пробуем редактировать, например, sass-файлы. В терминале сразу увидите, как gulp реагирует на Ваши действия и запускает задачу sass. То же самое и с html. Параллельно проверяте содержимое src/styles/main.css и src/index.html и убеждайтесь, что их содержимое меняется.
browser-sync - запуск локального веб-сервера
И напоследок рассмотрим очень крутую штуку, легко позволяющую поднять локально веб-сервер.
Я упоминал, что для проверки работы Вам нужно было настроить apache или nginx, или запустить сервер средствами IDE, или заливать файлы на хостинг.
С помощью этого плагина наш проект становится совершенно самодостаточным.
Всего лишь одна команда позволит запустить проект в нужном режиме, development или production.
То есть из двух разных папок, src или build.
Давайте поставим плагин и проверим на примере.
npm install browser-sync --save-dev
var ...
...
browserSync = require('browser-sync');
...
// Конфиги для локального вебсервера
var webserver = {
dev: {
server: {
baseDir: './src'
},
tunnel: true,
host: 'localhost',
port: 9001,
logPrefix: 'app_dev'
},
prod: {
server: {
baseDir: './build'
},
tunnel: true,
host: 'localhost',
port: 9002,
logPrefix: 'app_prod'
}
};
...
// Запуск локального веб-сервера
// development
gulp.task('webserver:dev', function () {
browserSync(webserver.dev);
});
// production
gulp.task('webserver:prod', function () {
browserSync(webserver.prod);
});
...
// Режим разработки
gulp.task('develop', gulpsync.sync([
'clean',
[
'html:dev',
'sass:dev'
],
'watch',
'webserver:dev'
]));
Мы создали конфиги для веб-сервера, указав разные корневые папки и порты, а также написали собственно задачи для запуска webserver:dev и webserver:prod. webserver:dev мы еще добавили в задачу develop. Проверим, что из этого получилось.
Запустите теперь gulp develop и немного подождите. У Вас откроется браузер, где в новой вкладке по адресу http://localhost:9001 откроется наш проект, запущенный из папки src. Откройте консоль и убедитесь, что это действительно режим разработки: стили и скрипты не сжаты, 3 файла scripts не загружены, для стилей есть sourcemaps. При этом в фоне работает watch, и если Вы поменяете стили, то переключившись в браузер и обновив страницу, Вы сразу увидите сделанные обновления. Просто отлично!
Получается, теперь все что нам нужно, чтобы начать работать - это запустить gulp develop. Остальное все сделается за нас: очистятся результаты предыдущей сборки, соберутся стили и html, запустится наблюдатель watch и веб-сервер. Да еще и откроется браузер с запущенным проектом.
В режим production я не стал включать webserver:prod - мы собираем для продакшена не чтобы запустить браузер, а чтобы собрать файлы и залить их на боевой сайт. Но убедиться, что все собрано правильно и корректно работает, смысл имеет. Сделайте сначала gulp production, а затем gulp webserver:prod. Проект из папки build запустится с адресом http://localhost:9001.
Открыв dev tools, Вы легко проверите, что подключились именно продакшеновские файлы, сжатые стили и скрипты, никаких sourcemaps. Также заметите 3 отдельных js-файла из папки scripts, которые сыпят в консоль сообщения 'init что-то там'.
Дополнение: browserSync.reload
Грамотный человек в комментариях (Dima) напоминает про одну замечательную вещь: browserSync.reload. Иными словами, liveReload, живая перезагрузка. Настроив ее, нам больше не придется обновлять страницу в браузере руками, после правки кода она перезагрузится сама. И сделать это очень просто.
Допустим, мы хотим обновлять страницу при изменении html, стилей, javascript-кода и шаблонов в папке tpl. Конечно, только в режиме develop. Для этого в таски html:dev и sass:dev нужно дописать одну строчку - .pipe(browserSync.reload({stream: true}))
Получится так
// Препроцессинг html
// development
gulp.task('html:dev', function() {
gulp.src(path.src.html)
.pipe(preprocess({context: {NODE_ENV: 'development', DEBUG: true}}))
.pipe(gulp.dest(path.src.root))
// НОВАЯ СТРОКА
.pipe(browserSync.reload({stream: true}));
});
...
// Компиляция sass, сборка стилей
// development
gulp.task('sass:dev', function() {
return gulp.src(path.src.sass)
.pipe(sourcemaps.init())
.pipe(sass())
.pipe(sourcemaps.write())
.pipe(gulp.dest(path.src.css))
// НОВАЯ СТРОКА
.pipe(browserSync.reload({stream: true}));
});
Для js-кода и шаблонов делаем чуть по-другому. Добавим пути к отслеживаемым файлам в конфиг path.watch и одну строку в watch-таску
watch: {
html: 'src/html/**/*.html',
sass: 'src/styles/**/*.scss',
// НОВЫЕ СТРОКИ
js: 'src/js/**/*.js',
tpl: 'src/tpl/**/*.html'
}
...
// watch-таска
gulp.task('watch', function(){
watch([path.watch.html], function(event, cb) {
gulp.start('html:dev');
});
watch([path.watch.sass], function(event, cb) {
gulp.start('sass:dev');
});
// НОВАЯ СТРОКА
watch([path.watch.js, path.watch.tpl]).on('change', browserSync.reload);
});
Перезапускайте gulp develop и пробуйте редактировать код, страница обновляется, кодить стало намного приятнее.
browserSync tunnel
Хочется упомянуть еще об одной небольшой детали насчет browserSync.
Откройте терминал и увидите примерно такую картину
Обратите внимание на строку
Tunnel: http://pjwhpjlsom.localtunnel.me
Она означает, что прямо сейчас Ваш проект доступен по этой ссылке извне.
Можно отправить ее коллеге-программисту или дизайнеру.
Конечно, это работает только до тех пор, пока у Вас запущен веб-сервер.
При следующем запуске адрес поменяется: удобно и безопасно.
Выводы, исходники проекта и нужные ссылки
На этом пока все, что я хотел рассказать о сборке фронтенда с помощью gulp. Статья получилась объемной, много кода, но и написали мы немало самых разных задач. Материал получился очень сжатым, но подробный рассказ о рассмотренных плагинах и вариантах сборки мог бы растянуться на десяток статей.
Моей главной целью было заинтересовать читателей, мало знакомых с инструментами и принципами сборки. Я постарался показать реальные задачи, которые возникают при сборке практически любого проекта, даже самого простого. Мы создали и настроили gulp так, что теперь можно использовать только 2 команды для всей работы и сколь угодно расширять проект, не заботясь о рутинных операциях.
gulp, как, например, и git, относится к таким инструментам, без которых в принципе можно обойтись. Код они за нас не напишут. Но после знакомства с ними ты уже не можешь представить, как жил и работал до этого. Они становятся настолько привычными, что любой проект ты начинаешь с создания репозитория и настройки сборки. И только когда все подготовительные операции пройдены, ты со спокойной душой начинаешь заниматься программированием.
Исходники проекта доступны по этой ссылке. Желаю удачной сборки и отличных проектов! Вопросы, пожелания и критику оставляйте в комментариях.
Предыдущие части статьи:
- Сборка фронтенда - часть 1. Основы сборки и организация проекта
- Сборка фронтенда - часть 2. Тестовое приложение Backbone + Require.js
Что еще почитать на тему фронтенда
- 10+ способов ускорить фронтенд
- Встраиваемый виджет на нативном javascript
- Корзина в интернет-магазине на javascript
- Разбираемся с throttle и debounce на примерах
- jquery-промисы: зачем и как работают
- Кроссдоменные ajax-запросы и немного php
- SPA своими руками
Истории из жизни айти и обсуждение кода.