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. Frontend Tooling with Grunt / Gulp / Yeoman Workshop JavaScript

    Days Munich, March 2014
  2. https://github.com/ddprrt/tooling-workshop https://github.com/ddprrt/generator-netural https://fettblog.eu Workshop samples

  3. Servus

  4. None
  5. @ddprrt

  6. Netural

  7. workingdraft.de

  8. None
  9. Our development stack

  10. Sass LESS Stylus CoffeeScript TypeScript JSHint JSLint HAML Jade Uglify

    CSSMIN ImageOptim Jasmine Mocha Proxy Connections Server Deployment CI/CD
  11. None
  12. None
  13. None
  14. I should use a tool

  15. None
  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"/>!
  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>!
  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
  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:
  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
  21. None
  22. None
  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
  24. None
  25. eco system

  26. None
  27. Runtime Environment JavaScript

  28. None
  29. package manager each module is installed via npm grunt and

    grunt plugins
  30. grunt provides an interface to node modules grunt plugins are

    usually just a wrapper
  31. npm install -g grunt-cli npm init touch Gruntfile.js

  32. to the command line!

  33. to the command line!

  34. npm install --save-dev grunt-contrib-* npm install --save-dev grunt

  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
  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' ]); };
  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
  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
  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
  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!
  41. None
  42. None
  43. basic tasks

  44. contrib tasks

  45. Sass LESS Stylus CoffeeScript TypeScript JSHint JSLint HAML Jade Uglify

    CSSMIN ImageOptim Jasmine Mocha Proxy Connections Server Deployment CI/CD
  46. Sass LESS Stylus CoffeeScript TypeScript JSHint JSLint HAML Jade Uglify

    CSSMIN ImageOptim Jasmine Mocha Proxy Connections Server Deployment CI/CD
  47. None
  48. None
  49. None
  50. None
  51. None
  52. connect

  53. Connect Connect is a middleware framework for node, shipping with

    over 18 bundled middleware and a rich selection of 3rd-party middleware.
  54. connect.static connect.directory connect.static connect.session

  55. connect.static connect.directory Request cannot GET Response OK Response OK

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

    hostname: ‘0.0.0.0’, keepalive: true } } },
  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.
  58. connect + watch

  59. grunt.loadNpmTasks('grunt-contrib-connect'); grunt.loadNpmTasks(‘grunt-contrib-watch'); ! grunt.registerTask('serve', [ 'connect:server', 'watch' ]);

  60. watch: { scripts: { files: ['scripts/*.js'], tasks: ['jshint', 'uglify'] },

    }, …
  61. connect.static connect.directory livereload Request Response OK! Response Injected

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

    hostname: ‘0.0.0.0’, keepalive: true } } },
  63. connect: { server: { options: { port: 9009, base: '.',

    hostname: ‘0.0.0.0’, livereload: 35729, open: true } } },
  64. middlewares

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

    */) { /* do something */ res.write(stdout); res.end(); } else { next(); /* next middleware in stack */ } }
  66. https://github.com/ddprrt/connect-php https://www.npmjs.org/package/connect-php http://fettblog.eu/blog/2013/11/17/the-magic-of-grunt- contrib-connect-and-how-to-run-php-with-it/

  67. CI / CD

  68. Yes, we can!

  69. Development Distribution Server + Livereload + Watch Sass —> TMP

    JS Unminified Assemble —> TMP Linting
  70. Development Distribution Server + Livereload + Watch Sass —> TMP

    JS Unminified Assemble —> TMP Linting Sass —> DIST JS Concat —> DIST + Minify Assemble —> DIST Linting Usemin
  71. grunt.initConfig({ // configurable paths yeoman: { app: 'app', dist: 'dist'

    }, … });
  72. sass: { dist: { options: { style: 'compressed' }, files:

    { '<%= yeoman.dist %>/styles/main.css': '<%= yeoman.app %>/styles/main.scss' } } ...
  73. ... server: { options: { style: ‘expanded’, debugInfo: true },

    files: { ‘.tmp/styles/main.css': '<%= yeoman.app %>/styles/main.scss' } } }
  74. • do not commit node_modules • run
 npm install
 grunt

    build • npm install just installs/updates modules which aren’t there already
  75. https://github.com/ddprrt/generator-netural http://assemble.io/ http://fettblog.eu/blog/2013/09/02/using-assemble-io-with- yeoman-ios-webapp-gruntfile/

  76. gruntplugins

  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.
  78. None
  79. None
  80. None
  81. grunt++

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

    grunt tasks require('load-grunt-tasks')(grunt); ! ... ! }; npm install --save-dev load-grunt-tasks
  83. 'use strict'; ! module.exports = function(grunt) { require(‘matchdep’) .filterDev(‘grunt-*') .forEach(grunt.loadNpmTasks);

    // faster! ! ... }; npm install --save-dev matchdep
  84. 'use strict'; ! module.exports = function(grunt) { require(‘load-grunt-config')(grunt); ! //Config

    intialized … now registerTasks ... }; npm install --save-dev load-grunt-config
  85. - myproject/ |-- Gruntfile.js |-- grunt/ |-- concat.js |-- uglify.js

    |-- imagemin.js
  86. // uglify.js ! module.exports = { dist: { files: {

    'dist/build.min.js': ['dist/build.js'] } } };
  87. http://use-init.com/ http://www.html5rocks.com/en/tutorials/tooling/ supercharging-your-gruntfile/

  88. // grunt-contrib-concurrent runs tasks parallel ! concurrent: { dist: [

    'sass', 'assemble' ], }
  89. None
  90. • Stream based build system • Configuration over code •

    Simple API • Small idiomatic Node modules Gulp?
  91. • Accessing the file system too often • Not really

    a “pipe” — tasks are run sequentially without being connected to the other With Grunt …
  92. • The Pipe is everything • Defining one bigger “task”

    of smaller build steps With Gulp …
  93. Gulp API gulp.task(‘taskname’, [‘deps’], fn); gulp.watch(‘files.js’, [‘tasks’], fn); gulp.src([’files.js’]); gulp.dest(’endpoint.js’);

  94. None
  95. None
  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
  97. None
  98. None
  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
  100. yeoman

  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
  102. npm install -g yo npm install -g generator-webapp yo webapp

    this.template() Usage
  103. this.prompt() this.mkdir() this.copy() this.template() API

  104. workingdraft.de/159

  105. workingdraft.de/152

  106. workingdraft.de/78

  107. None
  108. None