Grunt для тех, кто считает штуки вроде него странными и сложными

Фронтенд-разработчикам часто советуют делать следующие вещи:

Конечно, этот список далеко не полон, но это такие вещи, которые делать просто необходимо. Вы могли бы смело назвать эти советы задачами.

Бьюсь об заклад, вы наверняка уже что-то слышали про Grunt. Что ж, Grunt — это, по сути, планировщик задач. Grunt может делать все эти задачи за вас. Стоит лишь установить его, что, кстати, не так уж и сложно, и эти операции будут происходить автоматически, так, что вам про них даже не придётся вспоминать.

Но, давайте посмотрим правде в глаза. Grunt — одна из тех новомодных штук, которой пользуются все эти крутые парни, но которая на первый взгляд выглядит странно и пугающе. Я вас понимаю. И эта статья для вас.

Пресекаем заблуждения в зародыше

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

Мне не нужно то, что делает Grunt

Возможно, так оно и есть. Проверьте-ка ещё раз тот список задач в начале статьи. Эти рекомендации не просто неплохо было бы выполнять. Они — жизненно важная часть процесса веб–разработки в наше время. Если вы их уже соблюдаете, это замечательно. Вероятно, вы используете для этого разнообразные утилиты. Grunt помогает собрать их все, так сказать, под одной крышей. А если вы их ещё не используете, то, возможно, стоило бы, и тут вам может помочь Grunt. Ну а потом, когда вы уже будете их использовать, Grunt может сделать для вас ещё большее, что, в конечном итоге, будет означать, что вы лучше делаете свою работу.

Grunt работает на Node.js, а я его не знаю

Вам и не надо знать Node. Точно так же, как вам не нужно знать Ruby, чтобы пользоваться Sass. Или PHP, чтобы пользоваться WordPress. Или C++, чтобы пользоваться Microsoft Word.

Вещи, которые делает Grunt, я и так могу сделать, по-другому

Правда? Они все собраны в одном месте, настроены так, что запускаются автоматически, когда понадобятся, и есть у каждого члена команды, который работает над проектом? Рискну предположить, что это не так.

Grunt — консольная программа, а я всего лишь дизайнер

Я тоже дизайнер. И я предпочитаю использовать в работе приложения с графическим интерфейсом, если, конечно, он существует. Но я не думаю, что у Grunt1 такой интерфейс появится в ближайшее время.

Степень, в которой придётся работать с командной строкой, примерно такая:

  1. Перейти в папку с проектом.
  2. Напечатать grunt и нажать Return.

После установки, которая, повторюсь, не такая уж и сложная.

Хорошо. Устанавливаем Grunt

Необходимое условие для установки Grunt - наличие Node.js. Если Node.js у Вас не установлен, не беспокойтесь, это очень легко. вы просто скачиваете установочный файл и запускаете его. Давайте, просто кликните по большой кнопке Install на сайте Node.js.

Сам Grunt устанавливается на каждый проект отдельно. Вам нужно зайти в папку проекта и создать небольшой файл под названием package.json в его корне.

package.json в корне проекта

Содержимое файла package.json должно быть таким:

{
  "name": "example-project",
  "version": "0.1.0",
  "devDependencies": {
    "grunt": "~0.4.1"
  }
}

Измените название проекта и укажите актуальную версию, главное, объявление devDependencies должно остаться таким же, как есть.

Это то, как Node обозначает зависимости в проекте. В Node для этого есть специальный пакетный менеджер под названием NPM.

Как только package.json будет находиться в нужном вам месте, откройте терминал, и перейдите в папку проекта. Парни вроде меня, которые мало смыслят в терминале, делают это примерно так:

Колхозный способ смены папки в терминале

Далее запустите команду:

npm install

После того, как она будет выполнена, в проекте появится новая папка с именем node_modules.

Пример папки node_modules

Другие файлы, которые вы видите на анимации в корне моего проекта, README.md и LICENSE, находятся там только потому, что я собираюсь выложить этот проект на Github, они не относятся непосредственно к установке Grunt.

Последний шаг — установка Grunt CLI (command line interface, интерфейс командной строки). Это то, что заставит терминал обрабатывать команду grunt. Без неё запуск grunt вернёт ошибку «Command Not Found» («Команда или файл не найдена»). Она устанавливается отдельно по причинам эффективности. Иначе, если бы у вас был десяток проектов, пришлось бы устанавливать десять копий GruntCLI.

Установка GruntCLI — всего одна строчка в терминале. Просто запустите команду:

