Kinja

The platform behind Cink, Gizmodo, io9 & Lifehacker

Server-side: play.scala, closure-templates.soy

Client-side: backbone.js, require.js, closure-templates.js, foundation.scss

20 deploys a day

  • fully automated after a Git push
  • build & deploy by Jenkins took 12 minutes
  • downside: deployment needed a full JVM restart

Lots of frontend changes

80% of changesets didn't change .scala at all

JVM restart is an overkill

So we changed it

80% of our changesets are live in 2 minutes now

SBT.tasks.moveto(Grunt)

  • no need to use Play! this way
  • our SBT knowledge is kind of limited
  • our frontend team is full of JS devs
  • had good experiences with Grunt already

Grunt is great

  • Grunt is the JS task runner
  • "there's a task for that"
  • "there's an API"

An example


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

  # configuration happens here
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),

    jshint: {
      options: {
        jshintrc: '.jshintrc',
        reporter: require('jshint-stylish'),
        force: true
      },

      # this is the task jshint:dev
      dev: {
        src: [
          'public/javascripts/**/*.js'
        ]
      },

      # this is the task jshint:build
      build: {
        options: {
          reporter: 'jslint',
          reporterOutput: 'target/grunt/contrib-jshint.xml'
        },
        src: [
          'public/javascripts/**/*.js',
          '!public/javascripts/generated/**/*.js'
        ]
      }
    },

    # watch can run tasks whenever a file is changed, very
    # useful during development
    watch: {
      jshint: {

        # these are the files being watched
        files: [
          'public/javascripts/**/*.js'
        ],

        # the task to run on any change
        tasks: [
          'jshint:dev'
        ],
        options: {
          spawn: false
        }
      }
    },

    # this task will copy any *.css to filename-{md5}.css
    md5: {
      compile: {
        files: [
          {
            src: 'stylesheets/**/*.css',
            dest: 'stylesheets'
          }
        ],
        options: {
          encoding: null,
          keepBasename: true,
          keepExtension: true
        }
      }
    }
  });

  # on a watch:jshint event we want to jshint that very file only
  grunt.event.on('watch', function (action, filePath, target) {
    if (target === 'jshint') {
      grunt.config('jshint.dev.src', filePath);
    }
  });

  # load all installed grunt tasks
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-md5');

  # calling grunt without a task specified will run
  # jshint:build and then md5
  grunt.registerTask('default', [
    'jshint:build',
    'md5'
  ]);
};
						

Usage


# will run a build
grunt

# will run jshint on all JS files
grunt jshint:dev

# will start grunt watch
grunt watch
						

During development

Grunt Watch is used for

  • .soy to .js transforms
  • .scss to .css transforms
  • JSHinting

During a build

Series of tasks are run against our code

  • Code quality checks (JSHint, JSComplexity)
  • .scss to .css transforms
  • .soy to .js transforms
  • generating .js source
  • requirejs
  • uglify
  • md5

Interested?

  • Check Grunt API, it's great
  • explode your Gruntfile.js and DRY your config
  • see others too

EOM

Obviously, we're hiring

mailto:joco@gawker.com