hyper-optimized-workflow-slides



hyper-optimized-workflow-slides

0 4


hyper-optimized-workflow-slides


On Github jlengstorf / hyper-optimized-workflow-slides

Hyper-Optimized Workflow

With Grunt, LESS, Autoprefixer, Uncss, and More

Presented by Jason Lengstorf of Copter Labs

Twitter: @jlengstorf | Email: jason@copterlabs.com

A Little Background

  • Founder of Copter Labs
  • Consultant for startups/agencies
  • Pixel pushing since 2003
  • Author of 3 books on nerd shit

Workflow Optimization for Winners

What Is a Task Runner?

A task runner (or build system) is a script that executes a list of actions, typically using one or more plugins.

Popular Task Runners

Why Do We Need Task Runners?

Humans Are Bad at Menial Tasks

We have a tendency to be:

  • Lazy
  • Inconsistent
  • Forgetful
  • Easily Overwhelmed

Robots Are Good at Menial Tasks

(It’s what they’re built for.)

Automation Is a Huge Win

  • Less time spent “polishing”
  • Less effort to do things right
  • Tasks are always consistent
  • Tasks are always completed

What Task Runners Do

A (Very) Small Sampling of the Possibilities

Handle Preprocessing

LESS, SCSS, CoffeeScript, etc.

Concatenate & Minify Files

Such as CSS and JavaScript.

Lint Files

Using JSHint, JSLint, etc.

Start a Server

For local development.

Live Reload Changes

Refresh the browser when files change.

Create Production Builds

Output templates to static HTML, etc.

Optimize Images

Reduce the size of JPGs, PNGs, etc.

Bump File Versions

Update package.json, bower.json, the Git version...

Pretty Much Anything

If you can do it with code, it can be a task.

Real Examples Using Grunt & Gulp

Part 1: A Modern Fancypants App

  • Built on the MEAN stack
  • Uses LESS for styles
  • Uses EJS for templating

Headache Gruntwork

  • Compile LESS to CSS
  • Add vendor prefixes to CSS
  • Lint JS files
  • Combine and minify JS files
  • Set up a dev server locally
  • Reload when files are updated

App Source Code: http://git.io/2JuyAA

Let’s See the App

App Source Code: http://git.io/2JuyAA

The Rundown on Grunt

Vital Stats on Grunt.js

  • Grunt.js is built on Node.js
  • It’s the most popular task runner
  • The community is large (and helpful)
  • Thousands of available plugins
  • Building custom plugins is really easy

Install Grunt

Use npm to install grunt-cli:

npm install grunt-cli --save

Create a Gruntfile:

Create Gruntfile.js and save it at the root of your project.

'use strict';
module.exports = function(grunt) {

  // Dynamically loads all required grunt tasks
  require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);

  // Configures the tasks that can be run
  grunt.initConfig({

  });

});

Remember to install matchdep:

npm install matchdep --save-dev

Each Grunt Task Is a Plugin

// Install a plugin
npm install grunt-pluginname

// Install and save as a dependency
npm install grunt-pluginname --save

// Install and save as a dev dependency
npm install grunt-pluginname --save-dev

How File Matching Works

// Matches a single file
files: 'foo.js'

// Matches an array of files
files: [ 'foo.js', 'bar.js' ]

// Matches all files in the dir
files: '*'

// Matches all files with a given extension in the dir
files: '*.js'

// Matches all files with a given extension in all dirs
files: '**/*.js'

// Matches all files w/extension in this dir and one dir deeper
files: '{,*/}*.js'

// These rules can be combined with fragments of paths
files: 'src/js/{,*/}*.js'

Compile LESS to CSS the Hard Way

  • Client-side (no no no no)
  • Server-side (overkill in production)
  • Manually at deployment (a pain in the ass)
  • Using a third-party app like CodeKit (good, but...)
CodeKit is awesome, but it requires everyone on the team to have it installed. Plus, if the configuration is different between machines and/or team members, it can cause unexpected results.

Compile LESS to CSS the Easy Way

Install the plugin (grunt-contrib-less):

npm install grunt-contrib-less --save-dev

Add configuration to your Gruntfile:

    // Compiles LESS files to CSS
    less: {
      dev: {
        options: {
          cleancss: true // Minifies CSS output
        },
        files: { 'src/app/css/combined-grunt.min.css': 'src/app/less/{,*/}*.less' }
      }
    },
The plugin is saved for dev only because we don’t need to compile LESS in a production environment. Also, check out the wacky mess in the LESS filenames -- we’ll get to that on the next slide. Show the demo grunt testautoprefixer

