Сборка фронтенда - часть 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 своими руками
Истории из жизни айти и обсуждение кода.