npm install -g grunt-cli

После этого следует закрыть и заново открыть окно терминала. Это вообще хороший подход для того, чтобы убедиться, что всё работает как надо. Типа как перезагрузить компьютер после установки новой программы в старые добрые времена.

Давайте заставим Grunt объединить пару файлов

Представим, что в нашем проекте есть три отдельных файла JavaScript:

  1. jquery.js — библиотека, которую мы используем.
  2. carousel.js — плагин для jQuery, который мы используем.
  3. global.js — написанный нами файл, где мы настраиваем и вызываем наш плагин.

На продакшене нам надо объединить все эти файлы в один, для улучшения производительности (один http-запрос лучше, чем три). Нам нужно сказать Grunt, чтобы он это сделал для нас.

Но постойте, Grunt на самом деле ничего сам по себе не делает. Вы же помните, что Grunt — это всего лишь утилита для выполнения задач. А сами задачи нам придётся добавить. Пока что мы ещё не настраивали Grunt, чтобы он что-либо делал, поэтому давайте этим займёмся.

Официальный плагин Grunt для объединения файлов — grunt-contrib-concat

npm install grunt-contrib-concat --save-dev

Приятная особенность такого способа установки в том, что файл конфигурации package.json будет автоматически обновлён, и в него пропишется новая зависимость. Откройте его и убедитесь. Там появится новая строка:

"grunt-contrib-concat": "~0.3.0"

Вот теперь мы готовы использовать этот плагин. А чтобы его использовать, нам нужно начать настраивать Grunt и объяснять ему, что делать.

Указать Grunt, что ему делать, можно при помощи конфигурационного файла с названием Gruntfile.js2.

Точно так же, как и у файла package.json, у Gruntfile.js есть свой формат, которого нужно придерживаться. Я бы не стал заморачиваться по поводу того, что значит в нём каждое слово. Просто посмотрите на этот пример:

module.exports = function(grunt) {

    // 1. Вся настройка находится здесь
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),

        concat: {
            // 2. Настройка для объединения файлов находится тут
        }

    });

    // 3. Тут мы указываем Grunt, что хотим использовать этот плагин
    grunt.loadNpmTasks('grunt-contrib-concat');

    // 4. Указываем, какие задачи выполняются, когда мы вводим «grunt» в терминале
    grunt.registerTask('default', ['concat']);

};

Теперь нам нужно создать конфигурационный файл. Документация может быть ошеломляющей. Давайте сосредоточимся на простеньком примере.

Помните, у нас есть три файла JavaScript, которые мы пытаемся объединить. Мы перечислим пути к ним в src в качестве массива с путями к файлам (как строки в кавычках), и затем мы укажем файл назначения как dest. Файл назначения не обязательно должен существовать на данном этапе. Он будет создан в процессе, когда будет запущена соответствующая задача, которая склеит все файлы в один.

И jquery.js, и carousel.js являются библиотеками. Мы, скорее всего, не будем их изменять. И, для порядка в проекте, поместим их в отдельной папке /js/libs/. Файл global.js — то место, где мы пишем наш собственный код, поэтому он будет находиться прямо в корне папки /js/. Теперь нужно объяснить Grunt, как найти все эти файлы и склеить их в один файл production.js, названный так, чтобы показать, что он будет использоваться уже на настоящем, живом сайте.

concat: {
    dist: {
        src: [
            'js/libs/*.js', // Все JS в папке libs
            'js/global.js'  // Конкретный файл
        ],
        dest: 'js/build/production.js',
    }
}

Примечание: далее в этой статье будут приводиться такие же небольшие кусочки конфигурационного кода, как этот. Их цель — обратить внимание на важные места, однако, поначалу вы можете не понять, как каждый кусок будет интегрирован в большой файл. Если вы запутаетесь, и вам нужно будет посмотреть на эти примеры в контексте, просмотрите готовый полный файл конфигурации.

Когда конфигурация concat окажется на своем месте, откройте терминал и запустите команду:

grunt

И, смотрите, что происходит! Создан файл production.js, который является превосходным объединением трёх файлов. Лично для меня это был такой момент «Ух ты!». Незабываемое ощущение могущества, текущего в моих венах. Давайте наклепаем побольше таких штук!

А теперь заставим Grunt минифицировать наш JavaScript

Мы проделали столько дел для подготовки на предыдущих этапах, так что добавить ещё одну задачу для Grunt теперь относительно легко. Вот всё, что нужно сделать:

  1. Найти плагин для Grunt, который делает то, что нам нужно;
  2. Изучить стиль конфигурации этого плагина;
  3. Написать конфигурацию в соответствии с нашим проектом.

