$30 off During Our Annual Pro Sale. View Details »

Front-end tooling with Grunt, Gulp and Yeoman -...

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

Stefan Baumgartner

March 07, 2014
Tweet

More Decks by Stefan Baumgartner

Other Decks in Technology

Transcript

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

    CSSMIN ImageOptim Jasmine Mocha Proxy Connections Server Deployment CI/CD
  2. <?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"/>!
  3. <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>!
  4. <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
  5. <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:
  6. <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
  7. 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
  8. • parameter --save-dev adds modules to package.json • node_modules holds

    all the installed modules • npm install takes package.json to install required modules
  9. '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' ]); };
  10. '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
  11. '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
  12. '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
  13. • 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!
  14. Sass LESS Stylus CoffeeScript TypeScript JSHint JSLint HAML Jade Uglify

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

    CSSMIN ImageOptim Jasmine Mocha Proxy Connections Server Deployment CI/CD
  16. Connect Connect is a middleware framework for node, shipping with

    over 18 bundled middleware and a rich selection of 3rd-party middleware.
  17. connect: { server: { options: { port: 9009, base: '.',

    hostname: ‘0.0.0.0’, keepalive: true } } },
  18. 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.
  19. connect: { server: { options: { port: 9009, base: '.',

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

    hostname: ‘0.0.0.0’, livereload: 35729, open: true } } },
  21. return function(req, res, next) { if(/* req fulfills some condition

    */) { /* do something */ res.write(stdout); res.end(); } else { next(); /* next middleware in stack */ } }
  22. Development Distribution Server + Livereload + Watch Sass —> TMP

    JS Unminified Assemble —> TMP Linting Sass —> DIST JS Concat —> DIST + Minify Assemble —> DIST Linting Usemin
  23. sass: { dist: { options: { style: 'compressed' }, files:

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

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

    build • npm install just installs/updates modules which aren’t there already
  26. • 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.
  27. 'use strict'; ! module.exports = function(grunt) { // load all

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

    intialized … now registerTasks ... }; npm install --save-dev load-grunt-config
  29. // uglify.js ! module.exports = { dist: { files: {

    'dist/build.min.js': ['dist/build.js'] } } };
  30. • Stream based build system • Configuration over code •

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

    a “pipe” — tasks are run sequentially without being connected to the other With Grunt …
  32. 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
  33. 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
  34. • 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