Перезарядка вашего хрюкающего файла

Как выжать максимум из конфигурации сборки

Введение

Если мир Grunt для вас новичок, идеальной отправной точкой будет превосходная статья Криса Койера « Grunt для людей, которые думают, что такие вещи, как Grunt, являются странными и трудными ». После представления Криса вы создадите свой собственный проект Grunt и почувствуете часть возможностей, которые предлагает Grunt.

В этой статье мы сосредоточимся не на том, что многочисленные плагины Grunt делают с реальным кодом вашего проекта, а на самом процессе сборки Grunt. Мы дадим вам практические идеи по:

  • Как сохранить ваш Gruntfile в чистоте и порядке,
  • Как значительно сократить время сборки,
  • И как получать уведомления о сборке.

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

Организация вашего Gruntfile

Независимо от того, включаете ли вы много плагинов Grunt или вам приходится писать много ручных задач в своем Gruntfile, он может быстро стать очень громоздким и трудным в обслуживании. К счастью, существует немало плагинов, которые решают именно эту проблему: сделать ваш Gruntfile снова аккуратным и аккуратным.

Gruntfile до оптимизации

Вот как выглядит наш Gruntfile до того, как мы его оптимизировали:

module.exports = function(grunt) {

  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    concat: {
      dist: {
        src: ['src/js/jquery.js','src/js/intro.js', 'src/js/main.js', 'src/js/outro.js'],
        dest: 'dist/build.js',
      }
    },
    uglify: {
      dist: {
        files: {
          'dist/build.min.js': ['dist/build.js']
        }
      }
    },
    imagemin: {
      options: {
        cache: false
      },

      dist: {
        files: [{
          expand: true,
          cwd: 'src/',
          src: ['**/*.{png,jpg,gif}'],
          dest: 'dist/'
        }]
      }
    }
  });

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

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

};

Если сейчас вы говорите: «Эй! Я ожидал гораздо худшего! Это на самом деле ремонтопригодно!», вы, вероятно, правы. Для простоты мы включили только три плагина без особой настройки. Использование реального рабочего Gruntfile для создания проекта среднего размера в этой статье потребует бесконечной прокрутки. Итак, давайте посмотрим, что мы можем сделать!

Автоматическая загрузка плагинов Grunt

При добавлении нового плагина Grunt, который вы хотите использовать в своем проекте, вам придется добавить его в файл package.json в качестве зависимости npm, а затем загрузить в файл Gruntfile. Для плагина grunt-contrib-concat это может выглядеть следующим образом:

// tell Grunt to load that plugin
grunt.loadNpmTasks('grunt-contrib-concat');

Если вы сейчас удалите плагин через npm и обновите package.json, но забудете обновить Gruntfile, ваша сборка сломается. Здесь на помощь приходит отличный плагин load-grunt-tasks .

Раньше нам приходилось вручную загружать наши плагины Grunt, например:

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

С помощью load-grunt-tasks вы можете свернуть это до следующей строки:

require('load-grunt-tasks')(grunt);

После запроса плагина он проанализирует ваш файл package.json, определит, какие из зависимостей являются плагинами Grunt, и загрузит их все автоматически.

Разделение конфигурации Grunt на разные файлы

load-grunt-tasks немного уменьшил код и сложность вашего Gruntfile, но при настройке большого приложения он все равно станет очень большим файлом. Здесь в игру вступает load-grunt-config . load-grunt-config позволяет разбить конфигурацию Gruntfile по задачам. Более того, он инкапсулирует задачи load-grunt и их функциональность!

Однако важно: разделение Gruntfile может не всегда работать в каждой ситуации. Если у вас много общих конфигураций между вашими задачами (т. е. вы используете много шаблонов Grunt), вам следует быть немного осторожными.

С помощью load-grunt-config ваш Gruntfile.js будет выглядеть так:

module.exports = function(grunt) {
  require('load-grunt-config')(grunt);
};

Да, вот действительно, весь файл! А где теперь хранятся наши конфигурации задач?