Необходимый нам плагин для минификации кода — grunt-contrib-uglify. Точно так же, как и в прошлый раз, мы запускаем команду npm для его инсталяции.

npm install grunt-contrib-uglify --save-dev

Затем меняем содержимое файла Gruntfile.js, для загрузки плагина:

grunt.loadNpmTasks('grunt-contrib-uglify');

И после этого конфигурируем его:

uglify: {
    build: {
        src: 'js/build/production.js',
        dest: 'js/build/production.min.js'
    }
}

Теперь обновим задачу по умолчанию (default), чтобы она запускала ещё и минификацию:

grunt.registerTask('default', ['concat', 'uglify']);

Очень-очень похоже на то, как настраивалось объединение файлов, верно?

Запускаем команду grunt в терминале и получаем на выходе немного изысканного, минифицированного JavaScript кода:

Minified JavaScript

Тот самый файл, production.min.js, который мы будем подключать в index.html.

Настраиваем Grunt на оптимизацию наших изображений

Теперь, когда вы уже освоили процесс, нужно лишь ещё раз пройти его по шагам. Нужный нам плагин Grunt для оптимизации изображений — grunt-contrib-imagemin. Установим его:

npm install grunt-contrib-imagemin --save-dev

Зарегистрируем в Gruntfile.js:

grunt.loadNpmTasks('grunt-contrib-imagemin');

Настроим:

imagemin: {
    dynamic: {
        files: [{
            expand: true,
            cwd: 'images/',
            src: ['**/*.{png,jpg,gif}'],
            dest: 'images/build/'
        }]
    }
}

Убедимся, что он запускается:

grunt.registerTask('default', ['concat', 'uglify', 'imagemin']);

Запускаем grunt и наблюдаем великолепное сжатие изображений:

Минифицированные изображения

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

Проявим немного сообразительности и автоматизируем процесс

То, что мы уже сделали, само по себе невероятно круто и очень полезно. Но, кое где можно сделать еще лучше, и тем самым облегчить и улучшить процесс сборки, как для себя, так и для Grunt.

  1. Запускать наши задачи по необходимости автоматически;
  2. Запускать только те задачи, которые нужны в данный момент.

Например:

  1. Объединять и минифицировать код JavaScript, когда он изменился;
  2. Оптимизировать изображения, если добавилось новое, или обновилось уже добавленное в проект.

Добиться этого очень легко, надо лишь отслеживать изменения в файлах проекта. Мы можем попросить Grunt приглядывать за файлами, и, если произошли изменения, запускать определённые задачи. Отслеживание производится при помощи плагина grunt-contrib-watch.

Я позволю вам установить его самостоятельно. Установка этого плагина ничем не отличается от предыдущих. Настроим его, указав в свойстве watch конкретные файлы (или папки, или и то и другое), за которыми нужно следить. Под отслеживанием файлов я подразумеваю наблюдение за их изменением, удалением и добавлением. Затем мы указываем, какие задачи необходимо запустить, если такое изменение произошло.

Проще говоря, сейчас мы бы хотели, чтобы при наличии изменений в папке /js/ запускались процессы по объединению и минификации файлов. Или, другими словами, когда это случится, нам нужно, что бы Grunt запустил тип задач, связанных с JavaScript. Но, если что-нибудь произойдёт в другом месте, нам, наоборот, не нужно запускать эти задачи, потому как они будут бесполезны. Итак:

watch: {
    scripts: {
        files: ['js/*.js'],
        tasks: ['concat', 'uglify'],
        options: {
            spawn: false,
        },
    }
}

Пока все довольно просто, да? Единственная, немного странная опция — это spawn. И знаете, что? Я понятия не имею, что она обозначает. Из документации я лишь понял, что, по хорошему, её лучше не менять из значения по умолчанию. Ну, мы разрабатываем в реальном мире. Просто не трогайте эту настройку, пока всё работает, а если что-то пошло не так, принимайтесь за чтение мануала.

Примечание: Грустно, когда что-то так просто выглядит в учебнике, но у вас по какой-то загадочной причине не работает, правда? Если вы не можете заставить Grunt работать после каких-то изменений, то, скорее всего, причина в синтаксической ошибке в файле Gruntfile.js. В терминале это выглядит примерно так:

Ошибка при запуске Grunt

Обычно, Grunt неплохо объясняет, что именно пошло не так, так что не забывайте проверять сообщения об ошибках. В моем случае произошла ошибка синтаксиса, меня подвела пропущенная запятая. Нужно её добавить, и всё снова заработает.

Заставим Grunt работать с нашим препроцессором

Последний пункт нашего списка в начале статьи — использование Sass. И вновь, Grunt отлично подходит для этой задачи. Но, спросите вы, как же так? Разве Sass работает не на Ruby? Да, это действительно так. Правда, есть версия Sass, работающая на Node, но, она не добавляет в проект дополнительные зависимости, да и не такая свежая по сравнению с версией на Ruby. Так что мы будем использовать официальный плагин grunt-contrib-sass, который просто считает, что Sass уже установлен на вашей машине. Если это не так, изучите инструкцию по установке Saas из командной строки.

Действительно крутая фишка Sass в том, что он самостоятельно производит объединение и минификацию файлов. Так что в нашем маленьком проекте достаточно просто скомпилировать наш основной файл global.scss:

sass: {
    dist: {
        options: {
            style: 'compressed'
        },
        files: {
            'css/build/global.css': 'css/global.scss'
        }
    }
}

И, конечно, нам бы не хотелось запускать эту задачу вручную. У нас ведь уже есть плагин для отслеживания изменений, так что давайте его используем! В конфигурации watch добавим ещё одну подзадачу:

css: {
    files: ['css/*.scss'],
    tasks: ['sass'],
    options: {
        spawn: false,
    }
}

Готово. И теперь, всякий раз, когда мы вносим изменения в Sass файлы, исходящий CSS файл будет автоматически обновляться.

Давайте пойдём немного дальше, это определённо того стоит, и добавим поддержку LiveReload. С LiveReload вам больше не придётся переключаться в окно браузера и обновлять страницу. Страница будет обновляться автоматически, а в случае CSS, новые стили будут внедряться в страницу также, без перезагрузки. Особенно полезно для тех сайтов, отображение которых зависит от различного состояния страниц.

Это очень легко устроить, потому как LiveReload уже включён в плагин watch. Всё, что нам надо сделать:

  1. Установить плагин для браузера;

  2. Добавить пару строк в настройки watch: watch: { options: { livereload: true, }, scripts: { /* и т.д. */

  3. Перезапустить браузер и щёлкнуть на иконку LiveReload, чтобы включить его;

  4. Отредактировать файл Sass и увидеть, как изменения появляются на странице без перезагрузки.

Страница обновляется на лету

Загляденье.

Предпочитаете видео?

Если вы лучше воспринимаете информацию в формате видеоуроков, то, специально для вас я подготовил небольшой скринкаст, отлично дополняющий эту статью. Он опубликован на CSS-Tricks: First Moments with Grunt

Получаем level-up

Можете представить, какое море улучшений для вашего рабочего процесса вам теперь доступно? Наверняка, это станет должностью на полный рабочий день в некоторых организациях.

Какие-нибудь заумные приверженцы DevOps могут смотреть свысока на нашу простую конфигурацию. Но я бы посоветовал им умерить пыл. Даже то, что мы сделали уже сейчас чрезвычайно значимый шаг. И не забывайте, что это всё бесплатно, с открытыми исходниками, и это поразительно.

Вы можете получить настоящий level-up, используя следующие полезные задачи Grunt:

А также вы можете прокачаться ещё больше, просто поняв принцип работы самого Grunt:

Делимся знаниями

Я думаю, в завершении этой темы было бы неплохо организовать обмен знаниями между заинтересованными сторонами. Если вы установили Grunt в первый раз (или хорошо помните, как это делали), будьте особенно внимательны к неприятным мелочам, которые вы испытали и превозмогли. Это — те мелочи, которыми следует поделиться тут, в комментариях. Так мы сделаем из этой статьи полезный ресурс, помогающий преодолеть эти неприятные моменты без смущения. Мы все, вместе, занимаемся этой штукой!


Примечания

1. Может быть, кто-нибудь когда-нибудь сделает красивый UI под Grunt для вашей операционной системы. Но я до конца не уверен, что этот день наступит. Конфигурация плагинов — это важный атрибут использования Grunt. Каждый плагин немного отличается от остальных, в зависимости от того, что он делает. Это означает уникальный интерфейс для каждого отдельного плагина, и его воплощение — довольно рискованное занятие.

Возможно, неплохим компромиссом станет плагин Grunt DevTools для Chrome.

2. Gruntfile.js в документации и примерах часто называется Gruntfile. Не называйте этот файл буквально, Gruntfile, он не заработает.