Pull to refresh

Comments 33

А зачем это все, когда можно так?

{
// package.json
// ...
"scripts": {
    "styles:watch": "stylus assets/styles/main.styl -w -r -u kouto-swiss -o public/assets/main.css",
    "js:watch": "watchify -d -t debowerify assets/js/main.js -o public/assets/main.js",
    "server": "node app.js",
    "livereload": "node livereload.js",
    "start": "parallelshell \"npm run styles:watch\" \"npm run js:watch\" \"npm run server\" \"npm run livereload\""
}
// ...
}


Ну, только livereload-сервер нужно дописать, еще строка:

require('livereload').createServer({ port: 1234 }).watch('public/assets');


Неужели это сложнее?
Я, например, сходу не понимаю что за флаги такие и почему они тут. А gulpfile в этом посте довольно читабельный. Плюс наверно не слишком удобно получится, когда придется перечислять большое кол-во файлов.
Флаги доступно и понятно описаны в доках соответствующих модулей, а многие из них повторяются и легко запоминаются (например, -w — watch, -o — output). Тем более в gulp/grunt так или иначе все равно нужно эти флаги задавать, просто по-другому и гораздо более многословно. С большим количеством файлов, действительно, неудобно, но зачем вообще нужно много файлов? Для этого и есть main.js/styl/less/и т.д., где и прописаны всякие require, include, import и т.п. Максимум, что тут можно добавить — это генерация отдельного файла с зависимостями (скажем, генерируем отдельный css со скомпиленным bootstrap'ом, а в main инклюдим только variables, при желании переопределенный), но 2 файла — это не много. К тому же большинство модулей позволяют следить за целыми папками.
Не поводу топика, а в целом про то, что таск-раннеры не нужны.

Мне, кажется, есть некоторое лукавство во всем этом «Grunt/Gulp/etc на помойку, я все задачи могу написать в package.json». Оверхед на простейших сценариях очевиден, но в сложных сценариях не все так просто.

Да этот код не сложнее, но передавать параметры в виде аргументов командной строки то еще удовольствие, хотя можно завести по отдельному файлу на задачу и запускать их, а потом можно даже некоторые настройки общие, например, основные пути вынести в другой файл, чтобы править было удобно, а потом… получим свой таск-раннер.

Да, есть оверхед в сценарии когда вам надо запустить пару тройку задач в параллель, но если у вас с десяток задач, которые надо организовывать в сценарии, да желательно так, чтобы это таскать из проекта в проект, то не вижу смысла не использовать готовую архитектуру для этого.
Про простейшие сценарии в принципе и речь, хотя и сложные вполне реально сделать даже без отдельных js файлов. Но если ограничиваться сборкой ресурсов фронтенда, то каких-то реально сложных вещей и не должно быть, так ведь? А чаще всего только это и требуется. Да и не вижу никаких проблем таскать scripts из проекта в проект точно так же, как и gulp/gruntfile.
Вообще, вот есть статья на тему, может там все это лучше объяснено (важно еще хотя бы пробежать глазами следующую статью, продолжение этой). Лично меня она убедила.

Почему-то не проставились ссылки, вот статьи:
http://blog.keithcirkel.co.uk/why-we-should-stop-using-grunt/
http://blog.keithcirkel.co.uk/how-to-use-npm-as-a-build-tool/
Не убедили. Да npm может запускать задачи, да через консоль многие нужные вещи можно запустить, а если нет, то запустить свой скрипт, а в нем запустить то, что нужно. Есть даже переменные, громоздкие, конечно, $npm_package_config_*, но все же. С другой стороны, с тем же успехом можно просто свой скрипт на ноде, но к чему все эти велосипеды.

Про сложность. Для продакшена тот же css надо собрать из исходников, прогнать через автопрефиксер, возможно, объединить с какими-то другими стилевыми файлами не из исходников, сжать; если селекторов более 4096, разрезать для старых ие; пройти cache buster'ом, обновив пути в шаблонах. Если берем емейл рассылку, то заинлаинить в style атрибут, а все media queries в head. Не то, чтобы есть какая-то сложность когда есть просто ряд задач, но сложнее чем ваш пример. В статье предлагается написать гигантские строки, по типу
"autoprefixer -b '> 5%' < assets/styles/main.css | cssmin | hashmark -l 8 'dist/main.#.css'"
которые конечно можно разделить на строки по-меньше, но авторы предпочли не разделять, например, такое свое творение
"browserify -d assets/scripts/main.js -p [minifyify --compressPath . --map main.js.map --output dist/main.js.map] | hashmark -n dist/main.js -s -l 8 -m assets.json 'dist/{name}{hash}{ext}'"


1. Зачастую файл стилей не один, а несколько, например, ие специфичный, общий, мобильный. Сейчас у нас собираются файлы все из папки, которые не начинаются на нижнее подчеркивание. Мы просто добавляем файл в папку исходников стилей нужный и он собирается. В вашем примере легко понять как собрать конкретный файл, как собрать все файлы, а дальше стандартными средствами npm я не знаю как отфлильтровать файлы подходящие под одни условия и не походящие под другие. По мне есть два варианта, я через апи работы с файловой системой сам их найду и скормлю тулзе, либо уже надо, например, на баше сначала файлы это отфильтровать, а потом уже скармливать, можно, как вариант упомянутый в вашей статье, использовать nodejs альтернативы в командной строке (rimraf для rm, таже проблема с cp и другими). Все это не то, чтобы упрощает работу по сравнению с настройкой гранта, например.
2. Без внешних файлов, с переменными какая-то боль, мне удобнее в гранте написать
'<%= path.production %> чем громоздкое $npm_package_config_path_production, либо выносим конфигурацию во внешний скрипт, и запуск задач, чтобы они настройки брали оттуда тоже (справедливости ради некоторые могут брать настройки из json файла переданного как аргументв командной строке, но другие то не могут). Сейчас, например, у нас около 10-15 конфигурационных пременных, это по сути единственное, что мы иногда меняем заводя новый проект.
3. У сборки стилей, например, да и других ресурсов у нас обычно три таска: сборка во время разработки, для продакшена и компромисный, когда идет сборка в папку для продакшена не минифицированных версий (нужно при передаче на поддержку сторонним организациям, которые работают по-старинке). Похожая ситуация с js. Сейчас у нас около 15 плагинов-тасков, для части из них сформулированы несколько тасков-задач, из них формируется 3-5 сценариев. Хранить все это громадье в package.json и править не удобно, особенно когда команда и все её параметры записаны в виде одной длинной строки. Опять выходом будет вынести все в отдельные файлы, но в отличие от гранта с плагинов автозагрузки, например, задачи уже автоматом не подхватятся просто на основе используемых плагинов, а придется их прописать. Хотя можно, написать, свой автозагрузчик…
4. Приходим к набору файлов, который да можно таскать из проекта в проект, но это велосипед, который надо будет осваивать новому разработчику, с другой стороны Grunt/Gulp в любом случае известней и он может быть с ним знаком.
5. Как сделать стандартными средствами так, чтобы запустить несколько задач в параллель, а потом результат их выполнения передать в третью, притом кроссплатформенно? Может это просто, конечно, но опять же зачем мне решать эту задачу, если её решение идет в виде плагина, который нужно только настроить.

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

Ну и, конечно, я до сих пор не могу отойти и понять, как такое можно всерьез предлагать
"browserify -d assets/scripts/main.js -p [minifyify --compressPath . --map main.js.map --output dist/main.js.map] | hashmark -n dist/main.js -s -l 8 -m assets.json 'dist/{name}{hash}{ext}'"
Ну как минимум в stylus (уверен, что и у других препроцессоров такая возможность есть) автопрефиксы можно расставлять без дополнительных специализированных модулей с помощью kouto-swiss. А в статье показывается, как можно делать. Понятно, что это не безоговорочное руководство к действию, но возможность есть делать все, что необходимо. При желании более красивым способом.

1) Так кто мешает не подчеркивание использовать, а просто папку? Например, папка styles/main для основных файлов, все остальное в styles. Ну и как угодно по-другому. Да, скриптами сложно сделать именно как у вас, но зачем делать свалку файлов в одном месте, когда можно их распределить по смыслу без ущерба удобству?
2) Значит, в вашем случае такой подход, действительно, неприменим. Я не утверждал, что npm scripts полностью заменяет таск-менеджер — я говорил, что для простых (и наиболее распространенных) случаев он подходит больше.
3) Да, в таких случаях разумнее запускать отдельные js-файлы с реализацией задач через код, но что в этом плохого? Насколько я знаю, многие и с таск-менеджерами делают то же самое, вынося отдельные таски в отдельные файлы. Да и зачем задачи подгружать автоматом? Таски пишутся один раз и потом довольно редко изменяются, так что это не такая уж и проблема. Да и если это вдруг становится проблемой, то вы правильно заметили, что можно написать свой автолоадер для этого. И это совсем несложно.
4) Для человека, который не знает ни gulp, ни grunt (и т.п.), ни npm scripts совершенно нет никакой разницы. Но с тем, что модули таск-менеджеры популярнее, чем npm я согласен, так что в проекте, где все разрабы уже что-то знают, наверно, проще пользоваться модулем.
5) Стандартными — никак, но есть по крайней мере один модуль parallelshell (наверняка есть и другие, я не искал), задача которого как раз запускать в командной строке одновременно несколько процессов.

