Сборка фронтенда - часть 3. Сборка gulp

сентябрь 30 , 2016
Метки:
Предыдущая статья Исходники

В предыдущих частях статьи про сборку фронтенда мы поговорили про основы сборки и написали простое тестовое приложение на 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, относится к таким инструментам, без которых в принципе можно обойтись. Код они за нас не напишут. Но после знакомства с ними ты уже не можешь представить, как жил и работал до этого. Они становятся настолько привычными, что любой проект ты начинаешь с создания репозитория и настройки сборки. И только когда все подготовительные операции пройдены, ты со спокойной душой начинаешь заниматься программированием.

Исходники проекта доступны по этой ссылке. Желаю удачной сборки и отличных проектов! Вопросы, пожелания и критику оставляйте в комментариях.

Предыдущие части статьи:

Что еще почитать на тему фронтенда

Предыдущая статья Исходники
Метки:
Заходите в группу в контакте - https://vk.com/webdevkin
Анонсы статей, обсуждения интернет-магазинов, vue, фронтенда, php, гита.
Истории из жизни айти и обсуждение кода.
Как Вам статья? Оцените!