Presented by Jason Lengstorf of Copter Labs
Twitter: @jlengstorf | Email: jason@copterlabs.com
A task runner (or build system) is a script that executes a list of actions, typically using one or more plugins.
We have a tendency to be:
(It’s what they’re built for.)
LESS, SCSS, CoffeeScript, etc.
Such as CSS and JavaScript.
Using JSHint, JSLint, etc.
For local development.
Refresh the browser when files change.
Output templates to static HTML, etc.
Reduce the size of JPGs, PNGs, etc.
Update package.json, bower.json, the Git version...
If you can do it with code, it can be a task.
App Source Code: http://git.io/2JuyAA
App Source Code: http://git.io/2JuyAA
Use npm to install grunt-cli:
npm install grunt-cli --save
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
// 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
// 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'
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/* 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);
}
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'
}
},
.spinning-icon {
animation: spinme 2s infinite ease-in-out;
}
@keyframes spinme {
0% { transform: rotate(0deg); }
50% { transform: rotate(360deg); }
100% { transform: rotate(0deg); }
}
(Has anyone ever done this?)
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 testuncssInstall 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:testpassTo 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
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.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);
});
}
}
}
},
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
}
}
},
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
}
}
},
// Compiles LESS/JS and checks for todos
grunt.registerTask('default', [
'less:dev',
'autoprefixer:dev',
'jshint:dev',
'uglify:scripts'
]);
This is outside of grunt.initConfig().
// Starts a server and runs nodemon and watch using concurrent
grunt.registerTask('server', [ 'concurrent:dev' ]);
This is outside of grunt.initConfig().
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// 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
// 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
// Watches back-end files for changes, restarts the server
gulp.task('nodemon', function(){
nodemon({ script: 'server.js' });
});
Plugins: gulp-nodemon
// 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)
gulp.task('default', [ 'watch' ]);
So we can start with just gulp in the command line.
Presented by Jason Lengstorf of Copter Labs
Twitter: @jlengstorf | Email: jason@copterlabs.com