The JavaScript Task Runner
Dr. Gleb Bahmutov, PhD
@bahmutov
- code needs to be tested
- js/css files need to be
- bundled up
- minified
- templates need to be compiled
- images need adjustment
- versions need to be updated
Think of each build step as a task
- portability
- power
Oldest (?) technology still in use
all: dist/app.js dist/index.html
dist/app.js: tmp/index.js libs/jquery.js
concat #$% &$& %$& %$^@!^
cp -f -y ???
*.tmp.js: ($@.*.js)
uglify @$
Very non friendly syntax
include $(BUILD)/makefiles/bootstrap.mk
include $(INCLUDE_DIR)/module_interface.mk
$(foreach R,$(call REQUIREMENTS,.),\
$(eval $(call BUILD_REQUIREMENT,$(R))))
...
# typical command
$(JAVAEXE) -Xms512m -Xmx512m -cp "$(DOJO_PATH)/util/shrinksafe/js.jar$(PATH_SEPARATOR)$(DOJO_PATH)/util/closureCompiler/compiler.jar$(PATH_SEPARATOR)$(DOJO_PATH)/util/shrinksafe/shrinksafe.jar" \
org.mozilla.javascript.tools.shell.Main $(DOJO_PATH)/dojo/dojo.js baseUrl=$(DOJO_PATH)/dojo load=build --profile $(1) $(BUILDMODE)
@echo "Dojo returned"
...
Java world's contribution to task runner
Fixed build lifecycle:
Manages dependencies locally and in central repo.
- every developer knows what to expect
- test step
- managed dependencies
- inflexible
- super verbose XML configuration
- all commands through plugins (pom.xml > 500 loc commong for just jslint, minification and testing)
- slow
- Java-based
Works at Bocoup, Boston
"Doing all this stuff manually is a total pain, and building all this stuff into a gigantic Makefile / Jakefile / Cakefile / Rakefile / ?akefile that's maintained across all my projects was also becoming a total pain." - Ben Alman
Latest version 0.4.1 - major rewrite after 0.3.0
Install nodejs, server-side JavaScript engine.
sudo npm install -g grunt-cli
Grunt, plugins and project settings are CommonJs modules.
Create sample project npm init
Add grunt and a few plugins
npm i grunt --save
npm i grunt-contrib-jshint --save
npm i matchdep --save
Try running grunt
module.exports = function(grunt) {
grunt.initConfig({
jshint: {
src: '*.js'
}
});
var plugins = require('matchdep').filter('grunt-*');
plugins.forEach(grunt.loadNpmTasks);
// tasks composed from other tasks
grunt.registerTask('default', ['jshint']);
grunt.registerTask('pre-commit',
['clean', 'jshint', 'all-tests']);
};
// runs jshint task
grunt jshint
// runs another task
grunt pre-commit
// run 'default', ignore errors
grunt --force
// see what's going on
grunt --verbose
grunt.initConfig({
jshint: { // grunt jshint
options: {
// applies to all subtasks
},
sourceFiles: { // grunt jshint:sourceFiles
options: {
// add options for some files
},
src: 'src/**/*.js'
},
testFiles: { // grunt jshint:testFiles
src: 'test/**/*.js'
}
}
});
From concat plugin
grunt.registerTask('test',
['clean', 'concat', 'nodeunit']);
grunt.registerTask('default',
['jshint', 'test', 'build-contrib']);
uglify: {
dist: {
files: {
'dist/<%= my.name %>.min.js':
['<%= concat.dist.dest %>']
}
}
},
my: { name: 'example' }
module.exports = function( grunt ) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
banner: {
compact: '/*! <%= pkg.name %> <%= pkg.version %> */'
}
});
};
Gruntfile.js is just a JavaScript file, so the tasks can be generated programmatically.
var jshintOptions = any crazy js code
var myTask = super task
module.exports = function(grunt) {
jshint: jshintOptions,
myTask: myTask
};
var jshintOptions = any crazy js code
console.log(jshintOptions);
module.exports = function(grunt) {
jshint: jshintOptions
};
Or pass filter function to see all filenames for a task, see Files
Use time-grunt
module.exports = function(grunt) {
require('time-grunt')(grunt);
grunt.initConfig({
...
grunt.initConfig({
parallel: {
all: {
options: {
grunt: true
},
tasks: ['jshint', 'unit-test', 'requirejs']
}
}
});
28 official plugins have names like *grunt-contrib-**. Install them all using single grunt-contrib module.
2k (!) user plugins have usually names like grunt-*
Full list - generates automatically from npm registry, modules tagged with gruntplugin
Most plugins upgraded to work with grunt 0.4
Writing or updating a plugin is very easy
Use starter project and look at creating tasks. See api docs.
grunt.registerTask('foo', 'A sample',
function(arg1, arg2) {
grunt.log.writeln(this.name + ", " + arg1 + ", " + arg2);
});
grunt foo:first:second
// foo, first, second
Typical plugin to delete files / folders.
contrib-clean, contrib-copy, contrib-jshint, grunt-jslint, contrib-csslint, json-lint, contrib-qunit, contrib-sass, contrib-concat, contrib-requirjs, grunt-dojo, contrib-uglify, contrib-watch, grunt-notify
- grunt-nice-package for nice-package
- grunt-clean-console for clean-console
- grunt-npm2bower-sync
- grunt-deps-ok for deps-ok
- grunt-jshint-solid for jshint-solid
AngularJs Gruntfile.js, Modernizr Gruntfile.js, jQuery Gruntfile.js, QUnit Gruntfile.js,
Just like Makefile, grunt uses temp files to pass source between tasks, example
- Gruntfile complexity
- disk I/O is slow
New kid on the block gulpjs
Based on Nodejs event streams:
var fs = require("fs");
var zlib = require("zlib");
fs.createReadStream("input/people.csv.gz")
.pipe(zlib.createGunzip())
.pipe(fs.createWriteStream("output/people.csv"));
sudo npm install -g gulp
npm install --save-dev gulp gulp-util
// gulpfile.js
var gulp = require('gulp');
gulp.task('default', function(){
gulp.src('./client/templates/*.jade')
.pipe(jade())
.pipe(minify())
.pipe(gulp.dest('./build/minified_templates'));
});
Good comparison post. Due to lower I/O Gulp is much faster.
Grunt 0.5 will introduce streams.
My gulp plugin gulp-dotdot.
- simple, quick, easy
- all the tools (plugins) for front end development
- great API for writing plugins
- does NOT manage dependencies