Vendor Prefixes the Hard Way

  • Manually
  • LESS/SASS Mixins
/* This sucks to do while coding */
.manually {
    -webkit-box-shadow:inset 1px 1px 2px #111;
    box-shadow:inset 1px 1px 2px #111;
}

/* This isn't CSS */
.with-mixin {
    .box-shadow(1px, 1px, 2px, #111);
}

Add Vendor Prefixes the Easy Way

Install the plugin (grunt-autoprefixer):

npm install grunt-autoprefixer --save-dev

Add configuration to your Gruntfile:

    // Adds vendor prefixes to CSS
    autoprefixer: {
      dev: {
        src: 'src/app/css/combined-grunt.min.css'
      }
    },

Then Just Write CSS

.spinning-icon {
    animation: spinme 2s infinite ease-in-out;
}

@keyframes spinme {
    0%   { transform: rotate(0deg); }
    50%  { transform: rotate(360deg); }
    100% { transform: rotate(0deg); }
}

Aside: When I Started Using Autoprefixer, I Was Like

Remove Unused CSS the Hard Way

  • .
  • ..
  • ...

(Has anyone ever done this?)

Remove Unused CSS the Easy Way

Install the plugin (grunt-uncss):

npm install grunt-uncss --save-dev

Add configuration to your Gruntfile:

// Removes unused CSS selectors
uncss: {
  testuncss: {
    files: {
      'demo/css/uncss-test-clean.css': 'demo/index.html'
    }
  }
},
Show the demo! grunt testuncss

Lint JS Files

Install the plugins (grunt-contrib-jshint & jshint-stylish):

npm install grunt-contrib-jshint --save-dev
npm install jshint-stylish --save-dev

Add configuration to your Gruntfile:

    jshint: {
        dev: {
          options: {
            jshintrc: true,
            reporter: require('jshint-stylish'),
          },
          src: [ 
            '{,*/}*.js', 
            'src/app/js/{,*/}*.js', 
            '!src/app/js/combined*.js' 
          ]
        }
    },
Show the demo! grunt jshint:testfail grunt jshint:testpass

JSHint Configuration

To avoid non-issue errors, you can create a new file called .jshintrc:

{
  "node": true,
  "-W097": true, // Ignores "use strict" warning
  "browser": true,
  "devel": true,
  "validthis": true, // Avoids a warning using `this`
  "globals": {
    "angular": true
  }
}

More info: JSHint Docs

Combine and Minify JS Files

Install the plugin (grunt-contrib-uglify):

npm install grunt-contrib-uglify --save-dev

Add configuration to your Gruntfile:

    // Combines and minifies JS files
    uglify: {
      options: {
        mangle: false,
        compress: true,
        preserveComments: 'some'
      },
      scripts: {
        files: {
          'src/app/js/combined-grunt.min.js': [
            'src/app/js/{,*/}*.js',
            '!src/app/js/combined*.js'
          ]
        }
      }
    },
The mangle: false setting is there to avoid breaking Angular. There are ways to work around this issue, but they’re a little too involved to include here.

Set Up a Dev Server Locally

Install the plugin (grunt-nodemon):

npm install grunt-nodemon --save-dev

Add configuration to your Gruntfile:

    // Watches back-end files for changes, restarts the server
    nodemon: {
      dev: {
        script: 'src/server.js',
        options: {
          env: {
            PORT: 9000
          },
          ext: 'js,ejs,html',
          callback: function (nodemon) {
            nodemon.on('log', function (event) {
              console.log(event.colour);
            });

            // opens browser on initial server start
            nodemon.on('config:update', function () {
              // Delay before server listens on port
              setTimeout(function() {

require('open')('http://localhost:9000');
              }, 1000);
            });

            // refreshes browser when server reboots
            nodemon.on('restart', function () {
              // Delay before server listens on port
              setTimeout(function() {

require('fs').writeFileSync('.rebooted', 'rebooted');
              }, 1000);
            });
          }
        }
      }
    },

Reload When Files Are Updated

Install the plugin (grunt-contrib-watch):

npm install grunt-contrib-watch --save-dev

Add configuration to your Gruntfile:

    // Watches front-end files for changes and reruns tasks as needed
    watch: {
      todo: {
        // NOTE Uses the todo file list to save time if the list changes
        files: [ '<%= todo.src %>' ],
        tasks: [ 'todo' ]
      },
      styles: {
        files: [ 'src/app/less/{,*/}*.less' ],
        tasks: [ 'less:dev', 'autoprefixer:dev' ],
        options: {
          livereload: true
        }
      },
      scripts: {
        files: [ 'src/app/js/{,*/}*.js', '!src/app/js/combined*.js' ],
        tasks: [ 'jshint:dev', 'uglify:scripts' ]
      },
      server: {
        files: ['.rebooted'],
        options: {
          livereload: true
        }
      } 
    },

Run Nodemon and Watch Concurrently

Install the plugin (grunt-concurrent):

npm install grunt-concurrent --save-dev

Add configuration to your Gruntfile:

    // Allows us to run watch and nodemon concurrently with logging
    concurrent: {
      dev: {
        tasks: [ 'nodemon:dev', 'watch' ],
        options: {
          logConcurrentOutput: true
        }
      }
    },

Register the Default Task

  // Compiles LESS/JS and checks for todos
  grunt.registerTask('default', [
    'less:dev',
    'autoprefixer:dev',
    'jshint:dev',
    'uglify:scripts'
  ]);

This is outside of grunt.initConfig().

Register the Server Task

  // Starts a server and runs nodemon and watch using concurrent
  grunt.registerTask('server', [ 'concurrent:dev' ]);

This is outside of grunt.initConfig().

The Deets on Gulp

(I’m So Sorry About That)

Vital Stats on Gulp.js

  • Gulp.js is built on Node.js
  • It’s argued to be easier than Grunt
  • The community is smaller (but still helpful)
  • Fewer plugins (the important ones are there)
  • Building custom plugins is less structured

Install and Set Up Gulp

Install Gulp:

npm install gulp --save-dev

Create a gulpfile.js:

// Loads required dependencies
var gulp = require('gulp'),
    path = require('path'),
    rename = require('gulp-rename'),
    less = require('gulp-less'),
    prefix = require('gulp-autoprefixer'),
    jshint = require('gulp-jshint'),
    uglify = require('gulp-uglifyjs'),
    livereload = require('gulp-livereload'),
    server = require('tiny-lr')(),
    nodemon = require('gulp-nodemon');

// Sets up file paths for reuse in task/watch config
var filepaths = {
    scripts: [ 'app/js/**/*.js', '!app/js/combined*.js' ]
};
Don’t forget to install the plugins with npm install [plugin name] --save-dev

Compile, Compress, Prefix CSS

// Handles task relating to styles
gulp.task('styles', function(){
    gulp.src('app/less/main.less')

        // Compiles LESS files to CSS and minifies it
        .pipe(less({ cleancss: true }))

        // Adds vendor prefixes to CSS
        .pipe(prefix())

        // Renames the CSS file and saves it in the proper place
        .pipe(rename('combined-gulp.min.css'))
        .pipe(gulp.dest('app/css/'))

        // Triggers a live reload to show changes immediately
        .pipe(livereload());
});

Plugins: gulp-less, gulp-autoprefixer

Lint, Concatenate, Compress JS

// Handles tasks relating to scripts
gulp.task('scripts', function(){
    gulp.src(filepaths.scripts)

        // Checks JS files for issues
        .pipe(jshint())
        .pipe(jshint.reporter('jshint-stylish'))

        // Combines and minifies scripts
        .pipe(uglify('combined-gulp.min.js', { mangle: false }))

        // Saves the minified JS
        .pipe(gulp.dest('app/js/'))

        // Triggers a live reload to show changes immediately
        .pipe(livereload());
});

Plugins: gulp-jshint, gulp-uglifyjs

Set Up a Dev Server Locally

// Watches back-end files for changes, restarts the server
gulp.task('nodemon', function(){
    nodemon({ script: 'server.js' });
});

Plugins: gulp-nodemon

Reload When Files Are Updated

// Watches front-end files for changes and reruns tasks as needed
gulp.task('watch', [ 'nodemon' ], function(){
    livereload.listen();

    gulp.watch('app/less/**/*.less', [ 'styles' ]);
    gulp.watch(filepaths.scripts, [ 'scripts' ]);
});

Plugins: none (watch is built in)

Register a Default Task

gulp.task('default', [ 'watch' ]);

So we can start with just gulp in the command line.

Part 2: Grunt in Regular WordPress Sites

Let’s Just Look at It

Questions?

Don’t stick on this slide. Go to the next one.

Hyper-Optimized Workflow

With Grunt, LESS, Autoprefixer, Uncss, and More

Presented by Jason Lengstorf of Copter Labs

Twitter: @jlengstorf | Email: jason@copterlabs.com