Создайте папку grunt/ в каталоге вашего Gruntfile. По умолчанию плагин включает в эту папку файлы, соответствующие названию задачи, которую вы хотите использовать. Наша структура каталогов должна выглядеть так:

- myproject/
-- Gruntfile.js
-- grunt/
--- concat.js
--- uglify.js
--- imagemin.js

Давайте теперь поместим конфигурацию каждой из наших задач непосредственно в соответствующие файлы (вы увидите, что в основном это просто копирование и вставка из исходного файла Gruntfile в новую структуру):

хрюканье/concat.js

module.exports = {
  dist: {
    src: ['src/js/jquery.js', 'src/js/intro.js', 'src/js/main.js', 'src/js/outro.js'],
    dest: 'dist/build.js',
  }
};

grunt/uglify.js

module.exports = {
  dist: {
    files: {
      'dist/build.min.js': ['dist/build.js']
    }
  }
};

grunt/imagemin.js

module.exports = {
  options: {
    cache: false
  },

  dist: {
    files: [{
      expand: true,
      cwd: 'src/',
      src: ['**/*.{png,jpg,gif}'],
      dest: 'dist/'
    }]
  }
};

Если блоки конфигурации JavaScript вам не нравятся, load-grunt-tasks даже позволяет вместо этого использовать синтаксис YAML или CoffeeScript. Давайте напишем наш последний необходимый файл в YAML — файл « псевдонимов ». Это специальный файл, который регистрирует псевдонимы задач, что нам приходилось делать раньше в рамках Gruntfile с помощью функции registerTask . Вот наш:

grunt/aliases.yaml

default:
  - 'concat'
  - 'uglify'
  - 'imagemin'

Вот и все! Выполните следующую команду в своем терминале:

$ grunt

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

Минимизация времени сборки

Несмотря на то, что производительность времени выполнения и загрузки вашего веб-приложения гораздо более критична для бизнеса, чем время, необходимое для выполнения сборки, медленная сборка по-прежнему остается проблематичной. Это затруднит выполнение автоматических сборок с помощью таких плагинов, как grunt-contrib-watch или после фиксации Git достаточно быстро, и введет «штраф» за фактический запуск сборки — чем быстрее время сборки, тем более гибким будет ваш рабочий процесс. Если ваша производственная сборка выполняется дольше 10 минут, вы будете запускать ее только в случае крайней необходимости и будете уходить за кофе, пока она работает. Это убийца производительности. Нам есть что ускорить.

Создавайте только те файлы, которые действительно изменились: grunt-newer

После первоначальной сборки вашего сайта вполне вероятно, что вы затронете лишь несколько файлов в проекте, когда снова приступите к сборке. Допустим, в нашем примере вы изменили изображение в каталоге src/img/ — запуск imagemin для повторной оптимизации изображений имел бы смысл, но только для этого одного изображения — и, конечно же, повторный запуск concat и uglify — это просто пустая трата времени. драгоценные циклы процессора.

Конечно, вы всегда можете запустить $ grunt imagemin со своего терминала вместо $ grunt , чтобы выборочно выполнить текущую задачу, но есть более разумный способ. Это называется grunt-newer .

Grunt-newer имеет локальный кеш, в котором он хранит информацию о том, какие файлы действительно были изменены, и выполняет ваши задачи только для тех файлов, которые действительно были изменены. Давайте посмотрим, как его активировать.

Помните наш файл aliases.yaml ? Измените это с этого:

default:
  - 'concat'
  - 'uglify'
  - 'imagemin'

к этому:

default:
  - 'newer:concat'
  - 'newer:uglify'
  - 'newer:imagemin'

Просто добавив «newer:» к любой из ваших задач , вы сначала передаете исходные и целевые файлы через плагин grunt-newer, который затем определяет, для каких файлов, если таковые имеются, задача должна выполняться .

Запуск нескольких задач параллельно: grunt-concurrent

grunt-concurrent — это плагин, который становится действительно полезным, когда у вас много задач, независимых друг от друга и отнимающих много времени. Он использует количество процессоров вашего устройства и выполняет несколько задач параллельно.

