Front-end tooling with Grunt, Gulp and Yeoman - JavaScript Days 2014

Front-end tooling with Grunt, Gulp and Yeoman - JavaScript Days 2014

Slides from my 3hr workshop on front-end tooling with yeoman, grunt and gulp. Be sure to check some samples on https://github.com/ddprrt/tooling-workshop and https://github.com/ddprrt/generator-netural

187d92c9284160ad908885ab096f5209?s=128

Stefan Baumgartner

March 07, 2014
Tweet

Transcript

  1. 3.
  2. 4.
  3. 5.
  4. 6.
  5. 8.
  6. 10.

    Sass LESS Stylus CoffeeScript TypeScript JSHint JSLint HAML Jade Uglify

    CSSMIN ImageOptim Jasmine Mocha Proxy Connections Server Deployment CI/CD
  7. 11.
  8. 12.
  9. 13.
  10. 15.
  11. 16.

    <?xml version="1.0"?>! <!DOCTYPE project>! <project name="Boilerplate Build" default="build" basedir="../"><!-- one

    back since we're in build/ -->! ! <!-- load shell environment -->! <property environment="ENV" />! ! <!-- load property files -->! <property file="build/config/project.properties"/><property file="build/config/default.properties"/>! ! <!-- Load in Ant-Contrib to give us access to some very useful tasks! -->! <!-- the .jar file is located in the tools directory -->! <taskdef resource="net/sf/antcontrib/antlib.xml">! <classpath>! <pathelement location="${basedir}/${dir.build.tools}/ant-contrib-1.0b3.jar"/>! </classpath>! </taskdef>! ! <!-- merge the stylesheet properties -->! <var name="stylesheet-files" value="${file.default.stylesheets}, ${file.stylesheets}"/>! ! <!-- merge the pages properties -->! <var name="page-files" value="${file.pages}, ${file.pages.default.include}"/>! ! <!-- Test for Ant Version Delete this task and all instances of overwrite='no' if you can't upgrade to latest-->! <fail message="All features of the build script require Ant version 1.8.2 or greater. Please upgrade to the latest version or remove all instances of 'overwrite=no' (and this fail task) from the build script to continue">! <condition>! <not>! <antversion atleast="1.8.2"/>! </not>! </condition>! </fail>! <target name="version">! <echo message="H5BP Ant Build Script Version ${project.version}"/>! </target>! <!--! *************************************************! * BASE TARGETS *! *************************************************! -->! <target name="basics">! <if>! <equals arg1="${env}" arg2="dev"/>! <then>! <!-- Build a dev environment -->! <echo message="Building a Development Environment..."/>! <antcall target="-basics.dev"/>! </then>! <elseif>! <equals arg1="${env}" arg2="test"/>!
  12. 17.

    <include name="**/*.png"/>! </fileset>! </delete>! </then>! </if>! <!-- Run if available

    -->! <for param="image-dir">! <path>! <dirset dir="${dir.source}" includes="${dir.images}"/>! </path>! <sequential>! <property name="relative.image.dir" location="@{image-dir}" relative="yes"/>! ! <if>! <not>! <available file="${basedir}/${dir.publish}/${relative.image.dir}"/>! </not>! <then>! <copy todir="${dir.publish}/${relative.image.dir}">! <dirset dir="${dir.source}/${relative.image.dir}"/>! </copy>! </then>! </if>! ! <apply executable="${optipng.executable}" dest="./${dir.publish}/${relative.image.dir}/">! <fileset dir="./${dir.source}/${relative.image.dir}/" includes="**/*.png" excludes="${images.bypass}, $ {images.default.bypass}"/>! <arg value="-quiet"/>! <arg value="-o7"/>! <arg value="-out"/>! <targetfile/>! <srcfile/>! <mapper type="identity"/>! </apply>! <var name="relative.image.dir" unset="true"/>! </sequential>! </for>! </then>! </if>! </else>! ! </if>! <!-- Let's do ADVPNG -->! <!-- On *nix's and OS X, check for advpng and give a helpful message if it's not installed -->! ! <echo message="Now, we run advpng on the .png files..."/>! ! <if>! <os family="windows"/>! <then>!
  13. 18.

    <include name="**/*.png"/>! </fileset>! </delete>! </then>! </if>! <!-- Run if available

    -->! <for param="image-dir">! <path>! <dirset dir="${dir.source}" includes="${dir.images}"/>! </path>! <sequential>! <property name="relative.image.dir" location="@{image-dir}" relative="yes"/>! ! <if>! <not>! <available file="${basedir}/${dir.publish}/${relative.image.dir}"/>! </not>! <then>! <copy todir="${dir.publish}/${relative.image.dir}">! <dirset dir="${dir.source}/${relative.image.dir}"/>! </copy>! </then>! </if>! ! <apply executable="${optipng.executable}" dest="./${dir.publish}/${relative.image.dir}/">! <fileset dir="./${dir.source}/${relative.image.dir}/" includes="**/*.png" excludes="${images.bypass}, $ {images.default.bypass}"/>! <arg value="-quiet"/>! <arg value="-o7"/>! <arg value="-out"/>! <targetfile/>! <srcfile/>! <mapper type="identity"/>! </apply>! <var name="relative.image.dir" unset="true"/>! </sequential>! </for>! </then>! </if>! </else>! ! </if>! <!-- Let's do ADVPNG -->! <!-- On *nix's and OS X, check for advpng and give a helpful message if it's not installed -->! ! <echo message="Now, we run advpng on the .png files..."/>! ! <if>! <os family="windows"/>! <then>! 1530 lines of code
  14. 19.

    <include name="**/*.png"/>! </fileset>! </delete>! </then>! </if>! <!-- Run if available

    -->! <for param="image-dir">! <path>! <dirset dir="${dir.source}" includes="${dir.images}"/>! </path>! <sequential>! <property name="relative.image.dir" location="@{image-dir}" relative="yes"/>! ! <if>! <not>! <available file="${basedir}/${dir.publish}/${relative.image.dir}"/>! </not>! <then>! <copy todir="${dir.publish}/${relative.image.dir}">! <dirset dir="${dir.source}/${relative.image.dir}"/>! </copy>! </then>! </if>! ! <apply executable="${optipng.executable}" dest="./${dir.publish}/${relative.image.dir}/">! <fileset dir="./${dir.source}/${relative.image.dir}/" includes="**/*.png" excludes="${images.bypass}, $ {images.default.bypass}"/>! <arg value="-quiet"/>! <arg value="-o7"/>! <arg value="-out"/>! <targetfile/>! <srcfile/>! <mapper type="identity"/>! </apply>! <var name="relative.image.dir" unset="true"/>! </sequential>! </for>! </then>! </if>! </else>! ! </if>! <!-- Let's do ADVPNG -->! <!-- On *nix's and OS X, check for advpng and give a helpful message if it's not installed -->! ! <echo message="Now, we run advpng on the .png files..."/>! ! <if>! <os family="windows"/>! <then>! 1530 lines of code Original Ant tasks used:
  15. 20.

    <include name="**/*.png"/>! </fileset>! </delete>! </then>! </if>! <!-- Run if available

    -->! <for param="image-dir">! <path>! <dirset dir="${dir.source}" includes="${dir.images}"/>! </path>! <sequential>! <property name="relative.image.dir" location="@{image-dir}" relative="yes"/>! ! <if>! <not>! <available file="${basedir}/${dir.publish}/${relative.image.dir}"/>! </not>! <then>! <copy todir="${dir.publish}/${relative.image.dir}">! <dirset dir="${dir.source}/${relative.image.dir}"/>! </copy>! </then>! </if>! ! <apply executable="${optipng.executable}" dest="./${dir.publish}/${relative.image.dir}/">! <fileset dir="./${dir.source}/${relative.image.dir}/" includes="**/*.png" excludes="${images.bypass}, $ {images.default.bypass}"/>! <arg value="-quiet"/>! <arg value="-o7"/>! <arg value="-out"/>! <targetfile/>! <srcfile/>! <mapper type="identity"/>! </apply>! <var name="relative.image.dir" unset="true"/>! </sequential>! </for>! </then>! </if>! </else>! ! </if>! <!-- Let's do ADVPNG -->! <!-- On *nix's and OS X, check for advpng and give a helpful message if it's not installed -->! ! <echo message="Now, we run advpng on the .png files..."/>! ! <if>! <os family="windows"/>! <then>! 1530 lines of code Original Ant tasks used: concat - copy - delete - mkdir
  16. 21.
  17. 22.
  18. 23.

    In the end, I realized that a task-based build tool

    with built-in, commonly used tasks was the approach that would work best for me. Unfortunately, I couldn’t find a build tool that worked the way that I wanted. So I built one. — Ben Alman, 2012
  19. 24.
  20. 26.
  21. 28.
  22. 35.

    • parameter --save-dev adds modules to package.json • node_modules holds

    all the installed modules • npm install takes package.json to install required modules
  23. 36.

    'use strict'; ! module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-uglify'); ! grunt.initConfig({

    uglify: { dist: { files: [{ 'scripts/main.min.js' : ['scripts/main.js'] }] } } }); ! grunt.registerTask('default', [ 'uglify' ]); };
  24. 37.

    'use strict'; ! module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-uglify'); ! grunt.initConfig({

    uglify: { dist: { files: [{ 'scripts/main.min.js' : ['scripts/main.js'] }] } } }); ! grunt.registerTask('default', [ 'uglify' ]); }; task
  25. 38.

    'use strict'; ! module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-uglify'); ! grunt.initConfig({

    uglify: { dist: { files: [{ 'scripts/main.min.js' : ['scripts/main.js'] }] } } }); ! grunt.registerTask('default', [ 'uglify' ]); }; task target
  26. 39.

    'use strict'; ! module.exports = function(grunt) { grunt.loadNpmTasks('grunt-contrib-uglify'); ! grunt.initConfig({

    uglify: { dist: { files: [{ 'scripts/main.min.js' : ['scripts/main.js'] }] } } }); ! grunt.registerTask('default', [ 'uglify' ]); }; task target options
  27. 40.

    • registerTask registers a sequence of tasks! • grunt calls

    default task
 grunt build calls build task • initConfig takes an object of task definitions • loadNpmTasks loads one installed task
 has to be defined in package.json!
  28. 41.
  29. 42.
  30. 45.

    Sass LESS Stylus CoffeeScript TypeScript JSHint JSLint HAML Jade Uglify

    CSSMIN ImageOptim Jasmine Mocha Proxy Connections Server Deployment CI/CD
  31. 46.

    Sass LESS Stylus CoffeeScript TypeScript JSHint JSLint HAML Jade Uglify

    CSSMIN ImageOptim Jasmine Mocha Proxy Connections Server Deployment CI/CD
  32. 47.
  33. 48.
  34. 49.
  35. 50.
  36. 51.
  37. 52.
  38. 53.

    Connect Connect is a middleware framework for node, shipping with

    over 18 bundled middleware and a rich selection of 3rd-party middleware.
  39. 56.

    connect: { server: { options: { port: 9009, base: '.',

    hostname: ‘0.0.0.0’, keepalive: true } } },
  40. 57.

    Problem? Keep the server alive indefinitely. Note that if this

    option is enabled, any tasks specified after this task will never run. By default, once grunt's tasks have completed, the web server stops. This option changes that behavior.
  41. 62.

    connect: { server: { options: { port: 9009, base: '.',

    hostname: ‘0.0.0.0’, keepalive: true } } },
  42. 63.

    connect: { server: { options: { port: 9009, base: '.',

    hostname: ‘0.0.0.0’, livereload: 35729, open: true } } },
  43. 65.

    return function(req, res, next) { if(/* req fulfills some condition

    */) { /* do something */ res.write(stdout); res.end(); } else { next(); /* next middleware in stack */ } }
  44. 67.
  45. 69.
  46. 70.

    Development Distribution Server + Livereload + Watch Sass —> TMP

    JS Unminified Assemble —> TMP Linting Sass —> DIST JS Concat —> DIST + Minify Assemble —> DIST Linting Usemin
  47. 72.

    sass: { dist: { options: { style: 'compressed' }, files:

    { '<%= yeoman.dist %>/styles/main.css': '<%= yeoman.app %>/styles/main.scss' } } ...
  48. 73.

    ... server: { options: { style: ‘expanded’, debugInfo: true },

    files: { ‘.tmp/styles/main.css': '<%= yeoman.app %>/styles/main.scss' } } }
  49. 74.

    • do not commit node_modules • run
 npm install
 grunt

    build • npm install just installs/updates modules which aren’t there already
  50. 77.

    • Install grunt-init with npm install -g grunt-init • Install

    the gruntplugin template with git clone git://github.com/gruntjs/grunt-init- gruntplugin.git ~/.grunt-init/gruntplugin • Run grunt-init gruntplugin in an empty directory.! • Run npm install to prepare the development environment.
  51. 78.
  52. 79.
  53. 80.
  54. 81.
  55. 82.

    'use strict'; ! module.exports = function(grunt) { // load all

    grunt tasks require('load-grunt-tasks')(grunt); ! ... ! }; npm install --save-dev load-grunt-tasks
  56. 84.

    'use strict'; ! module.exports = function(grunt) { require(‘load-grunt-config')(grunt); ! //Config

    intialized … now registerTasks ... }; npm install --save-dev load-grunt-config
  57. 86.

    // uglify.js ! module.exports = { dist: { files: {

    'dist/build.min.js': ['dist/build.js'] } } };
  58. 89.
  59. 90.

    • Stream based build system • Configuration over code •

    Simple API • Small idiomatic Node modules Gulp?
  60. 91.

    • Accessing the file system too often • Not really

    a “pipe” — tasks are run sequentially without being connected to the other With Grunt …
  61. 92.
  62. 94.
  63. 95.
  64. 96.

    Apples & Oranges • Code over configuration • Tasks run

    a sequence of methods • Methods don’t care about IO • Good amount of plugins, able to use unrelated modules • Configuration over code • Small, independent tasks, but with strong connection to IO • S**tload of plugins, a lot of them not really good • There might be a task for your need already there
  65. 97.
  66. 98.
  67. 99.

    node-task • https://github.com/node-task/spec • Common ground for both gulp and

    grunt • Uses best practices of both libraries • The only thing different will be the interface
  68. 100.
  69. 101.

    • yeoman takes care about project scaffolding: installing best practices

    and common project standards by just one click! • plenty of official and community generators are out there! • overcomes blank page angst
  70. 107.
  71. 108.