GitHub — gulpjs/gulp: A toolkit to automate & enhance your workflow

GitHub - gulpjs/gulp: A toolkit to automate & enhance your workflow Расшифровка

Почему gulp?

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

Существует уже масса статей по поводу различий между Grunt и Gulp, и почему одно приложение стоит использовать, а другое нет. Brunch по типу задач тоже очень похожа на описанные выше программы, также выполняет такие задачи связанные с сервером или проверкой файлов.

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

Sample gulpfile.js

This file will give you a taste of what gulp does.

vargulp=require('gulp');varless=require('gulp-less');varbabel=require('gulp-babel');varconcat=require('gulp-concat');varuglify=require('gulp-uglify');varrename=require('gulp-rename');varcleanCSS=require('gulp-clean-css');vardel=require('del');varpaths={styles: {src: 'src/styles/**/*.less',dest: 'assets/styles/'},scripts: {src: 'src/scripts/**/*.js',dest: 'assets/scripts/'}};/* Not all tasks need to use streams, a gulpfile is just another node program * and you can use all packages available on npm, but it must return either a * Promise, a Stream or take a callback and call it */functionclean(){// You can use multiple globbing patterns as you would with `gulp.src`,// for example if you are using del 2.0 or above, return its promisereturndel(['assets']);}/* * Define our tasks using plain functions */functionstyles(){returngulp.src(paths.styles.src).pipe(less()).pipe(cleanCSS())// pass in options to the stream.pipe(rename({basename: 'main',suffix: '.min'})).pipe(gulp.dest(paths.styles.dest));}functionscripts(){returngulp.src(paths.scripts.src,{sourcemaps: true}).pipe(babel()).pipe(uglify()).pipe(concat('main.min.js')).pipe(gulp.dest(paths.scripts.dest));}functionwatch(){gulp.watch(paths.scripts.src,scripts);gulp.watch(paths.styles.src,styles);}/* * Specify if tasks run in series or parallel using `gulp.series` and `gulp.parallel` */varbuild=gulp.series(clean,gulp.parallel(styles,scripts));/* * You can use CommonJS `exports` module notation to declare tasks */exports.clean=clean;exports.styles=styles;exports.scripts=scripts;exports.watch=watch;exports.build=build;/* * Define default task that can be called by just running `gulp` from cli */exports.default=build;

Use latest javascript version in your gulpfile

Most new versions of node support most features that Babel provides, except the import/export syntax. When only that syntax is desired, rename to gulpfile.esm.js, install the esm module, and skip the Babel portion below.

Node already supports a lot of ES2022 features, but to avoid compatibility problems we suggest to install Babel and rename your gulpfile.js to gulpfile.babel.js.

npm install --save-dev @babel/register @babel/core @babel/preset-env

Then create a .babelrc file with the preset configuration.

And here’s the same sample from above written in ES2022 .

importgulpfrom'gulp';importlessfrom'gulp-less';importbabelfrom'gulp-babel';importconcatfrom'gulp-concat';importuglifyfrom'gulp-uglify';importrenamefrom'gulp-rename';importcleanCSSfrom'gulp-clean-css';importdelfrom'del';constpaths={styles: {src: 'src/styles/**/*.less',dest: 'assets/styles/'},scripts: {src: 'src/scripts/**/*.js',dest: 'assets/scripts/'}};/* * For small tasks you can export arrow functions */exportconstclean=()=>del(['assets']);/* * You can also declare named functions and export them as tasks */exportfunctionstyles(){returngulp.src(paths.styles.src).pipe(less()).pipe(cleanCSS())// pass in options to the stream.pipe(rename({basename: 'main',suffix: '.min'})).pipe(gulp.dest(paths.styles.dest));}exportfunctionscripts(){returngulp.src(paths.scripts.src,{sourcemaps: true}).pipe(babel()).pipe(uglify()).pipe(concat('main.min.js')).pipe(gulp.dest(paths.scripts.dest));}/* * You could even use `export as` to rename exported tasks */functionwatchFiles(){gulp.watch(paths.scripts.src,scripts);gulp.watch(paths.styles.src,styles);}export{watchFilesaswatch};constbuild=gulp.series(clean,gulp.parallel(styles,scripts));/* * Export a default task */exportdefaultbuild;

Возвращает

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

Всякий раз, когда в файловой системе создается символическая ссылка, объект Vinyl будет изменен.

  • Свойства cwd, base и path будут обновлены в соответствии с созданной символической ссылкой.
  • Свойство stat будет обновлено, чтобы соответствовать символической ссылке в файловой системе.
  • Свойство contents будет установлено как null.
  • Свойство symlink будет добавлено или заменено на исходный путь.

Примечание. В Windows ссылки на каталоги создаются с использованием соединений по умолчанию. Опция useJunctions отключает это поведение.

Добавление файлов в поток

src() также может быть размещен в середине конвейера для добавления файлов в поток на основе Globs (Шаблонов поиска). Дополнительные файлы будут доступны только для преобразований в потоке позже. Если Globs перекрываются, файлы будут добавлены снова.

Два или более globs, которые намеренно или не намеренно совпадают с одним и тем же файлом, считаются перекрывающимися. Когда перекрывающиеся Globs используются внутри одного src(), gulp делает все возможное, чтобы удалить дубликаты.

Это может быть полезно для переноса некоторых файлов перед добавлением простых файлов JavaScript в конвейер или удалением всего.

const { src, dest } = require('gulp');
const babel = require('gulp-babel');
const uglify = require('gulp-uglify');
exports.default = function() {	return src('src/*.js')	.pipe(babel())	.pipe(src('vendor/*.js'))	.pipe(uglify())	.pipe(dest('output/'));
}

Избегайте дублирования задач

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

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

Если у вас есть такой код:

// Пример НЕКОРРЕКТНЫЙ
const { series, parallel } = require('gulp');
const clean = function(cb) {	// тело функции	cb();
};
const css = series(clean, function(cb) {	// тело функции	cb();
});
const javascript = series(clean, function(cb) {	// тело функции	cb();
});
exports.build = parallel(css, javascript);

Лучше сделать так:

const { series, parallel } = require('gulp');
function clean(cb) {	// тело функции	cb();
}
function css(cb) {	// тело функции	cb();
}
function javascript(cb) {	// тело функции	cb();
}
exports.build = series(clean, parallel(css, javascript));

Использование

Пример gulpfile:

const { series, parallel } = require('gulp');
function one(cb) {	// тело функции	cb();
}
function two(cb) {	// тело функции	cb();
}
function three(cb) {	// тело функции	cb();
}
const four = series(one, two);
const five = series(four,	parallel(three, function(cb) {	// тело функции	cb();	})
);
module.exports = { one, two, three, four, five };

Вывод для tree():

{	label: 'Tasks',	nodes: [ 'one', 'two', 'three', 'four', 'five' ]
}

Вывод для tree({ deep: true }):

{	label: "Tasks",	nodes: [	{	label: "one",	type: "task",	nodes: []	},	{	label: "two",	type: "task",	nodes: []	},	{	label: "three",	type: "task",	nodes: []	},	{	label: "four",	type: "task",	nodes: [	{	label: "<series>",	type: "function",	branch: true,	nodes: [	{	label: "one",	type: "function",	nodes: []	},	{	label: "two",	type: "function",	nodes: []	}	]	}	]	},	{	label: "five",	type: "task",	nodes: [	{	label: "<series>",	type: "function",	branch: true,	nodes: [	{	label: "<series>",	type: "function",	branch: true,	nodes: [	{	label: "one",	type: "function",	nodes: []	},	{	label: "two",	type: "function",	nodes: []	}	]	},	{	label: "<parallel>",	type: "function",	branch: true,	nodes: [	{	label: "three",	type: "function",	nodes: []	},	{	label: "<anonymous>",	type: "function",	nodes: []	}	]	}	]	}	]	}	]
}

Использование первого агрумента с ошибкой

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

function callbackTask(cb) {	// `cb()` вызывается как пример выполнения какой-то работы	cb();
}
exports.default = callbackTask;

Чтобы указать Gulp, что ошибка произошла в задаче, использующей колбек первого агрумента с ошибкой, вызовите ее с Error в качестве единственного аргумента.

function callbackError(cb) {	// `cb()` вызывается как пример выполнения какой-то работы	cb(new Error('kaboom'));
}
exports.default = callbackError;

Однако, чаще всего вы будете передавать этот колбек другому API, а не вызывать его самостоятельно.

const fs = require('fs');
function passingCallback(cb) {	fs.access('gulpfile.js', cb);
}
exports.default = passingCallback;

Использование плагинов

Плагины Gulp — это преобразующие Node потоки, которые инкапсулируют обычное поведение для преобразования файлов в конвейер. В большинстве случаев размещаются между src() и dest() с помощью метода .pipe(). Они могут изменить имя файла, метаданные или содержимое каждого файла, который проходит через поток.

Плагины из npm, с использованием ключевых слов «gulpplugin» и «gulpfriendly» можно просматривать и искать на странице поиска плагинов.

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

const { src, dest } = require('gulp');
const uglify = require('gulp-uglify');
const rename = require('gulp-rename');
exports.default = function() {	return src('src/*.js')	// Плагин gulp-uglify не будет обновлять имя файла	.pipe(uglify())	// Поэтому используйте gulp-rename, чтобы изменить его	.pipe(rename({ extname: '.min.js' }))	.pipe(dest('output/'));
}

Метаданные задачи

СвойствоТипПримечание
namestringОсобое свойство именованных функций. Используется для регистрации задачи. Примечание: name не доступно для записи, его нельзя установить или изменить.
displayNamestringПри присоединении к taskFunction создается псевдоним задачи. В случаях, когда требуется использовать символы, которые не разрешены в именах функций, используйте это свойство.
descriptionstringПри присоединении к taskFunction предоставляет описание, которое будет напечатано в командной строке при выводе списка задач.
flagsobjectПри присоединении к функции taskFunction предоставляет флаги, которые должны быть напечатаны в командной строке при перечислении задач. Ключи объекта представляют флаги, а значения — их описания.
const { task } = require('gulp');
const clean = function(cb) {	// тело функции	cb();
};
clean.displayName = 'clean:all';
task(clean);
function build(cb) {	// тело функции	cb();
}
build.description = 'Билд проекта';
build.flags = { '-e': 'Пример флага' };
task(build);

Методы экземпляра

МетодВозвращаемый типВозвращает
isBuffer()booleanЕсли свойством экземпляра contents является Buffer, возвращает true.
isStream()booleanЕсли свойством экземпляра contents является Stream, возвращает true.
isNull()booleanЕсли свойством экземпляра contents является null, возвращает true.
isDirectory()booleanЕсли экземпляр представляет каталог, возвращает true. Экземпляр считается каталогом, когда isNull() возвращает true, свойство экземпляра stat является объектом и stat.isDirectory() возвращает true. Это предполагает, что объект Vinyl был создан с допустимым (или правильно сказать смоделированным) объектом fs.Stats.
isSymbolic()booleanЕсли экземпляр представляет собой символическую ссылку, возвращает true. Экземпляр считается символическим, когда isNull() возвращает true, свойство экземпляра stat является объектом и stat.isSymbolicLink() возвращает true. Это предполагает, что объект Vinyl был создан с допустимым (или правильно сказать смоделированным) объектом fs.Stats.
clone([options])objectНовый объект Vinyl со всеми клонированными свойствами. По умолчанию все пользовательские свойства клонированы. Если параметр deep имеет значение false, пользовательские атрибуты будут клонированы не глубоко. Если для параметра contents установлено значение false, а свойством экземпляра содержимого является Buffer, то Buffer будет использоваться повторно, а не клонироваться.
inspect()stringВозвращает отформатированную интерпретацию объекта Vinyl. Автоматически вызывается по Node’s console.log.
Другие сокращения:  Дозик - Персонаж

Наблюдение за файлами

API watch() связывает globs с тасками используя вотчер файловой системы. Он отслеживает изменения в файлах, соответствующих шаблону или шаблонам Glob и выполняет задачу, если происходит изменение. Если задача не сигнализирует об асинхронном выполнении, она никогда не будет запущена второй раз.

Этот API-интерфейс обеспечивает встроенную задержку и организацию очереди на основе наиболее распространенных значений по умолчанию.

const { watch, series } = require('gulp');
function clean(cb) {	// тело функции	cb();
}
function javascript(cb) {	// тело функции	cb();
}
function css(cb) {	// тело функции	cb();
}
exports.default = function() {	// Вы можете использовать одну задачу	watch('src/*.css', css);	// Или композицию тасков	watch('src/*.js', series(clean, javascript));
};

Параметры

Если taskName не указано, будет использовано свойство name именованной функции или пользовательское свойство displayName. Параметр taskName должен использоваться для анонимных функций, в которых отсутствует свойство displayName.

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

ПараметрТипПримечание
taskNamestringПсевдоним для функции задачи в системе задач. Не требуется при использовании именованных функций для taskFunction.
taskFunction (required)functionФункция задачи (таска) или составная задача — генерируется с помощью series() и parallel(). В идеале, именованная функция. Метаданные задачи могут быть прикреплены для предоставления дополнительной информации в командной строке.

Пишем нашу первую задачу

Первым делом нужно подключить Gulp в нашем файле.

Require говорит Node.js проверить папку node_modules и найти там папку gulp. Если такая имеется, то ее содержимое записывается в переменную gulp. Теперь можно писать задачи Gulp с нашей переменной. Базовый синтаксис:

Task-name – имя задачи, будет использоваться когда угодно при запуске задач в Gulp. Также задачу можно запустить через командную строку с помощью gulp task-name. Для теста создадим задачу hello, которая будет говорить Hello Zell!!.

Запустить задачу можно из командной строки.

В логах командной строки вернется Hello Zell!!.

Задачи в Gulp, обычно, немного сложнее, чем эта. Обычно, в задаче содержится два дополнительных метода Gulp и различные плагины. Ниже показано, как может выглядеть реальная задача:

Как можно заметить, настоящая задача использует два дополнительных метода – gulp.src и gulp.dest. Gulp.src говорит, какой файл использовать для задачи, а gulp.dest указывает на папку, куда поместить файлы после завершения задачи. Попробуем создать реальную задачу, скомпилируем Sass в CSS.

Подстановки в node

Подстановки позволяют добавить больше одного файла в gulp.src. Это похоже на регулярные выражения, но только для директорий. При использовании подстановок компьютер проверяет имена файлов и путей на совпадение шаблону. Если шаблон существует, то файл найден. Для большинства задач, как правило, требуется 4 различных модели подстановки:

*.scss: Символ * совпадает с любым шаблоном в текущей директории. В нашем случае мы ищем все файлы с окончанием .scss в корневой папке project.

**/*.scss: Это более продвинутый шаблон, который ищет файлы с окончанием .scss в корне и всех дочерних папках.

!not-me.scss: Символ ! указывает, что Gulp исключит из результата совпадений определенный файл. В нашем случае исключен будет not-me.scss.

*. (scss|sass): Знак и круглые скобки () помогают создавать множественные шаблоны, шаблоны разделяются символом |. В нашем случае Gulp найдет все файлы с окончанием .scss или .sass в корневой папке.

Теперь, когда мы знаем о подстановках, можно заменить app/scss/styles.scss на шаблон scss/ **/*.scss, данный шаблон совпадает с любым файлом .scss в папке app/scss или дочерней директории.

Все найденные Sass файлы будут автоматически подключены к задаче sass. Если добавить в проект файл print.scss, то в папке app/css появится файл print.css.

Нам удалось скомпилировать все файлы Sass в CSS всего одной командой. А что же собственно хорошего в том, чтобы каждый раз для компиляции самому запускать задачу gulp sass? К счастью мы можем задать автоматический запуск нашей задачи sass при любом пересохранении файла с помощью метода «watching».

Полезные материалы урока:

  1. Документация Gulp
  2. Проект-пример данного урока на GitHub
  3. Обновление Gulp до версии 4
  4. Установка рабочего окружения в Windows WSL
  5. Node.js
  6. Browsersync Documentation
  7. Стартер OptimizedHTML 5 на основе Gulp 4

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

Работа с gulpfile.js

Внимание! Все куски кода с примерами будут объединены в один полноценный пример «gulpfile.js» со всеми комментариями в конце данного урока.

Для начала определим константы Gulp в «gulpfile.js»:

// Определяем константы Gulp
const { src, dest, parallel, series, watch } = require('gulp');

Именно с помощью require() мы подключаем модули из папки «node_modules» и присваиваем их переменной или, как в нашем случае, константам.

Установим Live Server, который позволит нам использовать возможности локального сервера и автоматически обновлять страницы при изменениях в файлах. Лучшее решение — Browsersync. Давайте его установим командой:

npm i browser-sync --save-dev

Подключим Browsersync в проект:

// Определяем константы Gulp
const { src, dest, parallel, series, watch } = require('gulp');
// Подключаем Browsersync
const browserSync = require('browser-sync').create();

Здесь необходимо указать .create() для создания нового подключения.

Далее напишем функцию, которая определит логику работы «Browsersync». В отличие от Gulp 3, в Gulp 4 логика работы в комбайне не является таском. Это просто функция, которую можно экспортировать в таск или добавить в набор экспорта.

// Определяем логику работы Browsersync
function browsersync() {	browserSync.init({ // Инициализация Browsersync	server: { baseDir: 'app/' }, // Указываем папку сервера	notify: false, // Отключаем уведомления	online: true // Режим работы: true или false	})
}

Обратите внимание, что название функции не должно совпадать с названием переменной или константы, в которую мы подключаем пакет. Поэтмоу, в данном случае, название функции browsersync() будет содержать только строчные буквы.При использовании какого-либо модуля, рекомендую всегда читать его документацию на официальном сайте или на сайта npmjs.org. Как правило, разницы большой нет, где смотреть инструкцию, однако лучше отдавать предпочтение оф. сайту, так как информация на сайте npmjs.org может обновляться не сразу или иметь не полные инструкции.

Если в терминале выполнить команду gulp browsersync, мы получим ошибку «Task never defined: browsersync», так как функция browsersync() — это не таск, готовый к запуску.

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

Допишем далее в gulpfile.js:

// Экспортируем функцию browsersync() как таск browsersync. Значение после знака = это имеющаяся функция.
exports.browsersync = browsersync;

Запускаем новый таск командой:

gulp browsersync

После запуска мы увидим в браузере белую страницу с надписью «Cannot GET /». Это говорит о том, что в папке проекта «app/» нет индексного файла.

Если мы создадим в папке «app/» индексный файл «index.html», напишем в него что-нибудь и сохраним файл, то, после обновления страницы, мы сможем узреть в браузере результат нашего творчества.

Параметр online отвечает за режим работы. Укажите online: false, если хотите работать без подключения к интернету.

Работа с файлами

Методы src() и dest() используются для взаимодействия с файлами на вашем компьютере.

src() использует glob для чтения файловой системы и создания потока Node. Он находит все подходящие файлы и считывает их в память для прохождения через поток.

Glob — Шаблон поиска с использованием метасимволов, например **/*.js.

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

const { src, dest } = require('gulp');
exports.default = function() {	return src('src/*.js')	.pipe(dest('output/'));
}

Основным API потока является метод .pipe() для объединения потоков Transform или Writable.

const { src, dest } = require('gulp');
const babel = require('gulp-babel');
exports.default = function() {	return src('src/*.js')	.pipe(babel())	.pipe(dest('output/'));
}

dest() получает строку пути выходного каталога, а также создает поток Node. Когда метод получает файл, переданный через конвейер, он записывает содержимое и другие детали в файловую систему в данном каталоге. Метод symlink() также доступен и работает как dest(), но создает ссылки вместо файлов (подробности см. в symlink() API).

Другие сокращения:  рилем

Чаще всего плагины размещаются между src() и dest() с помощью метода .pipe() и преобразуют файлы в потоке.

Работа со скриптами

Создадим функцию scripts() до экспорта задач. Данная функция будет обрабатывать скрипты нашего проекта:

function scripts() {	return src([ // Берем файлы из источников	'node_modules/jquery/dist/jquery.min.js', // Пример подключения библиотеки	'app/js/app.js', // Пользовательские скрипты, использующие библиотеку, должны быть подключены в конце	])	.pipe(concat('app.min.js')) // Конкатенируем в один файл	.pipe(uglify()) // Сжимаем JavaScript	.pipe(dest('app/js/')) // Выгружаем готовый файл в папку назначения	.pipe(browserSync.stream()) // Триггерим Browsersync для обновления страницы
}

Для работы данной функции нам понадобятся модули «gulp-concat» и «gulp-uglify-es». Установим их в наш проект. Устанавливать несколько пакетов можно простым перечислением без каких-либо разделяющих символов одной командой:

npm i gulp-concat gulp-uglify-es --save-dev

И подключим данные модули к проекту в верхней части «gulpfile.js»:

// Определяем константы Gulp
const { src, dest, parallel, series, watch } = require('gulp');
// Подключаем Browsersync
const browserSync = require('browser-sync').create();
// Подключаем gulp-concat
const concat = require('gulp-concat');
// Подключаем gulp-uglify-es
const uglify = require('gulp-uglify-es').default;

Создадим в папке «app/» новую папку «js/» и в ней уже создадим новый файл «app.js». Для примера можно разместить следующий код в файле «app.js»:

$(document).ready(function() {	// $('body').hide()
})

Давайте разберемся, что происходит в функции scripts() нашего «gulpfile.js». Я буду указывать соответствующую строку кода из примера выше и объяснять, что мы делаем.

  • Строка 1: Создаем функцию scripts()
  • Строка 2: Возвращаем через return и тут-же открываем источник посредством src для объекта Vinyl.
  • Строки 3 и 4: Перечисление нескольких файлов в качестве источника.

    Внимание! В строке 3 мы подключаем jQuery из модулей. Его, соответственно, также нужно установить командой npm i jquery --save-dev

    Имейте ввиду, что подключение пользовательских скриптов, в которых могут быть использованы какие-либо JS библиотеки, нужно размещать после подключения библиотек в потоке, так как в процессе конкатенации файлы сливаются именно в той последовательности, в которой перечисляются пути до файлов в src, а подключение API библиотеки должно предшествовать использованию.

  • Строка 6: Конкатенация (слияние) содержимого перечисленных выше файлов в один виртуальный. Здесь мы должны указать название результирующего файла, в нашем случае это «app.min.js». Для удобства каждый новый вызов .pipe() рекомендую писать с новой строки.

    Так как concat не является частью Gulp, его можно установить дополнительно, как и другие дополнительные модули командой npm i gulp-concat --save-dev и подключить к проекту в верхней части «gulpfile.js» (мы это уже сделали выше).

  • Строка 7: Сжатие скриптов посредством модуля «gulp-uglify-es», который мы установили и подключили ранее, вместе с «gulp-concat». Обратите внимание, что данный модуль необходимо подключать с параметром .default в конце:
    const uglify = require('gulp-uglify-es').default;
  • Строка 8: Выгрузка результирующего файла в указанную директорию посредством dest().
  • Строка 9: Вызываем Browsersync для перезагрузки страницы. .stream() используется для инъекции в код, без hard reload, однако в данном случае произойдет именно перезагрузка страницы, так как Browsersync знает, что это лучший вариант для работы со скриптами. Если мы работаем со стилями, например, жесткая перезагрузка не обязательна и Browsersync просо подставит новый код в браузере, без перезагрузки страницы. Это мы рассмотрим далее в уроке.

Далее экспортируем функцию scripts() в таск. В нижней части «gulpfile.js», где у нас размещен предыдущий экспорт, добавляем экспорт таска scripts:

// Экспортируем функцию browsersync() как таск browsersync. Значение после знака = это имеющаяся функция.
exports.browsersync = browsersync;
// Экспортируем функцию scripts() в таск scripts
exports.scripts = scripts;

Поначалу может показаться, что система экспорта функций в таски не совсем удобна, ведь раньше, в Gulp версии 3, мы сразу писали таски, которые уже были готовы к использованию без экспорта. Но здесь фишка в том, что именно с помощью exports можно комбинировать любым способом любые функции. Это намного круче, намного удобнее и работает все намного быстрее. По мере изучения данного урока вы в этом убедитесь.

Запустим gulp scripts и проверим в терминале, как работает наш таск:

Таск scripts

Таск работает отлично. Если перейти в папку «app/js/», можно узреть вновь созданный минифицированный файл «app.min.js» со скриптами проекта. Открыв этот файл, мы увидим, что там находится скрипт библиотеки jQuery, а в конце строки наш пример кода из «app/js/app.js».

Добавим разметку-пример в файл «app/index.html» с подключенным скриптом, стилями и изображением:

Работа со стилями

Аналогичным образом можно поработать и со стилями. В папке «app/» создадим две папки — «sass» и «less». В папке «sass» создадим новый файл «main.sass», а в папке «less» создадим файл «main.less». Для примера их можно наполнить следующим содержимым:

// Содержимое файла main.sass
body	// display: none	display: grid
// Содержимое файла main.less
body {	display: none;
}

Ориентируясь на предыдущий опыт, установим одной командой модули «gulp-sass», «sass», «gulp-less», «gulp-autoprefixer» и «gulp-clean-css»:

npm i --save-dev gulp-sass sass gulp-less gulp-autoprefixer gulp-clean-css

И подключим их в проект:

// Определяем константы Gulp
const { src, dest, parallel, series, watch } = require('gulp');
// Подключаем Browsersync
const browserSync = require('browser-sync').create();
// Подключаем gulp-concat
const concat = require('gulp-concat');
// Подключаем gulp-uglify-es
const uglify = require('gulp-uglify-es').default;
// Подключаем модули gulp-sass и gulp-less
const sass = require('gulp-sass')(require('sass'));
const less = require('gulp-less');
// Подключаем Autoprefixer
const autoprefixer = require('gulp-autoprefixer');
// Подключаем модуль gulp-clean-css
const cleancss = require('gulp-clean-css');


Внимание! Для корректной работы Sass в настоящее время требуется установить дополнительный пакет «sass» (npm i -D sass), а подключать модули Sass в gulpfile.js следует следующим образом:

const sass = require('gulp-sass')(require('sass'));

Создадим переменную preprocessor в самом начале «gulpfile.js»:

// Определяем переменную "preprocessor"
let preprocessor = 'sass'; // Выбор препроцессора в проекте - sass или less
// Определяем константы Gulp
const { src, dest, parallel, series, watch } = require('gulp');
// ...

Теперь создадим функцию styles(), которая будет обрабатывать стили проекта, конкатенировать и сжимать. Обратите внимание, что мы используем наименование функции styles(), а не sass(), так как помимо Sass, у нас в проекте будет использоваться и Less. В качестве академического примера мы реализуем и такую возможность.

Давайте ознакомимся с полной функцией и будем разбираться, что здесь происходит:

Сборка проекта (build)

И последнее, что хотелось бы сегодня реализовать — это сборка проекта или build. Создадим в корне проекта (рядом с папкой «app/») папку «dist/», в которую будем собирать наш проект. Обратите внимание, что у нас уже все собрано изначально (стили, скрипты, сжаты изображения) и build я покажу только для академического примера, в реально работе данным подходом я пользуюсь редко.

Добавим новую функцию buildcopy():

function buildcopy() {	return src([ // Выбираем нужные файлы	'app/css/**/*.min.css',	'app/js/**/*.min.js',	'app/images/dest/**/*',	'app/**/*.html',	], { base: 'app' }) // Параметр "base" сохраняет структуру проекта при копировании	.pipe(dest('dist')) // Выгружаем в папку с финальной сборкой
}

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

Данную функцию экспортировать не обязательно, так как она будет являться частью таска build и автономно использоваться не будет. Создадим таск build и выполним нужные функции последовательно друг за другом с помощью series():

// Создаем новый таск "build", который последовательно выполняет нужные операции
exports.build = series(styles, scripts, images, buildcopy);

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

Соберем наш проект, выполнив в терминале:

gulp build

Для очистки папки «dist/» можно создать дополнительную функцию cleandist() по аналогии с cleanimg() и добавить ее в таск build для предварительной очистки целевой папки:

function cleandist() {	return del('dist/**/*', { force: true }) // Удаляем все содержимое папки "dist/"
}
// Создаем новый таск "build", который последовательно выполняет нужные операции
exports.build = series(cleandist, styles, scripts, images, buildcopy);

Свойства экземпляра

Все пути с внутренним управлением (любое свойство экземпляра, за исключением contents и stat) нормализуются и удаляются конечные разделители. См. «Нормализация и объединение» ниже, для получения дополнительной информации.

СвойствоТипОписаниеСброс
contentsReadableStream
Buffer
null
Получает и задает содержимое виртуального файла. Если установлено значение ReadableStream, оно помещается в клонируемо-читаемый поток.Если установлено любое значение, отличное от ReadableStream, Buffer или null.
statobjectПолучает и задает экземпляр fs.Stats. Используется при определении того, представляет ли объект Vinyl каталог или символическую ссылку.
cwdstringПолучает и устанавливает текущий рабочий каталог. Используется для определения относительных путей.Если задана пустая строка или любое нестроковое значение.
basestringПолучает и устанавливает базовый каталог. Используется для вычисления свойства relative экземпляра. На объекте Vinyl, сгенерированном с помощью src(), будет установлено glob base. Если установлено значение null или undefined, сработает фолбек к значению свойства экземпляра cwd.Если задана пустая строка или любое значение non-string (кроме null или undefined).
pathstringПолучает и задает полный абсолютный путь к файлу. Установка значения, отличного от текущего path, добавляет новый путь к свойству экземпляра history.Если установлено любое non-string значение.
historyarrayМассив всех значений path, которым был назначен объект Vinyl. Первый элемент — это исходный путь, а последний элемент — текущий путь. Данное свойство и его элементы должны рассматриваться как доступные только для чтения и изменяться только косвенно, путем установки свойства экземпляра path.
relativestringПолучает относительный сегмент пути между свойствами экземпляра base и path.Если установлено любое значение. Если есть доступ, когда path недоступен.
dirnamestringПолучает и задает каталог свойства экземпляра path.Если есть доступ, когда path недоступен.
stemstringПолучает и задает основу (имя файла без расширения) свойства экземпляра .Если есть доступ, когда path недоступен.
extnamestringПолучает и задает расширение свойства экземпляра path.Если есть доступ, когда path недоступен.
basenamestringПолучает и задает имя файла (stem extname) свойства экземпляра path.Если есть доступ, когда path недоступен.
symlinkstringПолучает и задает референсный путь символической ссылки.Если установлено любое non-string значение.
Другие сокращения:  Средства индивидуальной защиты | Средства защиты кожи | Кабинет ОБЖ

Символические ссылки в windows

При создании символических ссылок в Windows аргумент type передается методу Node’s fs.symlink(), который указывает тип цели. Тип ссылки установлен на:

  • 'file', если целью является обычный файл
  • 'junction', когда целью является каталог
  • 'dir', если целью является каталог и пользователь отключает опцию useJunctions

Если вы попытаетесь создать висячую (указывающую на несуществующую цель) ссылку (или висячий указатель), тип ссылки не может быть определен автоматически. В этих случаях поведение будет зависеть от того, создается ли висячая ссылка с помощью symlink() или dest().

Для висячих ссылок (указателей), созданных с помощью symlink(), входящий объект Vinyl представляет цель, поэтому его статистика будет определять желаемый тип ссылки. Если isDirectory() возвращает false, то создается ссылка ‘file’, в противном случае создается ссылка ‘junction’ или ‘dir’, в зависимости от значения опции useJunctions.

Для висячих ссылок (указателей), созданных с помощью dest(), входящий объект Vinyl представляет ссылку, обычно загружаемую с диска через src(…, {resolSymlinks: false}). В таком случае тип ссылки не может быть адекватно определен и по умолчанию используется ‘file’. Это может вызвать неожиданное поведение при создании висячей ссылки на каталог. Избегайте этого сценария.

Составление тасков

Gulp предоставляет два мощных метода для создания тасков — series() и parallel(), позволяющие объединять отдельные задачи в более крупные операции. Оба метода принимают любое количество функций или составных операций. series() и parallel() могут быть вложены сами в себя или друг в друга на любую глубину.

Чтобы ваши задачи выполнялись по порядку, используйте метод series().

const { series } = require('gulp');
function transpile(cb) {	// тело функции	cb();
}
function bundle(cb) {	// тело функции	cb();
}
exports.build = series(transpile, bundle);

Чтобы задачи выполнялись параллельно, объедините их методом parallel().

const { parallel } = require('gulp');
function javascript(cb) {	// тело функции	cb();
}
function css(cb) {	// тело функции	cb();
}
exports.build = parallel(javascript, css);

Таски выполняются сразу при вызове series() или parallel(). Это позволяет варьировать состав тасков, в зависимости от условий.

const { series } = require('gulp');
function minify(cb) {	// тело функции	cb();
}
function transpile(cb) {	// тело функции	cb();
}
function livereload(cb) {	// тело функции	cb();
}
if (process.env.NODE_ENV === 'production') {	exports.build = series(transpile, minify);
} else {	exports.build = series(transpile, livereload);
}

series() и parallel() могут быть вложены на любую глубину.

const { series, parallel } = require('gulp');
function clean(cb) {	// тело функции	cb();
}
function cssTranspile(cb) {	// тело функции	cb();
}
function cssMinify(cb) {	// тело функции	cb();
}
function jsTranspile(cb) {	// тело функции	cb();
}
function jsBundle(cb) {	// тело функции	cb();
}
function jsMinify(cb) {	// тело функции	cb();
}
function publish(cb) {	// тело функции	cb();
}
exports.build = series(	clean,	parallel(	cssTranspile,	series(jsTranspile, jsBundle)	),	parallel(cssMinify, jsMinify),	publish
);

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

Если у вас такой код:

// Так делать НЕЛЬЗЯ
const { series, parallel } = require('gulp');
const clean = function(cb) {	// тело функции	cb();
};
const css = series(clean, function(cb) {	// тело функции	cb();
});
const javascript = series(clean, function(cb) {	// тело функции	cb();
});
exports.build = parallel(css, javascript);

Лучше сделать так:

const { series, parallel } = require('gulp');
function clean(cb) {	// тело функции	cb();
}
function css(cb) {	// тело функции	cb();
}
function javascript(cb) {	// тело функции	cb();
}
exports.build = series(clean, parallel(css, javascript));

Специальный символ: ! (негативный, отрицательный)

Если globs перечисляются в порядке массива, отрицательный glob должен следовать по крайней мере за одним неотрицательным glob в массиве. Первый находит набор совпадений, затем отрицательный glob удаляет часть этих результатов. При исключении всех файлов в подкаталоге необходимо добавить /** после имени каталога.

['scripts/**/*.js', '!scripts/vendor/**']

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

['scripts/**/*.js', '!scripts/vendor/**', 'scripts/vendor/react.js']

Отрицательные globs могут быть использованы в качестве альтернативы для ограничения globs с двойными звездами.

['**/*.js', '!node_modules/**']

В данном примере, если бы отрицательный glob был с расширением .js !node_modules/**/*.js, globbing-библиотека не оптимизировала бы отрицание, и каждое совпадение пришлось бы сравнивать с отрицательным glob, который работал бы чрезвычайно медленно. Чтобы игнорировать все файлы в каталоге, лучше добавить только /** после имени каталога.

Точность временной метки

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

  • lastRun(someTask) вернет 1426000001111
  • lastRun(someTask, 100) вернет 1426000001100
  • lastRun(someTask, 1000) вернет 1426000001000

Точность mtime stat файла может варьироваться в зависимости от версии Node и/или используемой файловой системы.

ПлатформаТочность
Node v0.101000ms
Node v0.121ms
Файловая система FAT322000ms
Файловая система HFS или Ext31000ms
NTFS с использованием Node v0.101s
NTFS с использованием Node 0.12100ms
Ext4 с использованием Node v0.101000ms
Ext4 с использованием Node 0.121ms

Установка gulp и настройка

Для начал необходимо установить окружение. Если вы пользователь macOS или Windows, вы можете загрузить Node.js версии LTS с сайта Nodejs.org или воспользоваться актуальным способом установки окружения, который я предлагаю для работы с использованием WSL.

После установки окружения можно приступать к работе. Откроем терминал в папке проекта: создадим папку «html» на вашем компьютере и откроем в ней терминал.

Если вы пользователь Windows, для того, чтобы открыть терминал bash или командную строку в нужной папке, просто зажмите клавишу Shift и нажать правую кнопку мыши.Внимание! Лучше не создавать русскоязычные папки. Избегайте кириллицы в путях вашего проекта, папка вашего пользователя должна быть написана также латиницей для корректной работы.

Выполним инициализацию проекта командой npm init и укажем название нашего проекта myproject:

Инициализация проекта npm init

Если у вас нет желания заполнять остальные поля, можно оставить их пустыми, нажимая «Enter» или заполнить на свое усмотрение. По окончании заполнения полей, введите yes и нажмите «Enter».

Завершение инициализации проекта Gulp в npm init

Проект создан. У нас появился файл «package.json». Это файл манифеста нашего нового проекта, который, помимо той информации, что мы указали в терминале, содержит также информацию о используемых в нашем проекте пакетах и их версиях. Если в дальнейшем нам необходимо будет заново установить все используемые в проекте пакеты, можно будет сделать это всего одной командой npm i. В проект будут автоматически установлены модули соответствующих версий.

Файл package.json нашего проекта

Для начала установим Gulp локально в наш проект командой:

npm i gulp --save-dev

Если вы хотите, чтобы ваши зависимости были отражены в секции devDependencies манифеста, нужно обязательно указывать ключ --save-dev.

Теперь в файле package.json, в секции devDependencies появился gulp и информация о его текущей версии. После установки других пакетов таким-же образом, информация о них также будет отражена в «package.json».

Экземпляр chokidar

Метод watch() возвращает базовый экземпляр chokidar, предоставляя полный контроль над настройкой вотчинга. Чаще всего используется для регистрации отдельных обработчиков событий, которые предоставляют path или stats измененных файлов.

При непосредственном использовании экземпляра chokidar у вас не будет доступа к интеграции системы задач, включая асинхронное завершение, организацию очереди и задержку.

const { watch } = require('gulp');
const watcher = watch(['input/*.js']);
watcher.on('change', function(path, stats) {	console.log(`Файл ${path} был изменен`);
});
watcher.on('add', function(path, stats) {	console.log(`Файл ${path} был добавлен`);
});
watcher.on('unlink', function(path, stats) {	console.log(`Файл ${path} был удален`);
});
watcher.close();

watcher.on(eventName, eventHandler)

Регистрируются функции eventHandler, вызываемые при возникновении указанного события.

ПараметрТипПримечание
eventNamestringМожно наблюдать следующие события: 'add', 'addDir', 'change', 'unlink', 'unlinkDir', 'ready', 'error', или 'all'.
eventHandlerfunctionФункция, вызываемая при возникновении указанного события. Аргументы подробно изложены в таблице ниже.
АргументТипПримечание
pathstringПуть к файлу, который изменился. Если была установлена опция cwd, путь будет относительным путем удаления cwd.
statsobjectОбъект fs.Stat (агрумент может быть неопределен). Если для параметра alwaysStat установлено значение true, stats будет предоставлен всегда.

watcher.close()

Выключает средство просмотра файлов. После выключения больше событий не будет.

watcher.add(globs)

Добавляет дополнительные globs к уже запущенному экземпляру вотчера.

ПараметрТипПримечание
globsstring
array
Дополнительные globs для просмотра.

watcher.unwatch(globs)

Удаляет наблюдаемые globs, пока вотчер продолжает работу с оставшимися путями.

ПараметрТипПримечание
globsstring
array
Globs к удалению.

Экспорт

Задачи (таски) бывают общедоступными или частными.

  • Общедоступные задачи экспортируются из вашего gulpfile, что позволяет запускать их командой gulp.
  • Частные задачи создаются для внутреннего использования, как часть series() или parallel().

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

const { series } = require('gulp');
// Функция `clean` не экспортируется, поэтому ее можно считать частной задачей.
// Она все еще может быть использована в составе `series()`.
function clean(cb) {	// тело функции	cb();
}
// Функция `build` экспортирована, поэтому она общедоступна и может быть запущена командой `gulp`.
// Она также может быть использована в составе `series()`.
function build(cb) {	// тело функции	cb();
}
exports.build = build;
exports.default = series(clean, build);
~/gulp/docs
$ gulp --tasks
[11:14:59] Tasks for ~/gulp/docs/gulpfile.js
[11:14:59] ├── build
[11:14:59] └─┬ default
[11:14:59] └─┬ <series>
[11:14:59] ├── clean
[11:14:59] └── build

Раньше функция task() использовалась для регистрации ваших функций в качестве тасков. Хоть этот API все еще доступен, экспорт должен быть основным механизмом регистрации, за исключением крайних случаев, когда экспорт не будет работать.

Оцените статью
Расшифруй.Ру