Gulp: как я проект для production собирал, livereload запускал и перехват ошибок делал

    image


    Хочу поделиться историей моего знакомства с gulp и как я решал некоторые проблемы при разработке. Материал ориентирован на тех, кто знаком с nodejs и только начинает знакомиться с gulp, а также на тех, кто ищет решение схожей проблемы. Как следует из названия, статья состоит из трех глав.

    В самом начале


    Мое знакомство с gulp началось два месяца назад, когда, будучи некоторое время безработным, я начал подумывать устроиться в офис. Помониторив вакансии, я решил переквалифицироваться из php девелопера в frontend — благо опыт и знания позволяли это сделать быстро. И вот, спустя некоторое время, не без усилий, меня взяли на испытательный срок. Я смутно представляю, как мне удалось это сделать, но, придя в первый день на работу, я исписал страницу блокнота с двух сторон терминами, которые я слышал впервые… В тот день я понял, что затерялся в веке веб-разработчиков.

    Вступление


    Я расскажу про три мои проблемы в процессе знакомства с gulp:
    1. Как собирать compressed (production) и uncompressed версии проекта без особых усилий?
    2. Как заставить браузер автоматически перезагружать проект при внесении изменений?
    3. Как перехватывать ошибки в коде, чтобы watch таски не вылетали, если в коде была допущена ошибка?

    Для наглядности я сделал небольшой проект. Посмотреть можно по ссылке. Скачать со всеми вариациями gulpfile.js из текущей статьи можно архивом по ссылке.

    Структура проекта:
    gulpfile.habrahabr.webulla.ru/
    - assets/
    - - core/
    - - - components/
    - - - - woman.js
    - - - application.js
    - web/
    - - assets/
    - - index.html
    - gulpfile.js
    - package.json
    

    Работающий gulpfile.js, который я буду дополнять разобранными в статье решениями (в архиве — gulpfile-0-original.js):
    'use strict';
    
    // подключаем компоненты gulp
    var gulp = require('gulp');
    var browserify = require('gulp-browserify');
    
    // настройки путей к файлам
    var rootDir = '.';
    var sourceDir = rootDir + '/assets'; // здесь хранятся все исходники
    var destDir = rootDir + '/web/assets'; // здесь хранится все на выходе
    
    // блок с настройками компонентов
    // здесь я храню настройки для задач
    // удалил отсюда все кроме scripts для наглядности
    var components = {
      scripts: {
        source: sourceDir + '/core/application.js',
        dest: destDir,
        watch: sourceDir + '/core/**/*.js',
        options: {
          paths: ['./node_modules', sourceDir],
          debug: false,
          fullPaths: true
        }
      }
    };
    
    // задача для компиляции скриптов
    gulp.task('scripts', function () {
      gulp.src(components.scripts.source)
        .pipe(browserify(components.scripts.options))
        .pipe(gulp.dest(components.scripts.dest));
    });
    
    // задача для слежения за изменениями в скриптах
    gulp.task('watch:scripts', ['scripts'], function () {
      // если отслеживаемые файлы изменились, запускаем задачу компиляции скриптов
      gulp.watch(components.scripts.watch, ['scripts']);
    });
    
    gulp.task('default', ['scripts']);
    gulp.task('watch', ['watch:scripts']);
    

    Сборка проекта из консоли:
    gulp
    

    Запуск задачи для слежения за изменениями в файлах:
    gulp watch
    

    Глава 1 — Сборка compressed (production) и uncompressed версии проекта


    Проблема


    Чтобы уменьшить размер скомпилированного application.js я использую пакет gulp-uglify. Проблема в том, что искать косяки и дебажить код мне удобнее в не сжатом коде (без применения gulp-uglify). В общем, как сделать так, чтобы без правок в gulpfile.js можно было собирать как uncompressed версию проекта для отладки, так и compressed версию для продакшена?

    Решение


    Я не помню, где подсмотрел это решение, но оно мне понравилось и хочу поделиться с вами. Оно заключается в том, чтобы при запуске gulp из консоли, передавать определенный флаг:
    # сборка uncompressed версии
    gulp
    
    # сборка compressed версии
    gulp --production
    

    Для такой реализации я воспользовался:
    • пакетом yargs для доступа к аргументам команды;
    • пакетом gulp-uglify для сжатия кода проекта;
    • пакетом gulp-if, чтобы запускать gulp-uglify только когда это нужно.

    Сначала поставил все необходимые пакеты:
    npm install --save-dev yargs gulp-uglify gulp-if
    

    Подключил пакеты в gulpfile.js:
    var argv = require('yargs').argv;
    var gulpif = require('gulp-if');
    var uglify = require('gulp-uglify');
    

    Добавил pipe в задачу scripts:
    gulp.src(components.scripts.source)
        .pipe(browserify(components.scripts.options))
        .pipe(gulpif(argv.production, uglify())) // <- добавляем вот эту строчку
        .pipe(gulp.dest(components.scripts.dest));
    

    Добавление этой строчки эквивалентно следующему коду:
    var src = gulp.src(components.scripts.source)
        .pipe(browserify(components.scripts.options));
    
    // проверяем, передан ли флаг production
    if(argv.production) {
        src.pipe(uglify());
    }
    
    src.pipe(gulp.dest(components.scripts.dest));
    

    Пояснение: в случае, когда argv.production == true, будет применен uglify.

    Теперь сборку uncompressed версии проекта выполняю командой:
    gulp
    

    А сборку compressed версии:
    gulp --production
    

    Условие if(argv.production) {...} я использую и в других местах, например, при сборке стилей для продакшена.

    Результат


    Дополненный решением из этой главы gulpfile.js лежит в архиве и называется gulpfile-1-production.js.

    Глава 2 — Автоматическая перезагрузка вкладки с проектом в браузере после изменений


    Проблема


    Каждый раз, после внесения изменений в файлы и при запущенном gulp watch проект заново собирается, но страница с проектом не перезагружается. Мне нужно переключиться на chrome и обновить вкладку проекта. Удобно сделать так, когда в файлы вносятся правки, а на другом экране окно браузера с проектом автоматически перезагружается с новым кодом.

    Решение


    Эту проблему я решил с помощью пакета gulp-livereload и расширения livereload для chrome. Это расширение доступно и для других браузеров: http://livereload.com/.

    Установил необходимый пакет:
    npm install --save-dev gulp-livereload
    

    Подключил пакет:
    var livereload = require('gulp-livereload');
    

    Добавил команду запуска сервера в задачу watch:scripts:
      // запускаем сервер
      livereload.listen();
    

    Добавил команду перезагрузки сервера в задачу scripts:
    gulp.src(components.scripts.source)
        .pipe(browserify(components.scripts.options))
        .pipe(gulpif(argv.production, uglify()))
        .pipe(gulp.dest(components.scripts.dest))
        .pipe(livereload()); // <- добавляем вот эту строчку
    

    Запустил gulp watch:
    gulp watch
    

    Установил расширение chrome livereload. Скажем, папка web моего проекта доступна по адресу gulpfile.habrahabr.ru.local. Я открываю его в браузере, клацаю на кнопку Enable LiveReload в баре и готово! При каждом сохранении каких-либо файлов, отслеживаемых задачей watch, страница браузера автоматически перезагружается.

    Результат


    Дополненный gulpfile.js в архиве — gulpfile-2-livereload.js.

    Глава 3 — Перехват ошибок допущенных в проекте при разработке


    Проблема


    Если я допускал ошибку в скриптах проекта, то работающая задача gulp watch останавливалась с ошибкой при компиляции. Хотелось чтобы такого не происходило и gulp watch продолжал работать.

    Решение


    Для себя я решил пррблему так: создал отдельную функцию для перехвата ошибок с логгированием в консоль.
    /**
     * Обработчик ошибок.
     * @param e
     */
    var error = function (e) {
      console.error('Error in plugin "' + e.plugin + '"');
      console.error('   "' + e.message + '"');
      console.error('   In file "' + e.fileName + '", line "' + e.lineNumber + '".');
      console.log('--------------------------------------');
      console.log(e);
    };
    

    Добавил обработку события в задачу gulp scripts:
      gulp.src(components.scripts.source)
        .pipe(browserify(components.scripts.options).on('error', error)) // <- добавил .on('error', error) на этой строчке
        .pipe(gulpif(argv.production, uglify()))
        .pipe(gulp.dest(components.scripts.dest))
        .pipe(livereload());
    

    Теперь, даже если я допускаю ошибку в коде, то я вижу уведомление в консоли и мне не нужно заново запускать gulp watch.

    Результат


    Дополненный gulpfile.js в архиве — gulpfile-3-error.js.

    Заключение


    В этой статье я не хотел пересказывать документацию или рассказать что-то инновационное, а поделился своим опытом работы с gulp и важными для себя выводами. Все документации доступны по указанным в статье ссылкам.

    Также хочется услышать мнение опытных и знающих людей как по поводу технической части, так и по поводу текста: понятен ли материал, может где-то стоит по-другому сказать или что-то еще. Я бы хотел научиться доносить свою мысль до людей :) Спасибо за внимание!

    Посмотреть пример.
    Скачать пример архивом.

    Дополнения к статье


    Обновление от 06.11.2015 в 12:22
    Для обработки ошибок есть gulp-plumber
    agatische
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 11

      +2
      gulp.watch при изменении любого файла будет полностью пересобирать скрипты. При 2-х файлах это нормально. А если файлов 100? А если каждый из них прогоняется через babel? Инкрементальная сборка маст хев.

      livereload стили применяет также с перезагрузкой страницы? Применение новых стилей без перезагрузки страницы маст хев.

      И да, пересаживайтесь на webpack (говорю, как человек, который долгое время сидел на gulp)
        0
        Для того чтобы не пересобирать все файлы есть плагин gulp-changed (ну если только конечно вы в один файл все не компилите)
          0
          Ок, а что будет происходить, если вы файл удалили? Сначала собрали с ним, потом решили переименовать. Или совсем он вам не нужен. Что будет в этом случае?
            0
            Еще интересный вопрос — есть у вас 10 вендорных библиотек. В каждой по css-файлу. В каждом css-файле ссылки на изображения и шрифты. Где-то шрифты лежат в ../fonts, где-то в ./fonts, где-то прямо в ./, то же самое с картинками.
            Как будете собирать весь вендорный css в один файл, чтобы пути не поломать?
          0
          Для обработки ошибок есть gulp-plumber.
            0
            1. Проверку переданных аргументов удобно делать так же при помощи плагина gulp-util, помимо этого там есть еще набор приятных вкусняшек.
            2. Livereload'ов на свете много развелось, мне очень нравится browser-sync. Про использование в разных окружениях, в т.ч. с gulp, можно почитать тут. Там тоже много хороших возможностей, например, синхронизация действий в разных браузерах, возможность открывать сайт на разных машинах в пределах локальной сети и еще много разного.
            3. Про перехват ошибок уже выше написали — gulp-plumber для этого хорош.

            И еще — постарайтесь не держать все таски в одном файле, а для каждого создавать отдельный, а в основном gulpfile.js реквайрить директорию с тасками — так будет проще переносить их из проекта в проект, оставляя только нужные в каждом конкретном случае.
              0
              А еще вы однажды можете столкнуться с проблемами со стандартным gulp'овским вотчером (не будет следить за вновь созданными файлами, удаленными, например) — тогда обратите внимание на chokidar.
              0
              Есть несколько нерешённых проблем. С watch — проблема при сборке более одного файла за один цикл «смотрения». Т.е загружаем 2 файла — сборка отрабатывает одновременно дважды, всё херится(чаще всего).
              Вторая проблема — после того как вся сборка выполнилась ещё некоторое время что-то происходит. Я _догадываюсь_ что это — выгрузка из памяти на диск, но этот период довольно большой и никакой индикации я так и не настроил.

              Ну и бонусом — я так и не понял как следить за репозиторием, Mercurial там или Git, что бы на продакшене пересборку после пуша в мастер обновлять.
                0
                Gulp — это система сборки проекта, на продакшне обычно его задача — просто собрать конечные файлы и остановиться, а вотчеры используются для разработки. Если нужно следить за репозиторием, то можно написать bash-скрипт, который при изменениях будет запускать gulp для пересборки. Можно, например, вот это допилить под себя.
                  0
                  Никто не мешает настроить git hooks в случае использвоания одноименной СКВ, возможно что в других СКВ есть аналогичный функционал.
                0
                А я порекомендую свое детище — TARS. К тому же вы сможете с легкостью встроить все свое в него. Или наоборот, что-то вытащите для себя из него.

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое