Используем Grunt для облегчения жизни фронтенд-разработчика

Современный сайт — это уже далеко не набор HTML-файлов и картинок с несколькими таблицами стилей и скриптами, которые можно просто выложить на сервер как есть. Теперь для разработки используют множество инструментов: CSS-препроцессоры, минификаторы и склейщики скриптов, оптимизаторы картинок, генераторы спрайтов и многое другое. Запускать все руками быстро надоест, да и легко забыть что-то и наделать ошибок. Поэтому было бы неплохо это все автоматизировать.

Что такое системы сборки

Наверняка ты хотя бы раз слышал слова «мейк» или «мейкфайл». Это файл (обычно он так и называется: Makefile) сценария сборки, очень похожий на shell-скрипт. Система сборки читает этот файл и автоматически выполняет рутинные действия, экономя разработчику время и нервы. Make — одна из самых известных и старых систем сборки. Ее используют суровые программисты для автоматизации компиляции программ и других повторяющихся процессов, чтобы не вводить каждый раз длинные и сложные команды в терминале. Кроме Make, существует множество других систем сборки — Rake, Cake, Ant. Все они имеют свои преимущества, но и у всех есть один общий недостаток — они не приспособлены для нужд frontend-разработчика.

Конечно, при желании ты можешь приспособить любую из этих систем сборки для минификации JS или компиляции CoffeeScript, но на выходе ты получишь громозкий и негибкий инструмент, при этом потратив много времени. Куда удобнее было бы использовать специально адаптированные тулзы для решения повседневных задач фронтендщиков. Наибольшей популярностью у девелоперов пользуется система сборки Grunt.JS (gruntjs.com), причем вполне заслуженно. У Гранта есть несколько плюсов, которые отличают его от других систем сборки.

Во-первых, Грант создавался специально для фронтенд-разработчиков. С помощью плагинов (их уже несколько сотен) можно автоматизировать большинство повседневных задач клиентского разработчика.

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

В-третьих, благодаря использованию Node.js он легко устанавливается на всех популярных платформах: Mac, Windows и Linux.

Установка

Для использования Гранта тебе понадобится [установить Node.js][]. Вместе с ним установится менеджер пакетов npm, который понадобится для установки как самого Гранта, так и плагинов.

Для начала нужно установить консольную утилиту grunt, которая будет запускать Грант, установленный в папке твоего проекта.

$ npm install grunt-cli -g 

Ключ -g означает, что пакет будет установлен глобально (а не только для текущего проекта). Вероятно, понадобится запустить npm через sudo или открыть консоль под администратором.

Начало работы

Для использования в проекте Гранта нужно создать файл конфигурации — «грантфайл». Это обычный JS-файл с именем Gruntfile.js (можно использовать и CoffeeScript, тогда файл должен называться Gruntfile.coffee).

Грантфайл выглядит примерно так:

// Обязательная обертка module.exports = function(grunt) {  // Настройка задач grunt.initConfig({ // Склеиваем все JS-файлы в папке в один файл concat: { main: { src: 'js/**/*.js', dest: 'build/scripts.js' } } });  // Загрузка плагинов, установленных с помощью npm install grunt.loadNpmTasks('grunt-contrib-concat');  // Задача по умолчанию (она обязательно должна быть) grunt.registerTask('default', ['concat']); }; 

В грантфайле описываются параметры задач (у нас пока одна — concat — склеивание нескольких файлов в один), подключаются плагины (о них ниже), задаются группы задач (предпоследняя строчка).

В Гранте нет встроенных задач, для любой задачи нужно устанавливать плагин. Но сначала нужно создать еще один файл, который сильно облегчит работу с Грантом. Выполни npm init и ответь на вопросы, которые он задаст. (Можно просто нажимать — эти поля нам не понадобятся.) Теперь у нас появился файл package.json: в нем npm будет сохранять имена и версии установленных пакетов. Это позволит устанавливать все зависимости (Грант и плагины) одной командой (npm install) и быть уверенным, что проект не перестанет собираться из-за новой версии какого-то плагина.

Теперь можно установить первый плагин (и сам Грант):

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

Ключ --save-dev в дополнение к установке добавляет ссылку на пакет в package.json.

Запуск

Просто набери в терминале:

$ grunt 

Грант выполнит задачу default, которая запустит задачу concat.

Во время разработки удобно запускать Грант с ключом --debug. Задачи могут использовать его по-разному. Например, grunt-contrib-stylus в отладочном режиме не сжимает CSS-код.

$ grunt --debug 

Еще два ключа, которые могут пригодиться, если что-то пойдет не так: --verbose будет выводить подробную информацию обо всем, что делает Грант в данный момент, --stack покажет stack trace, если выполнение было прервано из-за ошибки JavaScript.

Конфигурация

 

Задачи, подзадачи, параметры

Подробнее разберем внутренности грантфайла. Конфигурация большинства задач выглядит примерно так:

concat: { options: { separator: ';' }, libs { src: 'js/libs/**/*.js', dest: 'build/libs.js' }, main: { src: [ 'js/mylibs/*.js', 'js/main.js' ], dest: 'build/scripts.js' } } 

Concat — это задача. Обычно имя задачи совпадает с именем грант-плагина, который за нее отвечает. В нашем случае это плагин grunt-contrib-concat. В других системах сборки задачи обычно называют целями (target).

Concat:libs и concat:main — это подзадачи, они позволяют запускать одну задачу для разных исходных файлов. В options определяются общие для всех подзадач параметры.

Можно принудительно запустить любую задачу или подзадачу:

$ grunt concat  # Задача concat $ grunt concat:main  # Подзадача concat:main $ grunt concat uglify # Задачи concat и uglify 
 

Списки файлов

Список исходных файлов можно задать тремя способами: один файл, массив файлов и генератор. Один файл:

'js/main.js' 

Массив:

[ 'js/utils.js', 'js/main.js' ] 

Можно использовать маски (glob, * — любые символы, /**/ — папка любой вложенности). Однако с масками нужно иметь в виду, что порядок файлов может оказаться любым. Это может привести к проблемам, например при склейке CSS- или JS-файлов.

[ 'js/libs/*.js', 'js/mylibs/**/*.js' ] 

Динамическая генерация списка сложнее, но и функциональнее.

{ expand: true, // Включение динамической генерации cwd: 'lib/',  // Родительская папка исходных файлов src: ['**/*.js'], // Маска исходных файлов (относительно cwd) dest: 'build/', // Папка для результирующих файлов // Необязательные параметры ext: '.min.js', // Расширение результирующих файлов flatten: true,  // Складывать все файлы в одну папку (без подпапок) rename: function(name) { return name.replace('big', 'small'); } } 
 

Шаблоны

Внутри параметров конфига можно использовать шаблоны. Грант использует шаблонизатор из библиотеки [Lo-Dash][].

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

concat: { main: { src: 'js/*.js', dest: 'build/scripts.js' } }, uglify: { main: { files: { // «Копируем» другой параметр конфига. Добавляем текущую дату в имя файла build.<%= grunt.template.today("m-d-yyyy") %>.js': '<%= concat.main.dest %> } } } 

А вот так можно использовать данные из JSON-файла:

pkg: grunt.file.readJSON('package.json'), banner: '/* <%= pkg.name %> v<%= pkg.version %> */' uglify: { main: { files: { '<%= pkg.name %>.min.js': '<%= pkg.name %>.js' } } } 

Grunt.template.today и grunt.file.readJSON — функции из API Гранта. Там довольно много удобных функций, которые можно использовать при написании собственных задач (об этом мы поговорим чуть позже).

 

Быстрое подключение плагинов

Думаю, тебе скоро надоест добавлять для каждого плагина строчку grunt.loadNpmTasks(‘имяплагина’), поэтому лучше сразу заменить все вызовы loadNpmTasks одной строкой:

require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); 

И установить matchdep:

$ npm install matchdep --save-dev 

Это строка вызовет loadNpmTasks для всех плагинов установленных с ключом –save-dev.

Решение повседневных задач

 

Склеивание JS-файлов

Для склеивания любых файлов используется плагин contrib-concat. В простейшем случае его конфигурация выглядит так:

concat: { main: { src: ['js/jquery.js', 'js/utils.js', 'js/main.js'], dest: 'build/scripts.js' } } 

Не забудь добавить в грантфайл плагин grunt-contrib-concat:

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

Добавь задачу в default, чтобы она запускалась при запуске Гранта без параметров:

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

И установи соответствующий пакет из npm:

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

Минификация JS-файлов

Плагин contrib-uglify сжимает JavaScript с помощью UglifyJS.

uglify: { main: { files: { 'build/scripts.min.js': '<%= concat.main.dest %>' } } } 

Вместо <%= concat.main.dest %> будет подставлен путь результирующего файла задачи concat (предыдущий раздел).

Часто нужно добавить в начало сжатого файла комментарий с копирайтом. Это можно сделать с помощью свойства banner.

uglify: { options: { banner: '/* Author: John Smith, <%= grunt.template.today("yyyy") %> */' }, main: { files: { … } } } 
 

Запуск CSS-препроцессоров

Для Гранта есть плагины для всех популярных препроцессоров: SASS, LESS и Stylus (contrib-sass, contrib-less и contrib-stylus). Я использую Stylus, поэтому и покажу на примере этого препроцессора.

stylus: { main: { files: { 'build/styles.css': 'styles/index.styl' } } } 
 

JSHINT

Для проверки JavaScript на ошибки есть плагин contrib-jshint.

jshint: { options: { browser: true, white: false, smarttabs: true, eqeqeq: true, immed: true, latedef: false, newcap: true, undef: true, trailing: true, jquery: true }, files: 'js/**/*.js' } 

Удобнее хранить параметры JSHint в отдельном JSON-файле — jshintrc.

jshint: { options: { jshintrc: '.jshintrc' }, files: 'js/**/*.js' } 

Этот файл может быть использован не только Грантом, но и, например, консольным JSHint или плагином SublimeLinter для Sublime Text.

 

Отслеживание изменений

Плагин contrib-watch запускает задачи при каждом изменении указанных файлов. Например, можно запускать CSS-препроцессор при каждом изменении исходных файлов:

stylus: { main: { 'build/styles.css': 'styles/index.styl' } } watch: { stylus: { files: 'styles/**/*.styl', tasks: 'stylus' } } 

Недавно в contrib-watch появилась возможность использовать LiveReload (раньше для этого нужен был отдельный плагин):

watch: { stylus: { files: 'styles/**/*.styl', tasks: 'stylus', options: { livereload: true }, } } 

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

А еще можно запускать сразу несколько задач при изменении файлов:

watch: { stylus: { files: 'styles/**/*.styl', tasks: ['stylus', 'autoprefixer', 'csso'] } } 
 

Веб-сервер

Простейший веб-сервер для статических сайтов — плагин contrib-connect. У него есть одна особенность, о которой нужно помнить: сервер будет работать, пока выполняются какие-то другие задачи. Чтобы сервер не закрывался сразу после запуска, нужно использовать параметр keepalive:

connect: { test: { options: { port: 8000, base: '.', keepalive: true } } } 

или запускать задачу connect одновременно с какой-то длительной задачей, например watch:

connect: { test: { options: { port: 8000, base: '.' } } }, watch: { ... } 

В обоих вариантах твой сайт доступен по адресу http://localhost:8000/.

Как написать свою задачу

Свои задачи делать довольно просто. Для примера сделаем задачу, которая будет запускать из Гранта консольный оптимизатор веб-графики [imgo][]. Стоит рассматривать эту задачу только как пример. Для реальной работы лучше использовать [grunt-imgo][] или [grunt-contrib-imagemi][].

 

Конфиг

Задача будет принимать список изображений и запускать imgo для каждого файла. Для этого нам понадобится всего лишь один параметр:

imgo: { images: { src: 'images/**' } } 
 

Код задачи

Добавить задачу можно двумя функциями:

  • grunt.registerMultiTask — задача с подзадачами, как concat, uglify и как описано в подразделе «Задачи, подзадачи, параметры» выше. Нам нужна именно такая;
  • grunt.registerTask — используется для задач-ссылок (как default выше) или задач, где несколько наборов входных данных не имеют смысла.

Вот, как это выглядит:

// Добавляем задачу imgo  grunt.registerMultiTask('imgo', 'Optimize images using imgo', function () { // Говорит о том, что вся задача асинхронная var done = this.async(); // Обрабатываем каждый файл (тоже асинхронно, потому что spawn() асинхронный) // В this.filesSrc находится список файлов текущей подзадачи с уже развернутыми масками. // (Другие параметры были бы в this.data.) grunt.util.async.forEach(this.filesSrc, function (file, next) { // Создаем процесс imgo, передаем ему имя текущего файла grunt.util.spawn({ cmd: 'imgo', args: [file] }, next); }, done); }); 

Задача должна быть асинхронной, потому что мы будем вызывать внешнюю программу, а в Node.js это асинхронная операция. Вызов this.async() говорит Гранту, что наша задача асинхронная, и возвращает функцию, которую необходимо вызвать, когда все файлы будут обработаны.

Сам цикл по исходным файлам тоже асинхронный. Для этого используется метод forEach из модуля async.

Хранение и использование

Задачи можно класть прямо в грантфайл, а можно в отдельные файлы или публиковать в npm (если твоя задача может быть полезна и другим людям).

Первый способ самый простой. Для этого надо разместить код задачи где-нибудь перед grunt.registerTask(‘default’, ).

Во втором случае нужно создать для задач отдельную папку и поместить код задачи в такую же обертку, как и у грантфайла:

module.exports = function(grunt) { grunt.registerMultiTask('imgo', 'Optimize images using imgo', function() { /* ... */ }); }; 

А в грантфайле написать:

grunt.loadTasks('tasks');  // Загружает все задачи из папки tasks 

Альтернативы гранта

Грант — далеко не единственный инструмент для автоматизации работы разработчика. Хотя, на мой вкус, он наиболее удобен для фронтенд-разработчика.

Условно такие инструменты можно разделить на три группы.

 

Классические системы сборки: Make, Ant и множество других

К недостаткам можно отнести неприспособленность к задачам фронтенд-разработчика (придется много делать самому то, что в Гранте уже есть готовое) и необходимость изучать новый язык написания скриптов (вместо привычного яваскрипта). Также тебе придется отдельно устанавливать все утилиты, которые будут использоваться для сборки: uglify, coffee и другие. Но если ты уже использовал такие инструменты в другой среде, то использование их для фронтенда может быть оправданным.

 

Консольные системы сборки для фронтенд-разработчиков

Из наиболее популярных стоит отметить Brunch и Yeoman.

  • Brunch гораздо проще в использовании, но менее гибкий, и для него существует всего лишь десятка три плагинов (для Гранта уже несколько сотен). В Бранче есть встроенный генератор проектов (есть разные «скелеты»: Backbone-приложения, Angular-приложения и другие), веб-сервер, вотчер и плагины для компиляторов, препроцессоров, минификаторов и тому подобного. Бранч позволяет двумя командами в терминале развернуть веб-приложение, запустить веб-сервер и начать с ним работать. При этом после каждого изменения оно будет автоматически пересобираться, а страничка в браузере — перезагружаться.
  • Yeoman с трудом можно назвать альтернативой Гранта, потому что это скорее продолжение Гранта. Йомен состоит из трех частей (все они могут использоваться как отдельные инструменты): Yo — генератор приложений (более продвинутый аналог grunt-init), Grunt и Bower — менеджер пакетов для фронтенда (как npm, но для браузера). Раньше для вызова всех трех инструментов использовалась команда yeoman, но сейчас yo, grunt и bower используются сами по себе, а Йомен теперь скорее субъективное воркфлоу, основанное на этих инструментах.
 

Системы сборки для фронтенд-разработчиков с графическим интерфейсом

Очевидный плюс — графический интерфейс и простота установки и использования. Минусы: сложность (а то и невозможность) расширения и настройки; невозможность хранить конфигурацию сборки в самом проекте; ограниченная поддержка операционных систем и платность большинства (если не всех) программ.

  • CodeKit (Mac, 25 долларов). Очень прост в использовании: достаточно перетащить папку проекта в программу, и он сам определит все используемые в проекте языки, сам настроит и запустит все компиляторы, будет следить за изменениями исходных файлов и перезагружать браузер.
  • Hammer (Mac, 24 долларов). Основное предназначение — упростить разработку простых статических сайтов. Кроме препроцессоров, минификаторов и прочего, тут есть встроенный HTML-препроцессор и шаблоны новых проектов.
  • LiveReload (Mac, 10 долларов). Сборщиком его можно назвать с натяжкой. Основное предназначение — отслеживать изменения в файлах и перезагружать страницу в браузере. Однако он может и компилировать код CSS-препроцессоров или CoffeeScript.
 

Конфигурации

Конфигурации позволяют переключаться между боевой и отладочной версиями сайта. В явном виде в Гранте их нет. Но можно сделать, например, так:

concat: { main: { src: 'js/*.js', dest: 'build/scripts.js' } }, uglify: { main: { files: { '<%= concat.main.dest %>': '<%= concat.main.dest %>' } } } ... grunt.registerTask('default', ['concat', 'uglify']); grunt.registerTask('debug', ['concat']); 

Отладочная версия собирается так: grunt debug, а боевая — просто grunt. HTML в обоих случаях не меняется, но в последнем варианте код будет дополнительно сжат.

Для более крупных проектов может понадобится что-то сложнее. Например, RequireJS и/или подключение разных файлов в шаблонах с помощью плагина preprocess.

Иногда бывает полезно знать, собирается ли отладочная версия, изнутри JS или CSS-препроцессора. Например, если есть какой-то отладочный код, который не должен попасть в продакшн.

Можно было бы перед запуском CSS-препроцессора или Uglify прогонять код через препроцессор (например, упомянутый уже плагин preprocess), но это добавит лишний шаг и нарушит синтаксис исходных файлов, что затруднит работу с ними в IDE. Однако можно повнимательнее изучить уже используемые в проекте плагины, и, вероятно, обнаружить, что есть способ проще. Например, для JS (если используется UglifyJS) и Stylus такой способ есть.

В UglifyJS уже есть что-то вроде встроенного препроцессора: переменная в коде заменяется значением, а образовавшийся мертвый код (if (false) { /* Например, такой */ }) удаляется из результирующего файла.

uglify: { options: { compress: { global_defs: { DEBUG: debug  // Та самая переменная } } }, main: { files: { '<%= concat.main.dest %>': '<%= concat.main.dest %>' } } } 

Пример использования переменной в JS:

if (typeof DEBUG === 'undefined') DEBUG = true; if (DEBUG) { alert('Это сообщение появится только в отладочном режиме'); } 

Первая строка включает отладочный режим по умолчанию. Таким образом при сборке проекта без задачи uglify отладочный код будет выполнен, а при сборке с ней — вообще удален из файлов.

В Stylus все еще проще. Грантфайл:

stylus: { production: { files: { 'build/styles.css': 'styles/index.styl' } }, debug: { options: { define: { DEBUG: debug } }, files: { 'build/styles.css': 'styles/index.styl' } } } 

И пример использования:

css DEBUG ?= true  div outline: 1px solid #c0ffee if DEBUG 

Заключение

Не удивлюсь, если после прочтения этой статьи Грант показался тебе слишком сложным. Действительно, довольно трудно сразу во всем разобраться, а в разделе «Альтернативы» есть инструменты, пользоваться которыми гораздо проще. Но, на мой взгляд в Гранте наилучшим образом сочетаются умеренная простота использования, почти безграничные возможности настройки и расширения и огромное количество уже готовых плагинов на все случаи жизни.

 

Полезные плагины для Гранта

Все плагины можно найти на сайте Гранта: gruntjs.com/plugins.

* webfont — конвертер SVG-файлов в веб-шрифт; * autoprefixer — добавление вендорных префиксов по данным caniuse.com; * imgo и imgmin — оптимизация веб-графики; * remove-logging — удаляет из JS вызовы console.log(); * string-replace — замена строк в файлах; * csso — оптимизация CSS; * exec — запуск исполняемых файлов; * preprocess — препроцессор: условия, подстановка переменных и тому подобное. 
 

Грант на продакшне

Если тебе интересно, как я использую Грант в боевых условиях, взгляни на парочку небольших примеров грантфайлов, решающих наиболее типичные задачи современного сайта. Весь код прокомментирован и доступен на GitHub: bit.ly/10Srxld.

 

jQuery-плагин

Грантфайл моего jQuery-плагина Social Likes.

Грант собирает два файла дистрибутива:

  • JS: минифицированный исходный код;
  • CSS: компиляция Stylus, оптимизация с помощью CSSO.

А еще:

  • иконки оптимизируются с помощью imgo и внедряются в CSS на этапе компиляции Stylus;
  • в начало сжатых JS и CSS добавляется комментарий с копирайтом и текущей версией плагина, которая достается из файла bower.json (конфиг менеджера браузерных пакетов Bower);
  • JS проверяется с помощью JSHint.
 

Сайт

Грантфайл небольшого статического сайта:

  • компиляция Stylus;
  • проверка JS с помощью JSHint;
  • склейка JS в один файл;
  • минификация JS с помощью Uglify;
  • оптимизация графики с помощью imgo;
  • создание ZIP-архива для отправки клиенту;
  • отслеживание изменений в исходных файлах с перезагрузкой страницы в браузере.
 

Быстрая инициализация (scaffolding) проектов с помощью grunt-init

Если установить grunt-init (npm install -g grunt-init) и набрать в консоли, например:

grunt-init node 

то получится вот такое дерево файлов:

$ tree . ├── Gruntfile.js ├── LICENSE-MIT ├── README.md ├── lib │ └── MyCoolProject.js ├── package.json └── test └── MyCoolProject_test.js 

Это очень мощный инструмент (у меня уже больше десятка собственных шаблонов, и в интернете можно найти много готовых), заслуживающий отдельной статьи.

Если кратко, то grunt-init:

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

Оставить мнение

Check Also

Конкурс хаков: пишем на PowerShell скрипт, который уведомляет о днях рождения пользователей Active Directory

В компаниях часто встречается задача уведомлять сотрудников о приближающихся днях рождения…