If you are an absolute beginner when it comes to Gulp, you might want to read Introducing WPGulp: An Easy to Use WordPress Gulp Boilerplate. Moreover, this post is just an article, which hasn’t been updated in a long time. I maintain this workflow under WPGulp repository. I suggest you all to read this post to get an idea and use WPGulp instead.
If you are not using Gulp or any task runner, believe me when I say that you are missing out on all the front-end fun stuff. Today, I intend to share my advanced Gulp based automated workflow for building WordPress themes, with the community.
I remember the time when I had to minify CSS and JS files, compress the images and the fuss of testing my designs across different browsers let alone devices โ oh my. If my recollection of lost isn’t misleading me then I used to spend like 30% of my productive hours on this stuff.
JUST A NOTE!
๐จโ๐ป I’m teaching thousands of devs how to become VSCode Power Users โ
โ
This site is super fast?! It’s hosted with Kinsta on Google servers โ
Thankfully, someone wrote a piece about DRY (Don’t Repeat Yourself) and it got me thinking about the flaws in my workflow. I believe a tweet from Chris Coyer led me to start using Grunt in 2012 or a little later. I used it for over a year and boy it was a fun ride.
Why Gulp?#
While Grunt took care of optimizing images, compressing scripts, and compiling Sass, I never really enjoyed writing Grunt files. Then in Dec 2013, I heard about this new task runner called Gulp. On a Sunday morning, I thought to create a dummy project and try Gulp, to say that I was hooked would be an understatement.
Why Do I Like Gulp?#
At that time, there was hardly any documentation available for using Gulp with WordPress, but thanks to the awesome helping community at Stack Exchange, I was able to make it work. I found Gulp to be a lot better than Grunt. Here is why:
- Gulp is easier to understand and more efficient
- Gulp has a JavaScript syntax unlike JSON syntax of Grunt
- Gulp is faster than Grunt (One of my theme’s compile time went from 12secs to 300ms)
- Gulp is intuitive and gets 20K-25k downloads a day, which is equal to or great than Grunt downloads
- Gulpโs code-over-configuration makes it not only easy to write tasks for, but also much easier to read and maintain
That said, let’s leave the story for now and see what it is like to be using Gulp with WordPress.
Do I Need to Use Gulp?#
Are you a web developer? Do you program in HTML/CSS sometimes?
โ Yes!
Did you ever use a task-runner? Feel like you belong to the stone age while all the crazy kids in town are using awesome advanced workflows?
โ Yes! :(.
OK! let’s get you started then.
Since this article is about an advanced Gulp based workflow, explaining how to do basic Gulp related stuff is out of the scope. But instead of leaving you (beginners) in the middle of nowhere, I’d like to suggest a few articles to help you get started.
- Basic: Learn what is Gulp and How to use it (https://www.digitalocean.com/community/tutorials/automate-your-tasks-easily-with-gulp-js)
- Detailed: Building with Gulp (https://www.smashingmagazine.com/2014/06/building-with-gulp/)
If having a basic Gulp workflow is nothing new for you, then maybe you’ll enjoy reading my advanced gulp workflow.
Advanced Gulp WordPress Workflow#
While building premium themes for WordPress, I end up with a lot of grunt work which includes
- Minifying & optimizing CSS with stylesheets and Sass
- Custom JavaScripts and 3rd Party Scripts via Bower
- Synced cross-browser design testing on different devices
- Tunneling my localhost dev branch to a random public URL for public access
- Building an installable zip theme file without any node modules, cache files or .Dot files data in it
Let’s explore how I get all this stuff done with Gulp.
I have set up a Github repository called Advanced Gulp WordPress. You can check out the structure of files inside it for better understanding.
A Look at gulpfile.js#
Now let’s take a look at gulpfile.js which is present in the root folder of our theme. I will explain each task one by one to make it easier for you to understand. You’ll find completed gulpfile.js at the end of this post.
Since I assume you already know how Gulp works, I will not be explaining how to put all the plugins/packages in package.json file and other basic setup related steps.
Step #1: Setting Up the Variables#
Firs of all I am going to set up certain variables so that I don’t have to change everything for every other theme I create. Let’s review the code
I’ve set up variables for
- L#12 Project name
- L#13 Project URL at localhost
- L#14 Path to folder of Bower Components
- L#15 Name of the folder which will be created for building an installable theme zip file
- L#16 Variable
buildInclude
contains the paths to files or folders which are to be included and excluded in while building a clean theme inside buildTheme
folder for creating a zip file
Step #2: Loading Gulp Plugins#
I am using quite a few gulp plugins to help manage everything we discussed above.
The plugins I am using to are as follows
- Gulp โ Pretty much self-explanatory
- BrowserSyn โ Amazing Time-saving synchronized browser testing
- AutoPrefixer โ Auto prefix CSS for legacy browsers
- UglifyCSS โ For CSS Minification
- Filter โ Enables you to work on a subset of the original files by filtering them using globbing.
- UglifyJS โ Minifies JS files
- ImageMin โ Minifies PNG, JPEG, GIF and SVG images
- Newer โ For passing through only those source files that are newer than corresponding destination files.
- Rename โ To easily rename files
- Concat โ To concatenate JS files
- Notify โ To send notification to OS based on node notifier module
- Combine Media Queries โ To combine repetitive media queries after Sass or Less
- Run Sequence โ Run a series of dependent gulp tasks in order
- Gulp Sass โ Gulp plugin for Sass which is based on libSass
- Load Plugins โ To automatically load in gulp plugins
- Ignore โ To ignore files in the stream based on file characteristics
- Zip โ To zip or compress files
- Plumber โ Fix node pipes, prevent them from breaking due to an error
- Cache โ A cache proxy task for Gulp
- Source Maps โ Source map support for CSS partial files
Phew! That was a lot. Well, at the end of the day, it’s all worth the effort.
Step #3: Task to Setup Browser Sync#
Let’s dive a little deeper and create some awesome tasks to automate our workflow. Browsersync is one of my favorite plugins.
I used to have LiveReload in my workflow, which would reload the web page whenever a file gets edited and saved. But when Browsersync was introduced, it helped me cut down half of the shitty things I had to do in order to automate syncing between different browsers. Browsersync helps me with the following stuff;
- Injecting CSS changes without any web page getting reloaded
- Change the port and set up a tunnel through a random public URL, which means my teammates can access the dev branch live at my localhost in a matter of minutes
- Synced testing across different browsers, where I open the given URL at my Desktop, Laptop, Tab, and mobile to test same features in a single go with synced scroll, clicks, form inputs, etc. Watch the video at Browsersync.
Step #4: Gulp Style Task#
Let’s take a look at the styles
task I built. In this task, there is a lot going on. We are compiling Sass, creating minified CSS and optimizing media queries.
Let me explain line by line, what’s going on in there
- L#1 – L#8 โ I created the
styles
task
- L#9 โ Here I provided the source of Sass file, which is a style.scss file present at
./assets/css/style.scss
- L#10 โ Initiated plumber to avoid pipe breaking due to minor CSS errors when I save a file
- L#11 โ Sourcemaps got initiated. A source map provides a way of mapping code within a compressed file back to its original position in a source file. This means that โ with the help of a bit of software โ you can easily debug your applications even after your assets have been optimized. The Chrome and Firefox developer tools both ship with built-in support for source maps.
- L#12 – L#20 โ Sass is being compiled in compact format, you can read about Sass formats here
- L#23 โ I am prefixing CSS with autoprefixer. The legacy browsers which should be supported are mentioned inside the arguments array
- L#26 โ Here I have saved the final compiled CSS file for our WordPress theme in the root directory. Since style.scss was being compiled, the output will be
style.css
file. Now all you need to do is make sure you use a “!” in the comments at the top of the style.scss file so that, even when the compiling/compressing happens, the comment remains: i.e. /*! Theme Name: Your Theme etc... */
- L#27 โ I filtered all the .css files in the root directory, which at the moment is one
style.css
file
- L#28 โ Combined the media queries inside selected files
- L#30 โ Renamed the file to style.min.css
- L#31 – L#33 โ Minified the style.min.css file
- L#34 โ Output was saved as a style.min.css file in the root of WP theme folder
Step #5: Scripts Minification and Concatenation#
Now to deal with custom JavaScript files and 3rd Party JS files, I have created two tasks called vendorsJs
and scriptsJs
. They both are pretty much similar. Let’s review the code.
Here is what’s happening in there
- I selected the vendor JS files paths with bower path at L#7 and the custom JS files paths at L#27
- Concatenated the files to two single files called vendors.js and custom.js
- Saved the output in
./assets/js/
folder
- Renamed the files to with .min suffix
- Minified/uglified the files
- And saved two more files called
vendors.min.js
and custom.js
Step #6: Image Optimization Task#
There nothing new in the image optimization task. Look at the code below.
- I selected the
./assets/img/raw/**/*.{png,jpg,gif}
path as a source
- Optimized the images and placed them in
./assets/img/
folder
Step #7: Building a Clean Installable Theme Zip File#
This is a group of tasks which are responsible for creating a clean copy of installable theme zip file. Take a quick look at the code. There is nothing much to it, it is all basic copy pasting and deleting or ignoring of files/folders.
This task will run only when I will use gulp build
command. Let’s review what happens when this command is run
- clear: Task to clear the gulp cache
- cleanup: Task to remove sass-cache, bower_components and .DS_Store files in the stream. It also ignores the node_modules folder
- cleanupFinal: Another cleanup task to be run at the end of build sequence. It may or may not have any difference from
cleanup
task
- buildFiles: Build task that moves essential theme files to the buildTheme folder
- buildImages: Task to build and copy the final set of images to the buildTheme folder
- buildZip: Task to create an installable Zip file of the buildTheme folder
- build: Main task which runs styles task to compile CSS then cleans up everything with a cleanup task. After that, it runs all the scripts related and build related tasks and a final cleanup task at the end to produce a theme zip file via buildTheme folder.
Step #8: Watch Task#
Finally, there is a watch task which helps in automatically running the styles, scripts, images and browser-sync related tasks in case of any change that occurs in the below-mentioned folder.
Final gulpfile.js#
What About You?#
Well, that’s about it. I’d love to see how you people use Gulp to automate front-end related grunt work. If you have any questions or any suggestions, let me know in the comment section below.
I’d also like to mention and thank Chris Coyer, Alex Vasquez, Matt Banks and all the Gulp contributors, who have helped me along the way with their GPL/MIT based code and contributions. Pull requests are welcomed.
Did you like what you read? Tweet About Advanced Gulp Workflow for WordPress.
JUST A NOTE!
๐จโ๐ป I’m teaching thousands of devs how to become VSCode Power Users โ
โ
This site is super fast?! It’s hosted with Kinsta on Google servers โ
Hey Ahmad, thank you so much for sharing this. I just got into using Grunt few weeks back and decided to use Grunt as I saw in WP community most people were using Grunt. I have few questions and I’d appreciate your time.
1. Do you use virtual server such as vagrant, etc. or something like XAMPP?
2. Will the above setup (browser sync) work when we use XAMPP?
Hey, Harish! I get that, WP core is still using Grunt. Just like WordPress Repo still uses SVN but you do use Git don’t you? Don’t let the core decide if you need to use the latest and better task runner or not. I use Grunt for my core contribution workflow.
1. Yes, first thing you should know that I am a Mac user, and everything I wrote above was tested in a Mac OS. I use both Vagrants and DesktopServer (which uses XAMP I think).
2. Yes, of course. There should be no issue at all. Try it out and let us know.
Hey Ahmad,
This is a superb and detailed write-up; I see some good things here I can take and use myself. Well done! =)
Hey, Alex!
You are the source of all the inspiration here. I have mixed your workflow with what I used to have back in the day. I do plan to push an update with split files based Gulp architecture and a few cool plugins too, which I found while writing this article.
Thank you for this post, very informative and takes a lot of the fear away ;)
I have only used Grunt a tad, and was still on the fence about trying to look into Gulp, but I am excited to give this a try in my workflow.
Thank you much!
Hey, Ryan!
I am sure you’ll enjoy working with Gulp. Thanks for stopping by.
I was aware of the “power” Gulp had, but didn’t have the courage to learn about it, just because I thought it was a rough road ahead. I develop WP sites on a daily bases, and learned so much from this article & your Git repo. Not only about Gulp. The structure of your WP base project is something that I’ll try and integrate in mine too. Big thank you for the extremely well explained tutorial!
It’s awesome to be helpful. About the structure, I intend to write a new post, since it is a bit unconventional. Keep up the good work.
I haven’t read the whole article but I already love you!
This looks very detailed, awesome job.
Thanks, Jorge!
If you’d like to read the follow up next month (which I am still working on), then sign up at Takeaway: My WordPress Newsletter
Very good and detailed guide for gulp. Well done! :)
nice article! it’s time to start using this stuff :)
Whaatttt? Tunnelling a URL to show others???? This is the remote freelancer’s dream!!!!
Damn right it is!
Thanks for this, there’s a lot of good stuff here and I’m going to go through it thoroughly to get started with Gulp.
One thing I noticed in your repo was that the ‘wp-content’ folder name is hard-coded. This can be changed in wp-config.php using the WP_CONTENT_DIR constant and may not always be ‘wp-content’.
Jason, thank you for stopping by. You are right about the hard coded links. Actually, since I posted this article I have updated my workflow a lot. I’ll write about it. But here are the humble open source beginnings of how to keep the configuration and modules separate, a WordPress Gulp Boilerplate.
I have been reading tutorials on Gulp and Grunt for a couple weeks now. This is the best and most detailed by far! Nice job!
Thanks, Jxn! I am going to write more of such these, my workflow has updated a lot since I wrote this.
This was so easy to follow. Even as someone just getting into workflow automation. Thanks for taking the time to break everything down and sharing your process!
Glad you liked it :)
You have my thanks! I didnt know much on how to work with gulp but with your article and some research on my own i was able to kickstart my knowledge big times.
That’s great. I am actually going to post a few more of these. Don’t forget to Subscribe to my newsletter called Takeaway WordPress Newsletter.
This is nice. Got it running on a WP install inside a Vagrant box.
Replaced gulp-combine-media-queries with gulp-group-css-media-queries because the former threw an error.
Hey guy, amazing article. I love to work with Gulp and Sass.
I’ve got a question: Where did you create this “mindmap” file structure (file structure graph)?
I forgot the name of that tool. Let me think over it.
Did you already remember it?
Nops! Unable to find it.
Okay. Did you use an online tool or something like Microsoft Visio?
I am pretty sure it was a terminal tool, probably a ruby GEM.
This is a nice approach to working with Gulp. I think that being able to use Browsersync alone makes it worth learning Gulp. I’ve been using a simpler Gulp workflow for a few months and was hoping to learn how to manage dependencies with Bower. But here are a couple of things I use to speed my workflow along and hope that it will help others as well.
I develop locally and use AMPPS (free) to help make my life easier. First, you can create domain names for your local installs instead of using localhost. Also, AMPPS comes with Softaculous that allows you to install WordPress very quickly using the auto-installer.
I found that it isn’t necessary to run โnpm installโ every time I want to use my workflow for a new theme. I simply copy the node_modules directory from another theme – change the Browsersync setting and run gulp. The problem with this is that my node_modules directory is about 1.3 Gb and that began filling up my hard drive very quickly. So, I moved the node_modules directory to the root folder (in AMPPS) and made an alias of it and placed the alias in my theme folder and it works seamlessly for each theme.
Thanks for the fantastic article.
Glad you found it useful. 1.3GB of node_modules is a pretty strange thing. I have never seen them taking more than 150MB space. You might wanna check if you have had been clearing the cache or not.
Very good article Ahmad. Thanks for tis effort. This browser-synch configuration doesn’t work on my XAMPP. My wordpress testsuite runs under: localhost/wordpress2.
Thanks. Did you configure the project URL with
localhost/wordpress2
?Now this is something. good work. :)
Epic work! Thanks for sharing, Ahmad
Glad I could help. An update is due!
Hi Ahmad,
What do you use to create page templates and get data from the DB?
I use TIMBER fo get all .twig templates running. Do you recomend it with this workflow, or do you have any other alternative?
Thanks. Great work!
I have used Timber with this workflow, works pretty great. Guess we are in the same boat for our love of Twig templating engine.
I read you’ve changed this workflow and I like to know what do you consider the most important thing you’ve changed?
by the way, nice work :)
Thanks!
Hey, Rutz!
Thanks! Glad you liked it. I have shifted this workflow to WPGulp, you can try it out.
When i try to run “gulp” i receiving the following error, could you please give me a hint what wrong.
[09:52:15] Using gulpfile /Volumes/Document HD/Developments/MAMP-htdocs/sandbox/yopress/wp-content/themes/Advanced-Gulp-WordPress/gulpfile.js
[09:52:15] Starting ‘styles’…
[09:52:15] Finished ‘styles’ after 17 ms
[09:52:15] Starting ‘vendorsJs’…
[09:52:15] Starting ‘scriptsJs’…
[09:52:15] Starting ‘images’…
[09:52:15] Starting ‘browser-sync’…
[09:52:15] Finished ‘browser-sync’ after 42 ms
[09:52:15] gulp-imagemin: Minified 0 images (saved 0 B – 0%)
[09:52:15] Finished ‘images’ after 125 ms
[09:52:15] gulp-notify: [Gulp notification] Custom scripts task complete
[09:52:15] Finished ‘scriptsJs’ after 386 ms
buffer.js:167
throw new TypeError(‘must start with number, buffer, array or string’);
^
TypeError: must start with number, buffer, array or string
at fromObject (buffer.js:167:9)
at new Buffer (buffer.js:58:10)
at Transform.transform [as _transform] (/Volumes/Document HD/Developments/MAMP-htdocs/sandbox/yopress/wp-content/themes/Advanced-Gulp-WordPress/node_modules/gulp-combine-media-queries/index.js:145:21)
at Transform._read (/Volumes/Document HD/Developments/MAMP-htdocs/sandbox/yopress/wp-content/themes/Advanced-Gulp-WordPress/node_modules/gulp-combine-media-queries/node_modules/readable-stream/lib/_stream_transform.js:184:10)
at Transform._write (/Volumes/Document HD/Developments/MAMP-htdocs/sandbox/yopress/wp-content/themes/Advanced-Gulp-WordPress/node_modules/gulp-combine-media-queries/node_modules/readable-stream/lib/_stream_transform.js:172:12)
at doWrite (/Volumes/Document HD/Developments/MAMP-htdocs/sandbox/yopress/wp-content/themes/Advanced-Gulp-WordPress/node_modules/gulp-combine-media-queries/node_modules/readable-stream/lib/_stream_writable.js:237:10)
at writeOrBuffer (/Volumes/Document HD/Developments/MAMP-htdocs/sandbox/yopress/wp-content/themes/Advanced-Gulp-WordPress/node_modules/gulp-combine-media-queries/node_modules/readable-stream/lib/_stream_writable.js:227:5)
at Transform.Writable.write (/Volumes/Document HD/Developments/MAMP-htdocs/sandbox/yopress/wp-content/themes/Advanced-Gulp-WordPress/node_modules/gulp-combine-media-queries/node_modules/readable-stream/lib/_stream_writable.js:194:11)
at write (/Volumes/Document HD/Developments/MAMP-htdocs/sandbox/yopress/wp-content/themes/Advanced-Gulp-WordPress/node_modules/through2/node_modules/readable-stream/lib/_stream_readable.js:623:24)
at flow (/Volumes/Document HD/Developments/MAMP-htdocs/sandbox/yopress/wp-content/themes/Advanced-Gulp-WordPress/node_modules/through2/node_modules/readable-stream/lib/_stream_readable.js:632:7)
at DestroyableTransform.pipeOnReadable (/Volumes/Document HD/Developments/MAMP-htdocs/sandbox/yopress/wp-content/themes/Advanced-Gulp-WordPress/node_modules/through2/node_modules/readable-stream/lib/_stream_readable.js:664:5)
at emitNone (events.js:67:13)
at DestroyableTransform.emit (events.js:166:7)
at emitReadable_ (/Volumes/Document HD/Developments/MAMP-htdocs/sandbox/yopress/wp-content/themes/Advanced-Gulp-WordPress/node_modules/through2/node_modules/readable-stream/lib/_stream_readable.js:448:10)
at emitReadable (/Volumes/Document HD/Developments/MAMP-htdocs/sandbox/yopress/wp-content/themes/Advanced-Gulp-WordPress/node_modules/through2/node_modules/readable-stream/lib/_stream_readable.js:444:5)
at readableAddChunk (/Volumes/Document HD/Developments/MAMP-htdocs/sandbox/yopress/wp-content/themes/Advanced-Gulp-WordPress/node_modules/through2/node_modules/readable-stream/lib/_stream_readable.js:187:9)
Try WPGulp https://github.com/ahmadawais/WPGulp it’s a maintained version of this workflow and much more portable.
Nice post!
But what about versioning of css and js files, have you tried getting that into your workflow? How or why not?
Thanks! Yes, I use Git for keeping up with the versioning of CSS and JS files. Don’t you?
Great write up!
Unrelated Question:
What did you use to create the folder/file structure image ? It’s fantastic!
Thanks!
Completely forgot what I used to do that. Will come around remembering it, some day!
So now, what is the best way to import Bootstrap to this workflow?
You just add CSS and JS files you need in their respective folders. Not sure, I understand what’s the problem here.
Newbie here. What is the theme based on? Bootstrap? Foundation? Your own? Thanks.
There’s no theme here. This is just a boilerplate. Also, I recommend you use to try WPGulp โ which is the maintained portable version of this workflow. If ask me personally, I hate frameworks and always build my own depending upon the projects.
Hey Ahmad, Thank you very much for writing this article. I’ve configured my WP themes with Gulp. Kudos to you!
Glad to know that! Keep up the good work!
Hi Ahmad,
With WPGulp it is possible to download package.json and that’s fantastic!
Do you have the package.json file for this boilerplate? In that way I won’t need to install each module one by one. Thank you!
Thanks, Jeff!
WPGulp is the advanced and improved form of this workflow. I have since abandoned this one and moved on to WPGulp. Though, you can find the package.json file for this one here https://github.com/ahmadawais/Advanced-Gulp-WordPress
I’d recommend you to use WPGulp instead.
Hey Ahmad,
great tutorial, thank you! What I don’t understand is, why you are separating the vendors and the custom JS files. What’s the purpose of that?
Best regards,
Dave
Hey, Dave!
Vendor files should always be enqueued before the custom JS files. That’s why!
Obvioulsy, custom JS files can have a dependancy and that kind of order can’t be handled if you don’t have a vendor file enqueued before the customJS files.
Hey Ahmad, thanks for the awesome build tool! Its working great except I can not get the comment to work for the child theme. I’ve added the ‘ /*! Theme Name: Child Theme …’ and it compiles to 1 line in my style.css any thoughts on what to do next?
Hi Ahmad,
Thanks for this info. I am wondering why does the link changed from http://localhost:3000/mysite to //localhost:3000/mysite the http: is being removed. Any suggestion on this?
Thanks
Mark, that’s just how browsers work.
Hi Ahmad, I have been following you from a long time and able to create my own gulp workflow for html based websites. Right now trying to implement gulp wworkflow for WordPress but little bit different with your version of gulp workflow but definitely it will help me to generate my own gulp workflow. In your version of gulp workflow you have used some plugin that right now I am not able to fully understand and bower as per their website had been deprecated. How we can combine different css in a single file before loading into WordPress theme(s) the same i want for all Js files. What should be our template file structure, if you could guide us in detail that would be a great help
Bower is old news. I’d recommend you to leave that alone and try WPGulp.
Thank you Ahmad for sharing your setup!
Been using grunt but want to switch to gulp instead and love your setup.
Got everything up and running but sourcemaps doesn’t seem to work.
Only getting reference to style.min.css not the partials.
Tried both on my own setup based on WPGulp and with your bolierplate. Any idea why?
Update! If I change in functions.php to use style.css instead sourcemaps seems to working just fine pointing to the correct *.scss.
Is this a requirement to get sourcemaps working or am I missing something?
Thank you in advance!
I would love to see a tutorial for gulp 4.
I am using local by flywheel with the wpgulp setup, everything is working fine except when i click on ‘view site’ in the local flywheel app my url is ‘http://starline.local’ which is correct. However the gulp url goes to “http://localhost:3000” and this works fine with the wpgulp. However i want the wpgulp to load the url ‘http://starline.local’ that local provides.
how do i set the url to be that instead of localhost. browsersyn only updates the localhost:3000 url and not the starline.local url.