How I use Gulp to automate my front-end build tasks

How I use Gulp to automate my front-end build tasks
Rate This Post

I use Gulp as an automated task runner primarily to streamline my front-end workflow because I prefer the focus on code-over-configuration. For me, I felt more comfortable around the JS code/stream to get the job done: in a way, it felt more obvious what was going on.

My specific gulpfile solves my specific workflow problems, but I think a lot of what I am doing can be repurposed. If anyone has any performance improvements or suggestions, please feel free to add them in the comments.

I’m assuming you know some of the basics of Gulp. If not, to get started, I recommend reading this blog post on Sitepoint to get a feel for it.

Nevertheless, I will go over some of the needs and assumptions for my gulp “build” in this post to describe why and what I’m doing.

Here’s a few basics that I wanted to accomplish:

  • general gulpfile maintainability: I want to keep my file as succinct and clean as possible so that it can be reused, understood and maintained.
  • maintaining a directory structure (see below) that allows flexible compilation. There are so many exceptions/anomalies where vendor and custom file need to be blended together or kept separate.
    • I use Twitter Bootstrap as a CSS Framework and in some cases, I’d like to compile a completely custom Bootstrap CSS build base on the situation out of Bootstrap LESS files.
    • I use normalize.css and would like to use the latest css file to compile it at the top of my custom file to reset all my styles.
    • There are certain vendor libraries that I grab off CDNs and don’t want them combined/minified. I only want to maintain a directory with the physical files as CDN fallbacks.
    • I want to create an intermediary “build” and final “production” version of my files. Each has separate configurations, such as minification and debugging calls.

Plugins

Here is a few plugins that I use:

  • gulp-load-plugins: it autoloads plugins (defined in the package.json) into a variable so you don’t have to put a bunch of “require” statements. Keeps the gulpfile more succinct.
  • gulp-jshint
  • gulp-util
  • gulp-minify-css: minifies CSS
  • gulp-concat
  • gulp-uglify: minifies JS
  • gulp-rename
  • gulp-clean
  • gulp-filesize: shows the file size of the resulting file
  • gulp-less: compile less files
  • gulp-strip-debug: take out debug statements littered in your JS code
  • gulp-autoprefixer: adds in some browser prefixes (based on your settings)into your css for browser compatibility
  • gulp-if: allows you to use conditionals in the flow – if a condition is true/false run a plugin against the stream

Directory Structure

 dir_structureThe following directory structure is what I’m working with.

  • src” folder contains all the raw css, js and less files.
  • build” folder is an intermediary build of my files and is not production-ready. It still contains debug statements and non-minified code. This is good for testing on your dev server or just trying to debug.
  • assets” folder contains all live/compiled assets. The css and js folders within contain the production-ready css and js files – devoid of debug statements and unminified code.
  • assets/vendor” folder is physical files that contain jQuery or Modernizr. These files I load off CDNs, but need a physical fallback on the server
  • less/vendor/bootstrap“: contains les files for the bootrap CSS framework. I copied the bootstrap.less file and renamed it to bootstrap_custom.less and only imported the LESS portions that I needed for my project. The gulpfile looks for this file specifically.
  • any “custom” folder contains your code

Code

