Bower керує версіями встановлених пакетів та залежностями кожного такого пакета. Але як же нам застосовувати ці пакети в коді ефективно — як для розробки, так і при розгортанні проекту? Тут з'являється Gulp. Він запускає завдання, як і його застарілий далекий родич Make або такі інструменти як Ant, Phing, Rake або новий малюк у цьому списку, Grunt.
Gulp проти Grunt
Є два інструменти, які набули популярності за останній рік — Gulp і Grunt.
Grunt був першим, хто отримав популярність і намагається надати вбудовану функціональність, щоб охопити всі типові випадки використання. Він слідує підходу на основі конфігурації.
Gulp з іншого боку пропонує дуже мало з коробки, натомість воліючи перекласти функціональність на безліч невеликих плагінів з однією функцією. Gulp застосовує потоки плагінів для створення комплексного робочого процесу.
Хоча обидва інструменти можуть виконувати завдання паралельно, Gulp робить це за замовчуванням, намагаючись досягти багаторазового паралелізму — працює так багато завдань, наскільки це можливо, при цьому дотримуються такі речі, як залежність між завданнями.
Чотири речі
Gulp робить із коробки чотири речі:
- Визначає завдання через gulp.task();
- Відстежує зміни у файловій системі через gulp.watch();
- Відкриває файли/папки за допомогою gulp.src();
- Зберігає файли/папки через gulp.dest().
Gulp буде викликати завдання за промовчанням або будь-яке інше завдання, задане в командному рядку, автоматично.
Все інше виходить шляхом послідовного виклику pipe() для gulp.src().
Віртуальна файлова система та потоки
Gulp працює на віртуальній файловій система, відома як vinylfs. Це означає, що ви можете модифікувати файли, не торкаючись диска, поки не закінчите — це дозволяє Gulp робити кілька потоків без необхідності зберігати в часові файли.
Щоб дізнатися більше про потоки, читайте Stream Handbook.
Установка
Gulp встановлюється ідентично Bower, для глобальної установки наберіть:
<code>$npm install-g gulp
Щоб встановити локально та зберегти в наш package.json:
<code>$npm install--save-dev gulp
Створення робочого процесу
Скажімо, ми хочемо створити єдині CSS та JS-файли, які автоматично будуть включені до нашого шаблону. Ми також повинні мати можливість легко перейти до оригінальних файлів для налагодження.
У нашого робочого процесу є дві цілі. Давайте подивимося на першу — мінімізація та об'єднання:
- знайти всі використовувані файли;
- мінімізувати їх;
- об'єднати файли в один;
- зберегти файли;
- замінити посилання у шаблонах.
Щоб зробити все це, ми будемо використовувати пакети gulp-uglifyjs, gulp-minify-css та gulp-usemin. Для їх встановлення просто виконайте:
<code>$ npm install--save-dev gulp-usemin gulp-uglify gulp-minify-css
Після цього наш робітник процес може виглядати приблизно так:
- gulp.src()
- uglifyjs і minify-css з:
- параметрами concat
- gulp.dest()
- usemin (заміна)
Припустимо, що наш шаблон в даний час знаходиться в /src /templates/layout.tpl. Спочатку скопіюємо його в /src/templates/layout.src.tpl. Цей файл містить інформацію для роботи Gulp, генеруючи layout.tpl для публікації або розробки відповідно.
Далі, додамо кілька директив у наш шаблон для роботи usemin. Ми зробимо це шляхом розміщення спеціальних коментарів навколо блоків з CSS і JavaScript, таких як:
<code data-language="html"><!--build:css /css/site.css--> <link href="/bower_components/bootstrap/dist/css/bootstrap.css" rel="stylesheet"> <link href="/bower_components/bootstrap/dist/css/bootstrap-theme.css" rel="stylesheet"> <!--endbuild-->
І для нашого JavaScript:
<code data-language="javascript"><!--build: js /js/site.js--> <script type="text/javascript" src="/bower_components/jquery/dist/jquery.js"></script> <script type="text/javascript" src="/bower_components/bootstrap/dist/js/bootstrap.js"></script> <!--endbuild-->
Тепер створимо наші завдання та зробимо це у gulpfile.js. Для початку ми повинні отримати всі необхідні нам модулі:
<code data-language="javascript">var gulp=require('gulp'); var usemin=require('gulp-usemin'); var uglify=require('gulp-uglify'); var minifyCss=require('gulp-minify-css');
Далі визначаємо завдання за замовчуванням:
<code data-language="javascript">gulp. task('default', function() { gulp.src('src/templates/layout.src.tpl') .pipe(usemin({ assetsDir: 'public', css: [minifyCss(), 'concat'], js: [uglify(), 'concat'] })) .pipe(gulp.dest('public'));});
Розберемо це код за кроками рядок за рядком. За допомогою gulp.task() ми визначаємо наше завдання з ім'ям default та з функцією зворотного виклику.
Потім відкриваємо src/templates/layout.src.tpl через gulp.src(). Далі вказуємо pipe() разом з usemin та налаштуваннями, в яких встановлено розташування ресурсів, що використовуються в шаблонах (assetsDir), а потім, оскільки ми хочемо обробити CSS та JavaScript-файли, з minifyCss() або uglify(), передаючи їм аргумент concat.
Остаточно вказуємо pipe() разом з gulp.dest(), щоб зберегти всі файли.
Для запуску просто виконайте gulp у командному рядку:
<code>$ gulp [09:11:05] Using gulpfile /path/to/gulpfile.js [09:11:05 ] Starting 'default'... [09:11:07] Finished 'default' after 2.01 s
Проте у нас є дві проблеми. Шаблон копіюється в public/layout.src.tpl, а не app/templates/layout.tpl і ми не маємо шрифтів для Bootstrap.
Щоб виправити це, давайте зробимо деякі завдання. По-перше, це завдання fix-template, яке використовуватиме плагіни gulp-rename і gulp-rimraf. gulp-rename перейменує файл відкритий через gulp.src, тоді як gulp-rimraf видаляє вихідний файл із диска. Потім ми збережемо файл із пам'яті в його нове місце.
<code data-language="javascript">var rename=require('gulp-rename'); var rimraf=require('gulp-rimraf'); gulp.task('fix-template', ['minify'], function() { return gulp.src('public/layout.src.tpl') .pipe(rimraf()) .pipe(rename("layout) .tpl")) .pipe(gulp.dest('src/templates')); });
Щоб зробити запуск автоматичним, ми могли б вказати це завдання залежне від завдання default. Але це означає, що вона буде виконуватися першою, перш ніж файл знаходиться в невірному місці. Так що замість цього ми повинні зробити все навпаки.
Для початку перейменуємо завдання default в minify :
<code data-language="javascript">gulp.task('default', function() {
на
<code data-language="javascript">gulp.task('minify', function() {
Потім додамо завдання minify як залежність від завдання fix-template, вказавши її як другий аргумент для gulp.task():
<code data-language="javascript">gulp.task('fix-template', ['minify'], function() {
Тепер ми запускаємо наші завдання у правильному порядку:
<code>$ gulp fix-template [16:48:29] Using gulp file /path/to/gulpfile.js [16:48:29] Starting 'minify'... [16: 48:29] Finished 'minify' after 44 ms [16:48:29] Starting 'fix-template'... [16:48:29] Finished 'fix-template' after 6.14 ms
Це все ще працює не та до, як ми очікували, на жаль! Тому що наше завдання minify встановлене для запуску асинхронно (за замовчуванням увімкнено максимальний паралелізм), залежність просто потрібна для виклику, але не завершення.
Ми можемо це виправити трьома способами — повертаючи коректний потік, використовуючи функцію зворотного дзвінка або за допомогою promise.
Найпростіший шлях полягає у використанні return: просто додайте його до першого рядка нашого завдання:
<code data-language="javascript">gulp.task('minify', function() { return gulp.src('src/templates/layout.src.tpl') …
З огляду на те, що minify/fix-template це наш набір за замовчуванням, ми можемо створити нове порожнє завдання default з fix-template як залежність і вона буде автоматично запускатися:
<code data-language="javascript">gulp.task( 'default', ['fix-template']);
А ще краще вказати тут всі свої з адачі, щоб Gulp спробував їх виконати за можливості:
<code data-language="javascript">gulp.task('default', ['minify', 'fix-template']);</</p> code>
Це також означає, що ми повинні вирішити залежність з minify для fix-template, потім minify ще буде викликано. Крім того, будь-які інші завдання, які залежать від minify можуть виконуватися відразу, а не залежати від виклику fix-template.
Останнє, що нам потрібно зробити це виправити шрифти Bootstrap. Зараз вони досі живуть у public/bower_components/bootstrap/dist/fonts, але наш site.css як і раніше вказує на відносний шлях ../fonts.
Ми можемо переробити це двома способами: скопіювати шрифти в папку public або просто оновити файл, щоб вказати йому копіювати всередину нашого bower_components. Для цього ми використовуємо простий плагін gulp-replace.
<code data-language="javascript">var replace=require('gulp-replace'); gulp.task('fix-paths', ['minify'], function() { gulp.src('public/css/site.css') .pipe(replace('../', '../bower_components /bootstrap/dist/')) .pipe(gulp.dest('public/css'));});
Зверніть увагу ще раз, що у нас є залежність для завдання minify; ми повинні додати завдання default у нашій залежності:
<code data-language="javascript">gulp.task('default', ['minify' , 'fix-template', 'fix-paths']);
Тепер завдяки паралелізму завдання fix-template та fix-paths (потенційно) працюватимуть одночасно після завершення minify.
Останнє, що ми повинні додати — це заголовок, який вказує, що файл був згенерований автоматично і не треба модифікувати його безпосередньо. Це можна отримати, як ви вже здогадалися, за допомогою gulp-header.
<code data-language="javascript">var header=require('gulp-header'); gulp.task('add-headers', ['fix-template'], function() { gulp.src('src/templates/layout.tpl') .pipe(header("<!--) Цей файл згенерований — не редагуйте його вручну!-->\n")). "/* Цей файл згенерований — не редагуйте його вручну!*/\n")) pipe(header("/* Цей файл згенеровано — не редагуйте його вручну! */ \n")) .pipe(gulp.dest('public/css')); });
Цього разу ми залежимо від fix-template і файл шаблону повинен знаходитися на своєму остаточному місці.
Розробка та очищення
Давайте створимо ще дві прості завдання: clean і dev.
<code data-language="javascript">gulp.task('clean', function() { var generated=['public/js/site.js', 'public/css/site.css', 'src/templates/layout.tpl']; return gulp.src(gen erated) .pipe(rimraf()); }); gulp.task('dev', ['clean'], function() { gulp.src('src/templates/layout.src.tpl') .pipe(rename('layout.tpl')) .pipe(gulp .dest('src/templates')); });
Завдання clean просто видаляє всі згенеровані файли, в той час як завдання dev копіює layout.src.tpl у layout.tpl додаючи наш автоматично згенерований заголовок — залишаючи оригінальні шляхи bower_component на місці.
Ми також можемо додати завдання clean як залежність для завдання minify:
<code data-language="javascript">gulp.task ('minify', ['clean'], function() {
Якщо тепер виконати gulp ми побачимо:
<code>$ gulp [22:56: 15] Using gulpfile /path/to/gulpfile.js [22:56:15] Starting 'clean'... [22:56:15] Finished 'clean' after 46 ms [22:56:15] Starting 'minify '... [22:56:19] Finished 'minify' after 4.81 s [22:56:19] Starting 'fix-template'... [22:56:19] Starting 'fix-paths'. [22:56:19] Finished 'fix-paths' after 8.52 ms [22:56:19] Finished 'fix-template' after 36 ms [22:56:19] Starting 'add-headers'... [ 22:56:19] Finished 'add-headers' after 4.18 ms [22:56:19] Starting 'default'... [22:56:19] Finished 'default' after 24 μs
Це тепер наш варіант розгортання готового проекту.
Автоматизація
Якщо ми хочемо автоматично зберігати наші мінімізовані файли оновленими в процесі розробки, ми можемо використовувати функціональність gulp.watch(). Давайте створимо фінальне завдання з ім'ям watch:
<code data-language="javascript">gulp.task('watch', ['default'] , function() { var watchFiles=[ 'src/templates/layout.src.tpl', 'public/bower_components/*/dist/js/*.js', '!public/bower_components/*/dist/js/* .min.js', 'public/bower_components/*/dist/*.js', 'public/bower_components/*/dist/css/*.css', '!public/bower_components/*/dist/css/*. min.css', 'public/bower_components/*/dist/font/*' ]; gulp.watch(watchFiles, ['default']); наші шаблони, а також усі файли .js та .css з пакетів Bower. Зауважте, що ми виключили файли .min.js і .min.css. Потім ми викликаємо gulp.watch()</span>, передавши в нього масив файлів і завдання <span class="var">default</span>, яке ми хочемо запускати при виявленні змін.</p> <pre><code>$ gulp watch [23:05:01] Using gulpfile /path/to/gulpfile.js [23:05:01] Starting 'watch'... [23:05:01] Finished 'watch' after 30 ms
З цього моменту Gulp сидітиме і чекатиме змін.
Якщо ви хочете переконатися, що це завдання виконується під час запуску, то можете встановити завдання default як залежність:
<code data-language="javascript">gulp.task('watch', ['default'], function() {
Автоматизація автоматизації
Ви, можливо, помітили одну річ — потрібно зробити велику кількість викликів, щоб включити всі необхідні нам плагіни.
Ми можемо скоротити цей список до одного, використовуючи — ось іронія — ще один плагін gulp-load-plugins. чески завантажувати всі плагіни Gulp з нашого package.json використовуючи ліниве завантаження, що робить їх усіх доступними як єдиний об'єкт.
<code data-language="javascript">$=require('gulp-load-plugins ') (); //Зверніть увагу на зайві дужки
З цього моменту всі наші плагіни будуть доступні як $.<плагін>, при цьому з імені відділяється gulp-і застосовується camelCase. Це означає, що gulp-usemin та gulp-uglify стануть $.usemin та $.uglify, відповідно, а gulp-minify-css стане $.minifyCss.
Огляд
Ви можете побачити закінчені gulpfile.js та інші пов'язані з ним файли тут.
Цей приклад коду не призначений для готового проекту!
Перекладіть дихання
Частина інструментів для фронтенду, як і раніше, перебуває на стадії розробки. Вони, безумовно, зведені на плечах гігантів, таких як Composer, Bundler та особливо npm. Сподіватимемося, що одного дня вони стануть з гігантами в один ряд.
Інструменти Node.js працюють, як правило, асинхронно, що дозволяє їм виконувати завдання паралельно і наскільки можливо швидко — що дуже важливо для оперативного розгортання.
Тепер, коли ми використовуємо Gulp та Bower, у наступному розділі ми поглянемо на Yeoman — інструмент для автоматизації скаффолдингу при створенні наступної програми.