Таск-раннеры громоздки, их эффективность растет со сложностью задач, но их не стоит пихать всегда и везде. И агитирую я не переезжать на npm scripts, а воспринимать его как альтернативу.

Ну и, конечно, я до сих пор не могу отойти и понять, как такое можно всерьез предлагать


А никто такое и не предлагает. Как я написал выше — это всего лишь пример, ни к чему не обязывающий. Как минимум эту задачу можно разбить на более читаемые и понятные.
Сложнее и не решает все задачи:
1. Код плохо читаем — будут баги при переносе и редактировании
2. В качестве аргументов не запихаешь весь накопленный опыт. Например, мы используем plumber для глушения ошибок, чтоб процесс не завершался. Очень бесит когда в фоне watch процесс умер, а ты несколько секунд рефрешишь браузер и не понимаешь почему не работает.
3. Если нужно добавить что-то более сложное, то в cmd это уже не запихаешь, т.к. большинство плагинов ее не поддерживает
1) Да почему он плохо читаем-то? Что из, скажем, строки со сборкой stylus'а неочевидно при просмотре? Какие тут баги могут быть, если работа скрипта основана исключительно на работе модуля, без какого-либо дополнительного кода? Какая проблема перенести scripts из package.json? У меня подобных проблем до сих пор не возникало.
2) Watch процессы не умирают, а просто выводят ошибку, возникшую при сборке, в консоль. По крайней мере у меня так с вышеуказанными скриптами.
3) Что же есть настолько сложное (или несовместимое) в сборке фронтенда, что это не съест cmd? Вышеописанный (и немного более сложный, этот я немного упростил) набор скриптов вполне себе нормально отрабатывает в и виндовом cmd, и в bash
Потому что плохо читаем.
Вот вам аналогия для вашего кода:
stylus assets/styles/main.styl -w -r -u kouto-swiss -o public/assets/main.css

stylus('assets/styles/main.styl', {
  w: true,
  r: true,
  u: 'kouto-swiss',
  o: 'public/assets/main.css'
});

Вам серьезно нравится такое читать? Ведь всегда можно заглянуть в документацию и узнать что же значат эти магические аргументы!
Строка stylus assets/styles/main.styl -w -r -u kouto-swiss -o public/assets/main.css лично мне вполне понятна. Как я говорил выше, флаги -w и -o являются, скажем так, общепринятыми. Флаг -u тоже очевиден, поскольку слово kouto-swiss после него можно интерпретировать однозначно — это зависимость. Делаем вывод, что -u — это что-то типа use и запоминаем этот флаг таким образом как минимум надолго. Остается флаг -r, который похожими интеллектуальными усилиями можно интерпретировать как resolve, то есть разрешать относительные пути для файлов исходников.
Ну а если даже такие усилия делать лень, то для каждой буквы есть соответствующий более длинный аналог: --watch, --resolve-url, --use, --out. И что же тут «плохо читается»?
Ну вот я и говорю, посмотрите аналогичный код на JS, по каждму аргументу тоже можно догадаться, что же там написано. И вместо того, чтобы работать надо каждый раз либо гадать, что же значит флаг, либо лезть в доки.
А если написать с длинными аналогами, то получится жесть:
{
  "scripts": {
    "stylus": "stylus assets/styles/main.styl --watch --resolve-url --use kouto-swiss --out public/assets/main.css"
  }
}

Ну и самое главное. Специальные инструменты всегда будут решать задачу лучше, чем такие общие, как npm-scripts. Им можно заменить таск раннер, тут я согласен. Но им не стоит заменять систему сборки. Webpack, на пример, умеет запускать веб сервер и держать ваши css/js/html/etc. в памяти, что ускоряет процесс сборки и сберечь ваши ssd.
Во-первых, какой такой КАЖДЫЙ раз? Таски пишутся один раз и потом, возможно, немного изменяются и дополняются, и то со временем все реже и реже. Во-вторых, я и пытаюсь донести мысль, что гадать не нужно, так как все это легко запоминается (по крайней мере не сложнее, чем названия всех необходимых модулей для сборки и код для их запуска). В-третьих, npm scripts — это и есть специальный инструмент. Да, он проще, чем модули, но тем не менее. В-четвертых, взгляните на мой первый комментарий, там ясно видно, что сервер скрипты тоже умеют запускать. Ну а сборка из памяти или уже реализована на уровне модулей, которые запускаются у меня из командной строки, или работают слишком быстро, чтобы я это заметил — точно сам не знаю. Хотя, возможно, на очень большом количестве файлов выигрыш будет заметен. Но как минимум для сборки js есть watchify, который вне зависимости от количества и сложности сборки на лету собирает только изменившийся файл, что в итоге занимает до 20 мс.
Пользуетесь plumber чтобы watch не падал? А вы в курсе, что у вас теперь процесс всегда завершается с кодом 0, то есть успешно? Это означает, что команда, к примеру, npm test && npm run deploy задеплоит даже код с ошибками.

Не надо так. Посмотрите мой пост, где рассказывается как надо обращаться с ошибками.

Кроме того, у вас функция run() не возвращает ничего. Gulp не сможет узнать об окончании таска, поэтому они у вас все будут выполняться параллельно.

Одно радует, раз у вас есть общий модуль с рецептами для сборки, можно поправить один раз, и станет лучше во всех зависимых проектах
Спасибо за ссылку на пост. Я глушу ошибки именно в develop режиме, потому что часто бывает такой сценарий: начал писать код (название функции), переключился на браузер чтобы посмотреть документацию и IDE (в моем случае PHPStorm) сохраняет файл при потере фокуса окна, gulp watch начинает выполняться, падает. Все это происходит в фоне и потому не заметно.
А в продакшен билде да, вывод ошибок обязателен… Пойду проверю падает ли он в продакшене при ошибках… и с каким кодом.
Мое решение не глушит ошибки, а обрабатывает правильно. То есть одна сборка у вас упадет, но процесс c watch останется. Поправите ошибки, сохранитесь – watch запустит сборку, и она пройдет успешно.
Автоматическая автоматизация автоматизации для автоматизации…
О, я тоже писал подобного монстра, только смысл в том, что он умеет компилить вообще всё подряд вперемешку — например кофе вместе с бабелом, потом js, потом опять кофе, потом всё объединяет в одмн, минифицирует, создаёт gzip и прочее. В общем выложил в gist с примером. Может кому понадобится: https://gist.github.com/SerafimArts/2101d66020c4791295aa
Из документации Gulp:

Your plugin should only do one thing, and do it well.

  • Avoid config options that make your plugin do completely different tasks
  • For example: A JS minification plugin should not have an option that adds a header as well

Guidelines


Если кто не понял, то плагинам следует исполнять только ОДНУ задачу — в этом и есть вся прелесть Gulp. Если уж очень нужно сделать подобный генератор, то гляньте в сторону Yeoman.
Да, я предполагал что мое решение выбивается из идеологии gulp (потому что подобных модулей не нашел), но оно было необходимо.
У меня просто есть скелет проекта, клонирую его, а там уже все что надо есть.
В gulpfile всего две строки:
var requireDir = require('require-dir');
requireDir('./gulp/tasks', { recurse: true });

свою заготовку форкнул и изменил из вот этого vigetlabs/gulp-starter.

Думаю что такой подход более гибкий и понятный.
Все таски в отделенной директории.
gulpfile никогда не редактируется.

А так да мы все велосипедисты =)

P.S. А парни пошли дальше. Они назвали директорию gulpfile.js ну а там естественно index.js и вообще красота получается =) Надо будет в своем скелете так же сделать =)
Спасибо за наводку! Действительно неплохое решение, но применимое скорее для больших проектов.
У меня была задача сделать что-то для небольших (обычно уже существующих со своей структурой) проектов + этим должны пользоваться джуниоры/мидлы и ничего не сломать :) А то за последнее время народилось кучи разносортных gulp/grunt файлов, в каждом проекте свой формат и свои костыли %)
Использую практически такой же подход, только я для своих нужд написал loader (gulp-task-loader-recursive) для gulp тасков, который, к тому же, загружает их рекурсивно и проставляет каждому файлу с таском красивое имя в зависимости от имени файла и папок, где он находится. В результате gulpfile.js состоит всего из 2 строк (можно и в 1 вместить), а сами задачи лежат в отдельных файликах. Кроме того, очень легко подключается Babel, который потом можно использовать в этих task файлах. Такие отдельные файлики уже гораздо проще копировать между проектами.

В целом, почему мне Gulp нравится больше, чем запуск задач через npm тем, что Gulp позволяет автоматизировать достаточно сложные вещи в достаточно читаемом и понятном виде.
это глоток воздуха после grunt'a <…> нужно подключать с десяток зависимостей и писать почти 50 строчек

Спасибо, наглотался:)
grunt тоже требовал подключения множества модулей и еще большим количеством кода. Про глоток воздуха — это про структуру тасков
Ну для сборки commonjs достаточно одного модуля.
Посмотрел код, вроде es6, а вроде ужас какой-то. Нафига вы столько написали, когда как раз для описанных задач есть webpack. Где в 1 строку вписывается правило для сборки less или чего вы там еще хотите.
es6 раньше не писал, в чем ужас?
webpack — альтернатива browserify, но он не умеет упаковывать less, копировать файлы и еще что-нить, что может понадобитсья. Gulp умеет много и позволяет делать что угодно.
У Laravel есть подобный подпроект, называется Elixir.
При небольшом желании, можно использовать отдельно.

Умеет less, sass, autoprefixer, babel, jsx, browserify и все такое
Только не умеет объединять всё это в одно (только несколько файлов с одним расширением), добавлять gzip, брать одновременно из нескольких директорий и прочие стандартные вещи. Крайне не рекомендую пробовать это поделие, т.к. написать подобное самому будет проще и быстрее.

Ещё добавлю предложение посмотреть WebPack (https://webpack.github.io/). Многие (каждый на моей памяти) кто попробовал говорят что в разы удобнее гулпа и за ним будущее.
Sign up to leave a comment.

Articles