Таск-раннеры и системы сборки сильно ускоряют работу, автоматизируя компиляцию, тестирование и другие рутинные задачи. Как и в любой другой области, на этом рынке существует сильная конкуренция. До 2014 года среди них главенствовал таск-раннер grunt, но позже из состава проекта выделилась небольшая команда, которая решила делать альтернативный инструмент, gulp, ориентированный на сборку проекта.
Чтобы помочь вам определиться с выбором, в рамках статьи рассмотрим основные таск-менеджеры:
- gulp
- grunt
а также коснемся других средств и способов сборки.
Забегая немного вперед, скажем, что мы в WaveAccess пользуемся именно gulp. Внедрить инструмент оказалось очень легко: у семейства продуктов JetBrains (IDEA, WebStorm, ReSharper), которые мы используем уже много лет, есть отличные плагины для работы с gulp/grunt и npm/nodejs.
Таск-менеджер vs. система сборки проекта: в чем разница?
Таск-менеджер — инструмент для автоматизации задач. В конфигурации раннеров можно записать имена этих задач; функцию, которая их выполняет; плагины для ускорения стандартных действий, но сами задачи могут быть произвольными. Например:
- Задачи для деплоя (zip проекта, загрузка проекта на удаленный сервер и тп)
- Задачи по сборке проекта (минификация, оптимизация, проверка кода на валидность и тп)
- Задачи для миграции данных и т.д.
Примеры таких инструментов — grunt и gulp.
Система сборки — это инструмент, который решает только одну типовую задачу сборки проекта на java script, в которую входят:
- конкатенация,
- проверка кода на валидность,
- минификация кода, и тд.
К подобным инструментам относятся Webpack, Broccoli, Brunch, Browserify и другие.
Все подобные frontend-задачи можно автоматически выполнять при помощи других средств: к примеру, с помощью npm run, о котором мы также поговорим в статье.
Пример
Рассмотрим gulp-файл для сборки проекта:
const gulp = require (‘gulp’); const coffee = require (‘gulp-coffee’); const concat = require (‘gulp-concat’); const uglify = require (‘gulp-uglify’); const imagemin = require (‘gulp-imagemin’); const sourcemaps = require (‘gulp-sourcemaps’); const del = require (‘del’); }
Но сборка — это частный случай большой типовой задачи. Для gulp можно написать и другой config — скажем, для деплоя:
var gulp = require('gulp'); var zip = require('gulp-zip'); var del = require('del'); var install = require('gulp-install'); var runSequence = require('run-sequence'); var awsLambda = require("node-aws-lambda"); gulp.task('clean', function(cb) { del(['./dist', './dist.zip'], cb); }); gulp.task('copy', function() { return gulp.src('index.js') .pipe(gulp.dest('dist/')); }); gulp.task('node-mods', function() { return gulp.src('./package.json') .pipe(gulp.dest('dist/')) .pipe(install({production: true})); }); // Clean up all aws-sdk directories from node_modules. We don't // need to upload them since the Lambda instance will already // have it available globally. gulp.task('clean-aws-sdk', function(callback) { del(['dist/node_modules/**/aws-sdk'], callback); }); gulp.task('zip', function() { return gulp.src(['dist/**/*', '!dist/package.json']) .pipe(zip('dist.zip')) .pipe(gulp.dest('./')); }); gulp.task('upload', function(callback) { awsLambda.deploy('./dist.zip', require("./lambda-config.js"), callback); }); gulp.task('deploy', function(callback) { return runSequence( ['clean'], ['copy'], ['node-mods'], ['clean-aws-sdk'], ['zip'], ['upload'], callback ); });
A можно описывать новые задачи как комбинации уже существующих:
gulp.task(‘deploy’, gulp.series (‘clean’, ‘copy’, ‘node-mods’, ‘clean-aws-sdk’, ‘zip’, ‘upload’) );
В этом и заключается отличие. Теперь рассмотрим основные инструменты.
gulp vs. grunt
Итак, перед нами два таск-раннера: gulp и grunt. Оба используют node.js и npm, а задачи им ставят, используя javascript.
Grunt Таск-раннер, который разработан для Node.js в 2012 году. Сейчас у него около 11к плагинов — почти вдвое больше, чем у gulp. До сих пор применяется в Adobe Systems, jQuery, Twitter, Mozilla, Bootstrap, Cloudant, Opera, WordPress, Walmart и Microsoft. |
Gulp Потоковая система сборки (a streaming build system). Создан как ответвление grunt и насчитывает около 4000 плагинов. |
На первый взгляд они схожи, однако у gulp есть то, что делает его более удобным именно для сборки: умение параллельно обрабатывать задачи и компактный конфиг, лаконичное API. Давайте посмотрим поближе их принцип работы.
Потоковая передача данных
Перед нами грант-файл, который осуществляет сборку и обработку CSS.
Из него видно, что grunt при запуске каждого процесса:
-
открывает файл;
-
запускает процесс;
-
сохраняет изменения;
-
закрывает обработанный файл, чтобы предотвратить вмешательство в него следующего процесса;
-
записывает файл в итоговую папку.
То есть, цепочка включает в себя создание нескольких временных папок и сохранение промежуточных файлов:
Плагины пишут разные авторы. Чтобы каждый плагин мог работать с файлами, обходя сохранение, файлы нужно представить в виде объектов. В gulp эту задачу выполняет виртуальная файловая система Vynyl-FS. И gulp сразу передает файл следующему процессу без создания временных файлов и без сохранения на диск.
Та же самая конфигурация для gulp уже компактнее:
Его общий механизм работы — потоковая обработка файлов без записи на диск:
Последовательность выполнения задач
Есть и другое отличие: gulp по умолчанию асинхронно выполняет таски. В этом есть и плюсы, и минусы. В том же конфигурационном файле мы даем команду считать файлы из директории dev/*scss и отправить их в SASS.
Потоки отправляют результат в .pipe. Метод .pipe позволяет собирать результат в буфер по частям, а когда он заполнен, сразу отправлять информацию в поток для чтения, еще не закончив получать содержимое директории.
Последовательное выполнение задач делает gulp быстрым и мощным, но изредка возникает необходимость все же выполнить задачи синхронно, как в grunt. Проблему можно решить через обратный вызов, возвращение потока или Promise. Более подробно задача разобрана на Хабре. Есть и альтернативный вариант на самом сайте npm.js
Если вы пользуетесь grunt, но вас привлекает потоковая передача данных -- тот же модуль Vynyl-FS можно использовать для ее реализации в grunt.
API
Лаконичное API gulp имеет всего 5 методов:
-
.task(name, fn). Регистрирует функцию с именем. Можно указать зависимость от других тасков, если нужно их выполнить сначала.
-
.run(tasks...). Выполняет задачи.
-
.watch(glob, fn). Выполняет функцию, если файл на месте glob меняется.
-
.src(glob). В качестве параметра принимает маску файлов и возвращает поток, представляющий эти файлы. Затем поток может быть передан на вход плагинам.
-
.dest(folder). Сохраняет файлы в указанную папку.
Особенно хотелось бы отметить наличие .watch() в “родном” API проекта, ведь слежение за постоянными изменениями файлов является важнейшей составляющей сборки. Краткость API дает возможность этому таск-менеджеру сфокусироваться на своей основной задаче – сборке проектов.
Альтернативы gulp и grunt
Несмотря на популярность gulp (больше 130 к скачиваний в день) и grunt (более 86 к скачиваний в день согласно npmjs.com), разработчики видят в этих системах и свои недостатки: к примеру, зависимость от плагинов, неполная документация, неудобный дебаггинг. В качестве альтернативы можно рассмотреть системы сборки проектов (такие как Broccoli и Webpack) или npm-скрипты.
Системы сборки проектов
Рассмотрим несколько альтернативных решений на платформе Node.js. Для сборки проекта они могут заменить gulp и grunt.
Brunch
Эта система, как и gulp, возникла как конкурент таск-раннеру grunt, однако разработчики изначально задумывали ее именно как помощник для сборки со всеми преимуществами и недостатками. Он без настроек “поймет”, что *.js — это файл со скриптами, *.coffee — это CoffeeScript; его конфиг более компактен. Однако никаких произвольных действий на этапе сборки он совершить не сможет.
Вот конфиг-файл Brunch. Он написан на CoffeeScript (можно также писать на JS):
exports.config = files: javascripts: joinTo: 'javascripts/app.js': /^app/ 'javascripts/vendor.js': /^(bower_components|vendor)/ stylesheets: joinTo: 'stylesheets/app.css' order: after: ['vendor/styles/helpers.css'] templates: joinTo: 'javascripts/app.js'
Здесь хочется обратить внимание на операторы joinTo и order. На уровне конфига Brunch понимает, что придется собирать файлы в нужном порядке. В результате, конфиг занимает 20-30 строк.
Broccoli
Инди-инструмент, который находится на стадии разработки. Его разработчики хотели создать конкуренцию уже gulp.
По сравнению с gulp, инструмент Broccoli использует другие принципы:
-
Ускорение сборки. Каждый плагин реализует промежуточное кэширование результатов сборки вместо частичной пересборки только нужных файлов.
-
Деревья вместо файлов. Gulp лучше всего трансформирует один файл в один итоговый. Broccolli по умолчанию использует только деревья, а не файлы, и их трансформирует в другие деревья (вырожденные из одной вершины).
В данный момент инструмент активно развивается, появляются новые плагины, но для серьезных проектов его использовать рано: плагинов пока недостаточно.
Webpack
Webpack - гибкая модульная система сборки. Она обладает непривычным синтаксисом, но сама воспринимает любые синтаксисы модулей.
Понимая, что придется конкурировать с такими гигантами как gulp, создатели решили облегчить нам жизнь при разработке больших проектов. И добавили в утилиту:
-
Умение автоматически строить дерево зависимостей и ресурсов.
-
Удобные средства для реализации динамической подгрузки.
-
Совместимость с практически любыми модулями (AMD, UMD, ES 2015, Common JS, сторонние модули на их основе).
-
Совместимость с препроцессорами (SASS, Babel, Coffee Script, Type Script и т.д.).
-
Live Reload (технологию асинхронной загрузки, при которой браузер обновляет не страницы целиком, а отдельные приложения).
-
Возможность делить код и генерировать множество bundle-файлов, избегая создания одного тяжелого bundle.js.
-
Умение оптимизировать код.
Отдельно можно отметить гибкий подход к зависимостям. Модулем может стать JS, CSS и HTML-файл, и даже JPEG с PNG. Можно использовать require(“myJSfile.js”) и require(“myCSSfile.css”), делить и использовать части артефакта повторно.
Подробнее о возможностях, конфигурации инструмента, плагинах можно найти на Github, в презентации с Fronttalks: глубокое погружение в Webpack.
npm скрипты
Задачи по сборке можно решить и при помощи npm-скриптов. Многих отпугивает эта идея: мало возможностей, скрипты недостаточно быстрые в сравнении с gulp или Webpack. Тем не менее, эти недостатки преувеличены.
Возможности npm-скриптов
Npm-скрипты решают довольно много задач. Так, например, можно реализовать скрипты ловушек:
{ "name": "npm-scripts-example", "version": "1.0.0", "description": "npm scripts example", "scripts": { "prebuild": "echo I run before the build script", "build": "cross-env NODE_ENV=production webpack" "postbuild": "echo I run after the build script" } }
Сценарии будут загружаться по порядку согласно префиксам: prebuild, например, стартует перед build, потому что у него есть префикс pre. Соответственно, postbuild будет загружен последним. Команда npm run build запустит их в нужном порядке.
Можно вызывать один скрипт из другого, чтобы декомпозировать сложные задачи. Например, здесь задача prebuild вызывает задачу clean.
{ "name": "npm-scripts-example", "version": "1.0.0", "description": "npm scripts example", "scripts": { "clean": "rimraf ./dist && mkdir dist", "prebuild": "npm run clean", "build": "cross-env NODE_ENV=production webpack" } }
Если задача становится слишком сложной, всегда можно вызвать отдельный файл:
{ "name": "npm-scripts-example", "version": "1.0.0", "description": "npm scripts example", "scripts": { "build": "node build.js" } }
За счет стриминга gulp для задач сборки стал гораздо удобнее, чем grunt. Но его можно реализовать и через npm. В Windows и Unix стриминг делается по умолчанию, без сохранения промежуточных файлов.
К примеру, в Unix можно сделать grep содержимого файла и направить его в новый файл:
grep ‘My Name’ bigFile.txt > linesThatHaveMyName.txt
Редирект(>)направляет нужные строки в конечный файл. Задача выполняется без сохранения промежуточных файлов.
Но есть и неудобства: нельзя оставлять комментарии в package.json. Выходом может стать создание коротких скриптов с понятными названиями, нацеленных на какую-то одну небольшую задачу. Более подробно вопрос замены таск-раннеров npm-скриптами хорошо освещен в англоязычной статье Why I Left Gulp and Grunt for npm Scripts.
Итог
На рынке существует большая конкуренция инструментов для автоматизации рутинных задач (например, gulp и grunt), а также инструментов для автоматизации сборки проекта (Webpack, Broccoli, Medusa, Browserify и т.д.).
Если смотреть на таск-раннеры, то gulp по сравнению с grunt более прост, понятен и производителен: выигрывает за счет экономии на дисковых операциях. Но у grunt больше плагинов (например, есть плагин для тестирования). Из-за этого у него остается много поклонников.
Если же говорить только о сборке, то у gulp есть все преимущества перед grunt:
-
Поточная архитектура для передачи файлов по цепочке, которую обеспечивает модуль Vynyl-FS.
-
По умолчанию - асинхронное выполнение задач.
-
Лаконичное API всего из 5 функций.
В то же время, для сборки Webpack является не менее интересным инструментом. В нем предусмотрена технология Live Reload, ускоряющая обновление браузера. Это огромный плюс: технология экономит время на нажатие кнопки обновления, которую разработчикам приходится нажимать постоянно. В gulp также есть Live Reload, но Webpack сложно сравнивать с gulp или grunt, так как он “заточен” только под билд и не “умеет” решать произвольные задачи.
Все эти решения прекрасно интегрируются с семейством продуктов от JetBrains, однако мы в WaveAccess предпочли именно grunt за широкие возможности и для верстальщиков, и для frontend-специалистов.
Если у Вас возникли вопросы и вам необходима разработка web-проекта, пишите нам на hello@wave-access.com