In the last lesson, we plugged in the parallel()
function built into the Gulp package, which allowed us to combine several functions into one task:
const { parallel } = require('gulp');
const sassCompile = (done) => {
console.log('Compile SASS to CSS');
done();
};
const pugCompile = (done) => {
console.log('Compile Pug to HTML');
done();
};
const imagesOptimize = (done) => {
console.log('Optimize Images');
done();
};
exports.default = parallel(sassCompile, pugCompile, imagesOptimize);
We will cover several other functions inside the Gulp package in this lesson. There's no need to describe them separately, so let's look at the most common ones.
The series()
function
It is very similar in functionality to the parallel()
function we learned in the previous lessons. But if there are two different functions, they must have differences, right? They do! The difference lies in the way we call the functions within the task. There are two approaches here:
- The
parallel()
function executes functions simultaneously. In the example above, thesassCompile()
,pugCompile()
, andimagesOptimize()
functions won't queue up or wait for each other to finish but will start working together. This approach is convenient if the tasks are in no way related to each other and the result of one doesn't depend on the others:
- The
series()
function implements series execution. It means a function won't start until the other one finishes. It helps when we use multiple subtasks, directly affecting what function will be next:
Let us pay attention to the moment when tasks started in the screenshots. With parallel execution, Gulp shows all the tasks running simultaneously, and when using the series()
function, the execution of a new task starts only after the previous one finishes.
The src()
and dest()
functions
Many of the tasks in Gulp are related to file processing. Whether it's SASS, Pug, or another tool, we should process them into a format understandable for browsers. To do this, we specify which file to process and where to move it after processing. Two functions are responsible for this in Gulp:
src()
to specify the path to the file we are processingdest()
to specify the path where to put the file we have already processed
There are no particular surprises here. These functions are responsible for handling file paths and communication. They're much more flexible than they may seem at first, but the rest of the functionality, such as caching, is used very rarely and in only large projects. Let's look at a basic example of a file-copying operation:
const { src, dest } = require('gulp');
const copyFile = () => {
return src('src/sass/app.scss')
.pipe(dest('build/styles'));
};
exports.copy = copyFile;
After completing the task, the src/sass/app.scss file will be copied to the build/styles/ directory:
layout-project/
├── build/
│ ├── styles/
│ │ └── app.scss
├── src/
│ ├── sass/
│ │ └── app.scss
│ ├── pages/
│ │ ├── index.pug
│ │ ├── sections/
│ │ │ ├── head.pug
│ │ │ └── footer.pug
├── gulpfile.js
├── package.json
└── node_modules/
You don't need it there now because the build directory will get the processed files, but now you know how they'll get there :)
The done()
function isn't called here, unlike it was in the previous examples. It is because we used the keyword return
. You may have read more about the return
keyword in the Introduction to Programming course.
Globs
In the example above, we had a clear path to the file we wanted to copy. For small projects, this can be a simple and efficient solution. But it is often necessary to process not just one but all files from a particular directory or even a directory tree. For example, we can have the following style structure:
layout-project/
├── src/
│ ├── sass/
│ │ ├── global.scss
│ │ ├── mobile.scss
│ │ ├── desktop.scss
How can these files be processed correctly? There are two options:
- Process each file individually
- Process all files within one function
With the first option, it's simple: we create three functions, combine them into a single task and execute:
const { src, dest, parallel } = require('gulp');
const copyGlobalScss = () => {
return src('src/sass/global.scss')
.pipe(dest('build/styles'));
};
const copyMobileScss = () => {
return src('src/sass/mobile.scss')
.pipe(dest('build/styles'));
};
const copyDesktopScss = () => {
return src('src/sass/desktop.scss')
.pipe(dest('build/styles'));
};
exports.copy = parallel(copyGlobalScss, copyMobileScss, copyDesktopScss);
Programmers are okay with solutions with only three files, but it's not the best practice. After all, we perform the same operation on each file. There will be changes only in the processed file.
To specify multiple files, we use path templates called Globs. It is a small package that converts templates to paths and is built into Gulp by default. You only really need to know a couple of tricks that you can use to select almost any file and in any quantity.
The first construction is the asterisk *
, which indicates that you should select everything that doesn't conflict with the specified path. In the last example, we can replace the filename with an asterisk, and then Gulp will select all three files:
- global.scss
- mobile.scss
- desktop.scss
const { src, dest } = require('gulp');
const copyScss = () => {
return src('src/sass/*.scss')
.pipe(dest('build/styles'));
};
exports.copy = copyScss;
You can make copying files even better. Suppose the SASS files are in different places, and we want to merge them into one. You have to go through all the available directories, see if there is a file with the extension scss/sass, and copy it. It uses a **
construction designed to iterate through directories. For example, you can modify the code as follows:
const { src, dest } = require('gulp');
const copyScss = () => {
return src('src/**/*.scss')
.pipe(dest('build/styles'));
};
exports.copy = copyScss;
When the task starts, we will search for files in all directories within src with the extension .scss. We look through the src subdirectories and the src directory. For example, we select the following files:
- src/styles.scss
- src/project/app/styles/app.scss
- src/sass/mobile.scss and so on
Imagine we use the method that searches for files in different directories when transferring them using dest()
. In this case, Gulp keeps the nesting it had. For example, the src/project/app/styles/app.scss file will end up in the build/styles/project/app/styles/app.scss path when we run the last example. This feature helps to avoid mistakes when working on a project.
It works fine, but there's one significant problem – there may be directories inside our src that we don't need to process files from.
For example, let us observe the directories with npm packages. We can deal with this problem using Globs. It has a directory exclusion method specified with a logical negation sign !
. In this case, we specify the desired paths and exceptions as an array of strings. Here we exclude the src/project directory from copying:
const { src, dest } = require('gulp');
const copyScss = () => {
// ['src/**/*.scss', '!src/project/**'] — It is a string array
// which excludes the src/project directory and all directories nested within it
return src(['src/**/*.scss', '!src/project/**'])
.pipe(dest('build/styles'));
};
exports.copy = copyScss;
The characters /**
go to the end of the excluded directory. We exclude not only the directory itself but also every subdirectory in it.
The pipe()
function
Without going into the fine details, pipe()
allows you to link read and write streams to each other. If we go back to the file copying example, it's through pipe()
that we can get a file and give it to dest()
for further processing.
The same is true for file processing with plugins, as in the case of the compilation of SASS into CSS. We can bind the whole chain using the pipe()
function.
In the context of working with the Gulp package, you can build a typical chain, or template, of tasks:
const task = () => {
return src('the file we are working with)
.pipe(pluginOne()) // Processing the first plugin
.pipe(pluginTwo()) // Processing by the second plugin
.pipe(pluginN()) // Processing another plugin
.pipe(dest('the path where we put the processed file'));
};
Do it yourself
Add multiple directories and files to the tutorial project. Create tasks to copy and move them to different directories. Practice the Globs patterns you have learned.
Recommended materials
Are there any more questions? Ask them in the Discussion section.
The Hexlet support team or other students will answer you.
For full access to the course you need a professional subscription.
A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.