As always, I’ll let the commented code (package.json and gulpfile.js) do most of the talking. But the most relevant gulp commands (without quotes)that result are:

  • gulp” : runs the default task – this creates the production-ready CSS and JS
  • gulp all“: runs both the default and build tasks and creates the build and production-ready code
  • gulp watch“: watches all the CSS, Js and LESS files for updates and runs the production/default task on the appropriate files
  • gulp clean”: cleans both the build and production CSS and JS directories – this is automatically called before any “gulp” or “gulp all”
  1. {
  2. "dependencies": {
  3. "gulp-load-plugins": "*",
  4. "gulp-jshint": "*",
  5. "gulp-util": "*",
  6. "gulp-minify-css": "*",
  7. "gulp-concat": "*",
  8. "gulp-uglify": "*",
  9. "gulp-rename": "*",
  10. "gulp-clean": "*",
  11. "gulp-filesize": "*",
  12. "gulp-less": "*",
  13. "gulp-strip-debug": "*",
  14. "gulp-autoprefixer": "*",
  15. "gulp-if": "*",
  16. "through2": "*"
  17. }
  18. }
  1. var path = require('path');
  2. var gulp = require("gulp");
  3. //load in the plugins via the gulp-load-plugins plugin - the plugins are defined in the package.json file
  4. var plugins = require("gulp-load-plugins")();
  5.  
  6. //object with directories, glob paths that can be re-used in the gulp file
  7. var settings = {
  8. "styles" : {
  9. "bootstrap" : ["./src/css/vendor/normalize.min.css",'src/less/vendor/bootstrap/bootstrap_custom.less'],
  10. "default" : ["./src/css/vendor/normalize.min.css","./src/css/vendor/**/*.css", 'src/less/custom/**/*.less', './src/css/custom/**/*.css'],
  11. },
  12. "js" : {
  13. "vendor" : ['./src/js/vendor/**/*.js'],
  14. "default" : ['./src/js/custom/**/*.js']
  15. },
  16. "paths" : {
  17. "default" : {
  18. "js" : './assets/js/',
  19. "css" : './assets/css/'
  20. },
  21. "build" : {
  22. "js" : './build/js/',
  23. "css" : './build/css/'
  24. }
  25. },
  26. "clean" : {
  27. "default" : ['./assets/js/','./assets/css/'],
  28. "build" : ['./build/js/','./build/css/']
  29. }
  30. };
  31.  
  32.  
  33.  
  34. //Make a general "style compiling" function to re-use in certain task calls
  35. function compileStyles(compilationItems, filename, dest, minify){
  36. return gulp.src(compilationItems)
  37. .pipe(plugins.less({
  38. paths: [ path.join(__dirname, 'less', 'includes') ]
  39. }))
  40. .on('error', plugins.util.log)
  41. .pipe(plugins.autoprefixer('last 2 version', 'safari 5', 'ie 7', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'))
  42. //if the minify flag is set run the css minification
  43. .pipe(plugins.if(minify, plugins.minifyCss()))
  44. .on('error', plugins.util.log)
  45. .pipe(plugins.concat(filename))
  46. .pipe(plugins.filesize())
  47. .pipe(gulp.dest(dest))
  48. .on('error', plugins.util.log);
  49. }
  50.  
  51. //Make a general "js compiling" function to re-use in certain task calls
  52. function compileJS(compilationItems, filename, dest, minify, cleandebug){
  53. return gulp.src(compilationItems)
  54. .pipe(plugins.concat(filename))
  55. //if the cleandebug flag is set run the strigDebug plugin
  56. .pipe(plugins.if(cleandebug, plugins.stripDebug()))
  57. .on('error', plugins.util.log)
  58. //if the minify flag is set run the js minification
  59. .pipe(plugins.if(minify, plugins.uglify()))
  60. /*.pipe(plugins.jshint())
  61.   .pipe(plugins.jshint.reporter('default'))*/
  62. .on('error', plugins.util.log)
  63. .pipe(plugins.filesize())
  64. .pipe(gulp.dest(dest))
  65. .on('error', plugins.util.log);
  66. }
  67.  
  68. //function that adds all the values of an array subkey into one array - useful for grabbing all style or JS paths on-the-fly
  69. function concatAll(type){
  70. var finaloutput = [];
  71. for (var k in settings[type]) {
  72. var arr = settings[type][k];
  73. finaloutput = finaloutput.concat(arr);
  74. }
  75. return finaloutput;
  76. }
  77.  
  78. // JS
  79.  
  80. //production version of your scripts
  81. gulp.task('scripts', function() {
  82. return compileJS(settings.js.default, 'scripts.min.js',settings.paths.default.js, true, true);
  83. });
  84.  
  85. //build version of your scripts
  86. gulp.task('scripts-nomin', function() {
  87. return compileJS(settings.js.default, 'scripts.js', settings.paths.build.js, false, false);
  88. });
  89.  
  90. //production version of vendor scripts
  91. gulp.task('scripts-vendor', function() {
  92. return compileJS(settings.js.vendor, 'vendor.min.js',settings.paths.default.js, true, true);
  93. });
  94.  
  95. //build version of your scripts
  96. gulp.task('scripts-vendor-nomin', function() {
  97. return compileJS(settings.js.vendor, 'vendor.js', settings.paths.build.js, false, false);
  98. });
  99.  
  100. // CSS
  101.  
  102. //production version of all boostrap and your custom files into one - not preferred but available if needed
  103. gulp.task('all-styles-together', function () {
  104. return compileStyles(concatAll("styles"), 'styles.min.css', settings.paths.default.css, true);
  105. });
  106.  
  107. //build version of all boostrap and your custom files into one - not preferred but available if needed
  108. gulp.task('all-styles-together-nomin', function () {
  109. return compileStyles(concatAll("styles"), 'styles.css', settings.paths.build.css, false);
  110. });
  111.  
  112. //production version of a bootstrap css
  113. gulp.task('bootstrap-styles', function () {
  114. return compileStyles(settings.styles.bootstrap, 'bootstrap.custom.min.css', settings.paths.default.css, true);
  115. });
  116.  
  117. //production version of your styles
  118. gulp.task('styles', function () {
  119. return compileStyles(settings.styles.default, 'styles.min.css', settings.paths.default.css, true);
  120. });
  121.  
  122. //build version of a bootstrap css
  123. gulp.task('bootstrap-styles-nomin', function () {
  124. return compileStyles(settings.styles.bootstrap, 'bootstrap.custom.css', settings.paths.build.css, false);
  125. });
  126.  
  127. //build version of your styles
  128. gulp.task('styles-nomin', function () {
  129. return compileStyles(settings.styles.default, 'styles.css', settings.paths.build.css, false);
  130. });
  131.  
  132.  
  133. // Watch
  134. gulp.task('watch', function() {
  135. var style = concatAll("styles");
  136. var js = concatAll("js");
  137. var all = style.concat(js);
  138.  
  139. gulp.watch(style, ['bootstrap-styles', 'styles']);
  140. gulp.watch(js, ['scripts', 'scripts-vendor']);
  141. gulp.watch(all, function(event) {
  142. console.log('File '+event.path+' was '+event.type+', running tasks...');
  143. });
  144.  
  145. });
  146.  
  147. // Clean
  148.  
  149. //clean both the production and build files
  150. gulp.task('clean', function() {
  151. gulp.start('clean-default', 'clean-build');
  152. });
  153.  
  154. //clean the production files
  155. gulp.task('clean-default', function() {
  156. return gulp.src(settings.clean.default, {read: false})
  157. .pipe(plugins.clean());
  158. });
  159.  
  160. //clean the build files
  161. gulp.task('clean-build', function() {
  162. return gulp.src(settings.clean.build, {read: false})
  163. .pipe(plugins.clean());
  164. });
  165.  
  166. //default task - clean production directory first
  167. gulp.task('default', ['clean-default'], function() {
  168. gulp.start('styles', 'scripts', 'scripts-vendor');
  169. });
  170.  
  171. //task that combines all scripts - clean production directory first
  172. gulp.task('combinedbootstrap', ['clean-default'], function() {
  173. gulp.start('all-styles-together', 'scripts', 'scripts-vendor');
  174. });
  175.  
  176. //build task - clean build directory first
  177. gulp.task('build', ['clean-build'], function() {
  178. gulp.start('styles-nomin', 'scripts-nomin', 'scripts-vendor-nomin');
  179. });
  180.  
  181. //run both the build and production tasks
  182. gulp.task('all', ['build','default'], function() {
  183. });