Seemingly out of the blue, the Gulp processing I had set up for this site started to have a race condition. I’d run my watch
command, change some CSS, and the processing would sometimes leave behind some extra files that were meant to be cleaned up during the processing. Like the cleanup tasks happened before the files landed in the file system (or something… I never really got to the bottom of it).
Nevermind about the specifics of that bug. I figured I’d go about solving it by upgrading things to use Gulp 4.x instead of 3.x, and running things in the built-in gulp.series
command, which I thought would help (it did).
Getting Gulp 4.x going was a heck of a journey for me, involving me giving up for a year, then reigniting the struggle and ultimately getting it fixed. My trouble was that Gulp 4 requires a CLI version of 2.x while Gulp 3, for whatever reason, used a 3.x version. Essentially I needed to downgrade versions, but after trying a billion things to do that, nothing seemed to work, like there was a ghost version of CLI 3.x on my machine.
I’m sure savvier command-line folks could have sussed this out faster than me, but it turns out running command -v gulp
will reveal the file path of where Gulp is installed which revealed /usr/local/share/npm/bin/gulp
for me, and deleting it manually from there before re-installing the lastest version worked in getting me back down to 2.x.
Now that I could use Gulp 4.x, I re-wrote my gulpfile.js into smaller functions, each fairly isolated in responsibility. Much of this is pretty unique to my setup on this site, so it’s not meant to be some boilerplate for generic usage. I’m just publishing because it certainly would have been helpful for me to reference as I was creating it.
Things my particular Gulpfile does
- Runs a web server (Browsersync) for style injection and auto-refreshing
- Runs a file watcher (native Gulp feature) for running the right tasks on the right files and doing the above things
- CSS processing
- Sass > Autoprefixer > Minify
- Break stylesheet cache from the templates (e.g.
<link href="style.css?BREAK_CACHE">
- Put style.css in the right place for a WordPress theme and clean up files only needed during processing
- JavaScript processing
- Babel > Concatenate > Minify
- Break browser cache for the
<script>
s - Clean up unused files created in processing
- SVG processing
- Make an SVG sprite (a block of
<symbol>
s - Name it as a sprite.php file (so it can be PHP-included in a template) and put it somewhere specific
- Make an SVG sprite (a block of
- PHP processing
- Update the Ajax call in the JavaScript to cache-bust when the ads change
Code dump!
const gulp = require("gulp"),
browserSync = require("browser-sync").create(),
sass = require("gulp-sass"),
postcss = require("gulp-postcss"),
autoprefixer = require("autoprefixer"),
cssnano = require("cssnano"),
del = require("del"),
babel = require("gulp-babel"),
minify = require("gulp-minify"),
concat = require("gulp-concat"),
rename = require("gulp-rename"),
replace = require("gulp-replace"),
svgSymbols = require("gulp-svg-symbols"),
svgmin = require("gulp-svgmin");
const paths = {
styles: {
src: ["./scss/*.scss", "./art-direction/*.scss"],
dest: "./css/"
},
scripts: {
src: ["./js/*.js", "./js/libs/*.js", "!./js/min/*.js"],
dest: "./js/min"
},
svg: {
src: "./icons/*.svg"
},
php: {
src: ["./*.php", "./ads/*.php", "./art-direction/*.php", "./parts/**/*.php"]
},
ads: {
src: "./ads/*.php"
}
};
/* STYLES */
function doStyles(done) {
return gulp.series(style, moveMainStyle, deleteOldMainStyle, done => {
cacheBust("./header.php", "./");
done();
})(done);
}
function style() {
return gulp
.src(paths.styles.src)
.pipe(sass())
.on("error", sass.logError)
.pipe(postcss([autoprefixer(), cssnano()]))
.pipe(gulp.dest(paths.styles.dest))
.pipe(browserSync.stream());
}
function moveMainStyle() {
return gulp.src("./css/style.css").pipe(gulp.dest("./"));
}
function deleteOldMainStyle() {
return del("./css/style.css");
}
/* END STYLES */
/* SCRIPTS */
function doScripts(done) {
return gulp.series(
preprocessJs,
concatJs,
minifyJs,
deleteArtifactJs,
reload,
done => {
cacheBust("./parts/footer-scripts.php", "./parts/");
done();
}
)(done);
}
function preprocessJs() {
return gulp
.src(paths.scripts.src)
.pipe(
babel({
presets: ["@babel/env"],
plugins: ["@babel/plugin-proposal-class-properties"]
})
)
.pipe(gulp.dest("./js/babel/"));
}
function concatJs() {
return gulp
.src([
"js/libs/jquery.lazy.js",
"js/libs/jquery.fitvids.js",
"js/libs/jquery.resizable.js",
"js/libs/prism.js",
"js/babel/highlighting-fixes.js",
"js/babel/global.js"
])
.pipe(concat("global-concat.js"))
.pipe(gulp.dest("./js/concat/"));
}
function minifyJs() {
return gulp
.src(["./js/babel/*.js", "./js/concat/*.js"])
.pipe(
minify({
ext: {
src: ".js",
min: ".min.js"
}
})
)
.pipe(gulp.dest(paths.scripts.dest));
}
function deleteArtifactJs() {
return del([
"./js/babel",
"./js/concat",
"./js/min/*.js",
"!./js/min/*.min.js"
]);
}
/* END SCRIPTS */
/* SVG */
function doSvg() {
return gulp
.src(paths.svg.src)
.pipe(svgmin())
.pipe(
svgSymbols({
templates: ["default-svg"],
svgAttrs: {
width: 0,
height: 0,
display: "none"
}
})
)
.pipe(rename("icons/sprite/icons.php"))
.pipe(gulp.dest("./"));
}
/* END SVG */
/* GENERIC THINGS */
function cacheBust(src, dest) {
var cbString = new Date().getTime();
return gulp
.src(src)
.pipe(
replace(/cache_bust=\d+/g, function() {
return "cache_bust=" + cbString;
})
)
.pipe(gulp.dest(dest));
}
function reload(done) {
browserSync.reload();
done();
}
function watch() {
browserSync.init({
proxy: "csstricks.local"
});
gulp.watch(paths.styles.src, doStyles);
gulp.watch(paths.scripts.src, doScripts);
gulp.watch(paths.svg.src, doSvg);
gulp.watch(paths.php.src, reload);
gulp.watch(paths.ads.src, done => {
cacheBust("./js/global.js", "./js/");
done();
});
}
gulp.task("default", watch);
Problems / Questions
- The worst part is that it doesn’t break cache very intelligently. When CSS changes, it breaks the cache on all stylesheets, not just the relevant ones.
- I’d probably just inline SVG icons with PHP
include()
s in the future rather than deal with spriting. - The SVG processor breaks if the original SVGs have
width
andheight
attributes, which seems wrong. - Would gulp-changed be a speed boost? As in, only looking at files that have changed instead of all files? Or is it not necessary anymore?
- Should I be restarting gulp on gulpfile.js changes?
- Sure would be nice if all the libs I used were ES6-compatible so I could
import
stuff rather than having to manually concatenate.
Always so much more that can be done. Ideally, I’d just open-source this whole site, I just haven’t gotten there yet.
Our gulp file is actually pretty similar, with the exception of cache busting. Since we are enqueueing the scripts and styles in php (wordpress) we simply add the file modification timestamp as a cachebuster. Works pretty well, the cache only busts when the file actually changes.
The wp enqueue functions takes $ver as a fourth parameter like so:
wp_enqueue_style( 'handle', './pathtofile/style.css', 'dep', filetime('./pathtofile/style.css') );
Ah, should clarify, second enqueue parameter is url to the file, and the filetime function is the filesystem path to the file.
I guess the assumption is that
filetime()
is trivial for the server to compute on each request? I’m not server-saavy enough to know.My man, I’m sorry to tell you that gulp-cli hasn’t hit 3.0 yet (it has hit 0.3.0 though).
But cli isn’t even required.
All it does is let you run your local (node modules or such) version of gulp by calling it globally (in the right project), allowing for different projects with different versions of gulp but only one global gulp.
filetime()
is really clever, Jakob. It seems to be very performant, and is probably negligible for a handful of files.For websites that don’t change in production too often, a more granular approach is appending the theme version:
wp_get_theme()->get('Version')
. This has the added bonus of forcing us to keep the number updated :)I come up with more universal, ultimate solution:
https://gist.github.com/marsjaninzmarsa/038ce27f1e76b90edd5773532e48520b
Something along those lines for your gulp cache-busting. It will calculate everything in the filter for every build but will only update the hash if the file changed.
// Install gulp-rev, gulp-rev-replace
var revisionFilter = gulp.filter([‘/*’, ‘!/index.html’], { restore: true });
gulp.src(…)
.pipe(revisionFilter)
.pipe(gulp.rev()) // calculate the hash of the files
.pipe(revisionFilter.restore)
I use ‘gulp-rev’ to do the versioning based on a content hash, which should keep it constant unless a file has actually changed. Because of the structure of our project (outputs a bunch of css files for different themes, which can be chosen in our CMS), and then we use it to generate a manifest to get all the filenames for our backend code. That said, I haven’t tried this in Gulp 4 yet. With Gulp 3 not working in the latest versions of Node, it’s a hurrdle we will soon need to conquer.
Just adding my 2 cents for
gulp-rev
– I’ve been using it for years with great success, together withgulp-rev-rewrite
andgulp-rev-delete-original
.Zach – I’ve had no issues with it with Gulp 4 :)
I have also made a Gulp 4 file that is public https://github.com/jenstornell/gulpfile-style-script. One nice thing about my repo is that you can install it just by copy/paste one line into the cmd.
I really like gulp-load-plugins. It basically let’s you skip most of the required stuff at the top.
let $ = require('gulp-load-plugins')();
Then anytime you need something, like autoprefixer
.pipe($.autoprefixer())
It just knows how to get it from your package.json
Ahan didn’t noticed earlier
Unfortunately, I had problems with gulp + wordpress + browser-sync.
Gulp often spilled out.
I rewrote everything to a webpack, it works great now.
For this works the whole environment on docker containers along with xdebug for wordpress.
Each change in js / css files creates separate hash files.
Which are loaded dynamically into php.
CSS is served as inline, there is no problem with the size because each subpage has its own css and js.
If anyone interested then I recommend https://github.com/tomik23/docker-wordpress-xdebug
Of course, the template itself is quite poor, this is a trimmed version and only as an example.
Wow my recent gulp work flow is very much similar to yours
Just php related tasks are missing in my work flow
For cache busting I used current date+time and used regex to replace it in html files
In order to avoid having to restart gulp based on file changes I implemented two things:
– Add
watchEvents: [ 'change', 'add', 'unlink', 'addDir', 'unlinkDir' ]
to the BrowserSync config.– gulp-include so I don’t have to define the source files for JS in the gulp file, concatenating and building different files based on their location in the
./src/
directory.If interested, here’s a gist with the full gulp setup.
I am in the process of switching over to stylelint from sass-lint though, since sass-lint is no longer maintained.
Mini Gulp-related linkdump…
November 2019 – Kieran Barker’s How to minify JavaScript and CSS files using gulp
December 2019 – Ethan Marcottes’s Setup
December 2019 – Jason Pamental’s Setup