Самое приятное то, что его конфигурация очень проста. Предполагая, что вы используете load-grunt-config, создайте следующий новый файл:

хрюканье/concurrent.js

module.exports = {
  first: ['concat'],
  second: ['uglify', 'imagemin']
};

Мы просто настраиваем параллельное выполнение треков с именами « первый » и « второй ». Сначала необходимо запустить задачу concat , и в нашем примере пока больше ничего запускать не нужно. Во втором треке мы добавили и uglify , и imagemin , поскольку они независимы друг от друга и оба занимают значительное количество времени.

Само по себе это еще ничего не дает. Нам нужно изменить псевдоним задачи по умолчанию , чтобы он указывал на параллельные задания, а не на прямые. Вот новое содержимое grunt/aliases.yaml :

default:
  - 'concurrent:first'
  - 'concurrent:second'

Если теперь вы повторно запустите сборку grunt, параллельный плагин сначала запустит задачу concat, а затем создаст два потока на двух разных ядрах ЦП для параллельного запуска imagemin и uglify. Ура!

Однако небольшой совет: скорее всего, в нашем базовом примере grunt-concurrent не ускорит вашу сборку значительно. Причиной этого являются накладные расходы, возникающие при создании разных экземпляров Grunt в разных потоках: в моем случае спавн pro составляет не менее +300 мс.

Сколько времени это заняло? время-хрюканье

Теперь, когда мы оптимизируем каждую из наших задач, было бы очень полезно понять, сколько времени требуется для выполнения каждой отдельной задачи. К счастью, для этого тоже есть плагин: time-grunt .

time-grunt — это не классический плагин grunt, который вы загружаете как задачу npm, а скорее плагин, который вы включаете напрямую, аналогично load-grunt-config. Мы добавим требование для time-grunt в наш Gruntfile, точно так же, как мы это сделали с load-grunt-config. Наш Gruntfile теперь должен выглядеть так:

module.exports = function(grunt) {

  // measures the time each task takes
  require('time-grunt')(grunt);

  // load grunt config
  require('load-grunt-config')(grunt);

};

И мне жаль разочаровывать, но все — попробуйте перезапустить Grunt из своего терминала, и для каждой задачи (и, кроме того, всей сборки) вы должны увидеть красиво отформатированную информационную панель о времени выполнения:

Время хрюкать

Автоматические системные уведомления

Теперь, когда у вас есть сильно оптимизированная сборка Grunt, которая выполняется быстро, и при условии, что вы каким-то образом автоматически собираете ее (например, просматривая файлы с помощью grunt-contrib-watch или после коммитов), было бы здорово, если бы ваша система могла уведомить вас, когда ваша новая сборка будет готова к использованию или когда произойдет что-то плохое? Встречайте grunt-notify .

По умолчанию grunt-notify предоставляет автоматические уведомления обо всех ошибках и предупреждениях Grunt, используя любую систему уведомлений, доступную в вашей ОС: Growl для OS X или Windows, Центр уведомлений Mountain Lion и Mavericks и Notify-send. Удивительно, но все, что вам нужно, чтобы получить эту функциональность, — это установить плагин из npm и загрузить его в свой Gruntfile (помните, что если вы используете grunt-load-config выше, этот шаг автоматизирован!).

Вот как это будет выглядеть в зависимости от вашей операционной системы:

Поставить в известность

Помимо ошибок и предупреждений, давайте настроим его так, чтобы он запускался после завершения выполнения нашей последней задачи. Предполагая, что вы используете grunt-load-config для разделения задач по файлам, нам понадобится вот этот файл:

хрюканье/notify.js

module.exports = {
  imagemin: {
    options: {
      title: 'Build complete',  // optional
        message: '<%= pkg.name %> build finished successfully.' //required
      }
    }
  }
}

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

Подводя итоги

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

Если вы обнаружите еще один драгоценный камень, который еще больше улучшит Grunt и его плагины, сообщите нам об этом! А пока, счастливого хрюканья!

Обновление (14.02.2014). Чтобы получить копию полного рабочего примера проекта Grunt, нажмите здесь .