Task runners like Gulp automate most all of the legwork that comes from customizing your webdev toolkit. Here, you will tie together many cool technologies that we have learned from other workshops. We can convert pug into HTML, SASS into CSS, CoffeeScript into JavasScript, and we still get hot reloading and linting.
Note: Gulp helps you pick whatever tools you want to use. For the purposes of this tutorial, we wanted to apply a lot of what we've learned in previous workshops. But gulp has thousands of plugins, of which Pug and CoffeScript support are just two.
Gulp only has four core methods: task, src, dest, and watch. The rest will be run through plugins. This makes gulp customizable, and its simplicity explains its gaining popularity among developers.
- task: Defines a new task. This method takes two arguments, the name of the task, and a callback function to run the actual task.
- src: Sets the folder where source files are located
- dest: Sets the destination folder where build files will be placed
- watch: Monitors source files and run an appropriate task(s) whenever a file is changed
Additionally, gulp makes heavy use of the pipe method. This takes the data from the previous command and uses it in the next action.
🍴 Fork this repo and clone the copy as usual! Open up terminal and go to your local copy.
💻 Run these two commands in your project directory to install gulp globally and then locally:
npm install -g gulp
npm install --save-dev gulp
Much like how node gets marching orders from package.json
, gulp reads all of its instructions from gulpfile.js
.
🚀 Open gulpfile.js
Let's make sure that we can use gulp. Under where it says //Include Gulp
, add:
🚀 const gulp = require('gulp');
Great! Now on to the plugins that we'll need.
First, let's download the dependencies. We'll need gulp-pug
and gulp-pug-linter
, which checks for errors in your Pug file.
💻 npm install --save-dev gulp-pug gulp-pug-linter
🚀 Under the comment // Include Our Plugins
, add the following:
const pug = require('gulp-pug');
const pugLinter = require('gulp-pug-linter');
🚀 Now we'll write our first task. Here's the outline, which you can add under the comment // Compile Pug with linter
:
gulp.task('compile-pug', () => {
// task goes here
})
We have ourselves a task! But it doesn't do anything. Inside that anonymous function, we want to
- find all the
.pug
files, - run them through the
pug
plugin that we installed - freak out if there's an error, and
- save our new
.html
files to the destination directory/public
.
Gulp makes this super easy.
🚀 Add this inside that anonymous function
gulp.src('./src/pug/*.pug')
.pipe(pug())
.on('error', onError)
.pipe(gulp.dest('./public'))
What is this onError
method referring to? Great question. Let's add it after the // onError method
comment.
function onError(err) {
console.log(err);
this.emit('end');
}
The onError function prevents the default gulp command from exiting if there are errors in the Pug file (which can be annoying if you would like gulp to run continuously while you're working).
What about that linter we installed? Let's make a task for that, too.
🚀 Below the 'compile-pug' task, add
gulp.task('lint-pug', () => {
return gulp
.src('./src/pug/*.pug')
.pipe(pugLinter())
.pipe(pugLinter.reporter())
})
We can reference any task by typing gulp <task-name>
in terminal. Let's compile our Pug!
💻 gulp compile-pug
Now you should be able to see the html file in your ./public folder 👍
To play with the linter, you can try typing in some invalid syntax into your Pug file and run gulp compile-pug
. The task will pause (but not terminate), and your linter will tell you where the error is in terminal. You can go ahead and correct the Pug file, and the task will continue automatically. (Cool!)
We'll use browserSync for easy local testing. Let's download the plugin:
💻 npm install --save-dev browser-sync
And add this to the top of gulpfile.js (where all of the dependencies live):
const browserSync = require('browser-sync');
Note: browser-sync is not a gulp plugin, so it doesn't have a gulp-
in front of its name. That said, it works really well with gulp.
🚀 Now let's make browserSync into a third gulp task. Find // browserSync task
and add
gulp.task('browserSync', () => {
browserSync.init({
server: {
baseDir: 'public',
}
});
});
We specify the base directory as public
because that's where we asked gulp to pipe the compiled files.
Try running
:computer: gulp browserSync
to see your website locally hosted (a browser window should pop up)!
Let's take it a step further. It's nice if we don't have to call browserSync every time we compile our pug file, right? Let's add browseSync into our compile-pug
task like so:
🚀 Append another pipe inside the compile-pug
method directly after .pipe(gulp.dest('./public'))
:
.pipe(browserSync.reload({
stream: true,
}));
Now you can testing it! In the Pug file, let's change h1 Hey there, CS52
to h1 Welcome to the Gulp workshop!
. After you save the change, call gulp compile-pug
again and call gulp browserSync
. The browser window will pop up, reflecting the change you just made!
What if we want our page to be interactive? We need some sort of JavaScript. We wrote a .coffee
file so need to compile it. gulp-coffescript
can achieve this for us easily! And gulp-uglify
minimizes the resulting .js
file nicely!
First download the plugins:
:computer: npm install --save-dev gulp-coffeescript gulp-uglify
🚀 And add them as dependencies
const coffeescript = require('gulp-coffeescript');
const uglify = require('gulp-uglify');
🚀 Under // Compile our coffeescript
add a new task:
gulp.task('coffee', () => {
gulp.src('./src/coffee/*.coffee')
.pipe(coffeescript())
.pipe(uglify())
.pipe(gulp.dest('./public'))
.pipe(browserSync.reload({
stream: true,
}));
});
What's going on?
- grab all the
.coffee
files from the/src/coffee
directory, - pipe the files through the
coffeescript
plugin, which compiles it to JavaScript, - pipe that JavaScript to
uglify
to minimize it, - save it to the
/public
directory, and - tell browserSync to reload the page (like we did with Pug)
Now in your terminal, run:
:computer: gulp coffee
Go have a look at the /public
directory and open the shiny, new .js
file. It's smushed into one line to make the file size smaller. Now when you run gulp browserSync
again, you can click on the button and get a random word!
Finally, we need style to prettify our website!
Let's download the plugin:
:computer: npm install --save-dev gulp-sass gulp-cssnano
🚀 Gotta add those dependencies
const sass = require('gulp-sass');
const cssnano = require('gulp-cssnano');
What are they used for? sass
compiles our .scss
files into css files, and cssnano
minimizes CSS files.
🚀 Time for another gulp task. Under // Compile sass
add:
gulp.task('sass', () => {
return gulp.src('./src/style/*.scss')
.pipe(sass()) // Using gulp-sass
.pipe(cssnano())
.on('error', onError)
.pipe(gulp.dest('./public'))
.pipe(browserSync.reload({
stream: true,
}));
});
Run it with
:computer: gulp sass
There are lots of similarities between all of these tasks (that's what developers love about gulp). However, let us know if anything doesn't make sense.
Now we have all the source files compiled and minimized. But we still have to call gulp sass
, gulp compile-pug
, and gulp coffee
to compile after we make changes. Lame! So let's tell gulp to listen for changes and call those tasks us.
🚀 Under "// watch" add a new task:
gulp.task('watch', ['browserSync'], () => {
gulp.watch('./src/pug/*.pug', ['lint-pug', 'compile-pug']);
gulp.watch('./src/style/*.scss', ['sass']);
gulp.watch('./src/coffee/*.coffee',['coffee']);
});
This method watches our files and invokes another gulp task if changes occurred. For instance, if you change a .pug
file in the folder /src/pug/
, we ask it to run the gulp tasks lint-pug
and compile-pug
.
See that we put the ['browserSync']
parameter in the method? It'll be cumbersome to open up two command line windows and run gulp browserSync
and gulp watch
separately, so here we make them run together by specifying that browserSync
must have completed before watch
is allowed to run.
Now you can type in the command line
:computer: gulp watch
,
and if you make a change to one of you source files, your website will be reloaded automatically.
👍 Awesome copying and pasting skills. We are ready to see our product! Finally, let's write the default task, which we want to compile all the files.
🚀 Under // default gulp
, add:
gulp.task('default', ['compile-pug','sass','coffee','watch'], function () {
// This will only run if the lint task is successful...
});
To call the default task, just run
💻 gulp
You ought to have a finished product that looks something like this:
and automatically updates if you change any .coffee
, .scss
, or .pug
file.
At this point you should have ...
- installed gulp (globally and in the repo)
- added external plugins
- compiled and minified all of the files in
src/
- hot reloading for local testing
- error checking
- an understanding of how cool gulp is!