Gulp workflow for Perch
What is Gulp?
Gulp is a tool for automating tasks in your development workflow such as using CSS preprocessors and optimising assets for production.
New to Gulp?
If you are new to Gulp, it would be helpful to learn the basics first. Zell Liew’s Gulp for Beginners article is a great introduction and it’s very easy to follow.
What we are doing:
- Inserting assets (CSS and JS) automatically to documents*
- Compiling Sass to CSS
- Autoprefixing CSS
- Reloading the browser when changes saved
- Minifying assets (CSS and JS)**
Notes:
* While we can use gulp-inject
plugin to insert the appropriate tags for our assets, we will handle this in a neater and more maintainable way with Pipit Pinion, a Perch Feather that helps you manage front-end assets such as CSS and Javascript through Perch.
We won’t do anything significant to our JS workflow other than minification for production. You can build upon this part of the workflow if you need to do more such as concatenation or using CoffeeScript.
** We’ll rely on Sass partials for concatenating our CSS instead of using a Gulp plugin. So if you want to write your Sass in multiple files and combines them later, you can take advantage of Sass imports.
Let’s say your main file is styles.scss
. You can create partials _footer.scss
and _blog.scss
and include them in your styles.scss
with:
body {
background-color: tomato;
}
/* more styles */
@import 'blog';
@import 'footer';
The above combines your Sass files before compiling and outputs a single CSS file styles.css
. If you are new to Sass, Robert Cooper has written a good introduction.
Inserting Assets:
We will be using Perch Layouts and the Pinion Feather to manage what (and when) assets are added to our documents.
Using Perch Layouts
Layouts are a common way of including repeated sections in Perch websites. For instance, we can use Layouts to include the <head>
on all pages with a simple perch_layout()
call and manage what goes in the <head>
from one file.
We will be using Layouts to insert CSS assets in the <head>
and Javascript assets just before the closing tag </body>
. You can change this easily if you require a different setup.
We will be using 2 layouts: global/top.php
and global/bottom.php
. These go into your perch/templates/layouts
folder.
global/top.php
:
<!doctype html>
<html>
<head>
<!-- We will insert assets here -->
</head>
<body>
global/bottom.php
:
<!-- we will insert assets here -->
</body>
</html>
In our pages page.php
we add the global/top.php
layout at the top of the document (after the Perch runtime include):
<?php perch_layout('global/top'); ?>
and add the global/bottom.php
layout at the bottom of the document:
<?php perch_layout('global/bottom'); ?>
Using Pipit Pinion
As mentioned above, we will use a Perch Feather called Pinion to manage our assets. You can download Pinion from here. Installation instructions are in the documentation.
Here’s the file structure we will be using for our assets:
website/
├ perch/
├ src/
├ scss/
├ js/
└ vendor/
├ tmp/
└ assets/
src
is where our development assets are. Uncompiled Sass, unprefixed CSS, unminified JS, etc. You may also have some assets that you only use for development in there. This is where we work.
tmp
is where our compiled assets will be generated during development. These are the assets we serve during development. They may not be fully optimised for production. If you’re using Git, you may choose to gitignore
this entire folder.
assets
is where our production-ready assets will be. They will also be generated later on.
So let’s configure Pinion to use this structure:
Open the config.php
file placed in perch/addons/feathers/pipit_pinion
. Here we can define what folder will have our production-ready assets, and what folder we want to use during development.
The PIPIT_PINION_ASSETS_DIR
is the folder we want to use by default for our assets in production. In this example we are using the assets
folder:
define('PIPIT_PINION_ASSETS_DIR', 'assets');
The PIPIT_PINION_ASSETS_DEV_DIR
is the folder we want to use by default for our assets in development. In this example we are using the tmp
folder:
define('PIPIT_PINION_ASSETS_DEV_DIR', 'tmp');
Pipit Pinion has 2 functions you can use: perch_get_css()
and perch_get_javascript()
. The functions have flexible options which you can learn more about in the documentation.
By default, perch_get_css()
and perch_get_javascript()
inserts all CSS files and Javascript files from your PIPIT_PINION_ASSETS_DIR/css
and PIPIT_PINION_ASSETS_DIR/js
respectively into your document.
When you set PERCH_PRODUCTION_MODE
to PERCH_DEVELOPMENT
in your perch/config/config.php
, the functions inserts all CSS files and Javascript files from your PIPIT_PINION_ASSETS_DEV_DIR/css
and PIPIT_PINION_ASSETS_DEV_DIR/js
into your document.
So in our case, we get the files in assets/css
and assets/js
by default. And when PERCH_PRODUCTION_MODE
is set to PERCH_DEVELOPMENT
, we get the files in tmp/css
and tmp/js
.
These are the defaults, and Pinion is flexible enough to allow us specify other folders or specific files if needed.
So let’s update our layouts:
global/top.php
:
<!doctype html>
<html lang="en">
<head>
<?php
perch_get_css();
// If your 'src/vendor' includes some CSS files
perch_get_css(['dir' => 'src/vendor']);
?>
</head>
<body>
global/bottom.php
:
<?php
// If your 'src/vendor' includes some JS files
perch_get_javascript(['dir' => 'src/vendor']);
perch_get_javascript();
?>
</body>
</html>
Gulp
Create a project by running the npm init
command in your website root. Install Gulp into the project:
$ npm install gulp --save-dev
File Structure:
Here’s the file structure we want:
website/
├ perch/
├ src/
├ scss/
├ js/
└ vendor/
├ tmp/
├ assets/
├ gulp/
├ gulpfile.js
├ package.json
└ node_modules/
gulp
is where we’ll place our gulp plugins. gulpfile.js
stores our Gulp configuration.
node_modules
is, surprise surprise, where the node modules are. This is automatically created when you install plugins.
package.json
is an automatically created file that contains details about the project such as dependencies.
gulpfile.js
Before we start creating our tasks, install the following plugins:
gulp-load-plugins
- allows us to separate our tasks in multiple files.run-sequence
- used to run a sequence of tasksdel
- used to delete filesbrowser-sync
- used to sync browser scroll in multiple windows (and across devices) as well as reloading the page when we update certain files
Create your gulpfile.js
and add the following
var gulp = require('gulp');
var plugins = require('gulp-load-plugins')({pattern: '*'});
var runSequence = require('run-sequence');
var del = plugins.del;
var browserSync = require('browser-sync').create();
function getTask(task) {
return require('./gulp/' + task)(gulp, plugins);
}
The getTask()
function gets the tasks we write in other files.
Browser Sync:
We are using Browser Sync which not only keeps multiple browsers in sync, but also any pages open on different devices. So when you are developing on your computer you can, for instance, have phones and tablets mounted next to your screen and Browser Sync will sync your scrolls and page reloads on all of them.
So if you have separate config
files for different environments (development and production) like this:
switch($_SERVER['SERVER_NAME']) {
case 'localhost':
include(__DIR__.'/config.localhost.php');
break;
default:
include('config.production.php');
break;
}
You may need to add another case
statement with your local IP address so you’re using the correct config
file.
switch($_SERVER['SERVER_NAME']) {
case 'localhost':
include(__DIR__.'/config.localhost.php');
break;
case '192.168.10.136':
include(__DIR__.'/config.localhost.php');
break;
default:
include('config.production.php');
break;
}
Instead of hardcoding the IP address you can also use getHostByName(getHostName())
. More on this in the Multiple Server Config post.
Browser Sync requires you to add a script before the </body>
closing tag. It tells you so when you run the task:
[Browsersync] Copy the following snippet into your website, just before the closing </body> tag
<script id="__bs_script__">//<![CDATA[
document.write("<script async src='http://HOST:3000/browser-sync/browser-sync-client.js?v=2.18.13'><\/script>".replace("HOST", location.hostname));
//]]></script>
To only include in development mode:
<?php if(PERCH_PRODUCTION_MODE === 'PERCH_DEVELOPMENT'): ?>
<script id="__bs_script__">//<![CDATA[
document.write("<script async src='http://HOST:3000/browser-sync/browser-sync-client.js?v=2.18.13'><\/script>".replace("HOST", location.hostname));
//]]></script>
<?php endif; ?>
Learn more about Perch Production Modes.
Now we can start writing our Gulp tasks.
Sass and CSS autoprefixing
gulp/sass.js
handles Sass compilation and CSS autoprefixing. Required installs:
gulp-sass
gulp-autoprefixer
module.exports = function (gulp, plugins, config) {
return function () {
var destination = 'tmp/css';
var stream = gulp.src('src/scss/**/*.scss')
.pipe(plugins.sass())
.pipe(plugins.autoprefixer({
browsers: ['last 6 versions'],
cascade: false
}))
.pipe(gulp.dest(destination));
return stream;
};
};
Javascript
This workflow doesn’t use any Javascript preprocessing. In this example, we’ll just copy our files to tmp/js
as is, but we will only do so if the files has changed.
We’ll handle this in gulp/scripts.js
. Required installs:
gulp-newer
- to check whether the files insrc/js
are newer than those intmp/js
module.exports = function (gulp, plugins, config) {
return function () {
var destination = 'tmp/js';
var stream = gulp.src('src/js/**/*.js')
.pipe(plugins.newer(destination))
.pipe(gulp.dest(destination));
return stream;
};
};
Back to gulpfile.js
Now we can go back to our gulpfile.js
and set up our development build. Add the following to your file:
gulp.task('browserSync', function() {
browserSync.init()
})
// deletes the contents of the 'tmp' folder
gulp.task('clean:tmp', function() {
return del('tmp');
})
// get tasks from the 'gulp' folder
gulp.task('sass', getTask('sass'));
gulp.task('scripts', getTask('scripts'));
// use Gulp's watch to check whether certain files are saved
gulp.task('watch', function (){
//if .scss file or .js file is saved, run the sass and script tasks respectively
gulp.watch('src/scss/**/*.scss', ['sass']);
gulp.watch('src/js/**/*.js', ['scripts']);
// reload browser if perch template, master page or layout is saved
gulp.watch('perch/templates/**/*.html', browserSync.reload);
gulp.watch('perch/templates/**/*.php', browserSync.reload);
// reload browser if any asset compiled and/or saved
gulp.watch('tmp/**/*.*', browserSync.reload);
})
// This sequence of tasks is our development build
gulp.task('default', function (callback) {
runSequence('clean:tmp',
['browserSync', 'sass', 'scripts'], 'watch',
callback)
})
Now to run our dev build, we run the default
task. The default task can be run from the command line with:
$ gulp
If we change the name of the task to something else gulp.task('dev', ...)
, we run it with the command:
$ gulp dev
Remember to define your production mode in your perch perch/config/config.php
(or config.environment.php
) so Pipit Pinion gets the correct files.
define('PERCH_PRODUCTION_MODE', 'PERCH_DEVELOPMENT');
Minifying CSS
We’ll create gulp/minify-css.js
to minify our CSS files. Required installs:
gulp-cssnano
- minifies CSS files
module.exports = function (gulp, plugins, config) {
return function () {
var destination = 'assets/css';
var stream = gulp.src('tmp/css/**/*.css')
.pipe(plugins.cssnano())
.pipe(gulp.dest(destination));
return stream;
};
};
Minifying Javascript
Similarly we’ll create gulp/minify-js.js
to minify our Javascript files. Required installs:
gulp-uglify
- minifies Javascript files
module.exports = function (gulp, plugins, config) {
return function () {
var destination = 'assets/js';
var stream = gulp.src('tmp/js/**/*.js')
.pipe(plugins.uglify())
.pipe(gulp.dest(destination));
return stream;
};
};
Production Build
Now we can modify our gulpfile.js
to include a production build task:
gulp.task('minify-css', getTask('minify-css'));
gulp.task('minify-js', getTask('minify-js'));
gulp.task('clean:dist', function() {
return del('assets');
})
gulp.task('build', function (callback) {
runSequence('clean:dist',
['sass', 'scripts'],
['minify-css', 'minify-js'],
callback)
});
And to run the build
task:
$ gulp build
Final words
There’s no one correct workflow and Gulp isn’t the only task runner option. The above is a generic and modular Gulp workflow for Perch which can be built upon.
And with Pipit Pinion’s flexibility you can manage what assets are added to your documents in an easy and maintainable way regardless of what task runner you use and regardless of whether you use a task runner at all.
All the Gulp files are available on GitHub.