SlideShare ist ein Scribd-Unternehmen logo
1 von 74
Downloaden Sie, um offline zu lesen
MASTERING GRUNT
S p e n c e r H a n d l e y
ABOUT ME
S P E N C E R H A N D L E Y
Mastering Grunt
VIDEO SERIES
@spencer414
www.spencerhand.ly
GRUNT BASICS
Minifying
Uglifying
Concatenating
Linting
CDNifying
Image Optimization
Replacing Compiling StylusWatch/Live Reload
much more…
USED BY
*shameless plug
REASONS TO USE IT
• Huge Community
• Strong Adoption
• Valuable Resume Boost
• Highly in Demand
• Easy and, dare I say fun to use
• All the cool kids use it
PLUGINS
4000+ today
GRUNTFILE
'use strict';
module.exports = function (grunt) {
require('jit-grunt')(grunt, {
});
var appConfig = {
app: require('./bower.json').appPath || 'app',
dist: 'dist'
};
grunt.initConfig({
});
grunt.registerTask('default', [
]);
};
INIT CONFIG
grunt.initConfig({
concat: {
foo: {
// concat task "foo" target options and files go here.
},
bar: {
// concat task "bar" target options and files go here.
},
},
uglify: {
bar: {
// uglify task "bar" target options and files go here.
},
},
});
When you run a task, Grunt looks here
for it’s configuration.
INIT CONFIG
Multi-tasks can have multiple
configurations, defined using
arbitrarily named "targets."
gruntjs.com
concat: {
foo: {
// You could run this with concat:foo
},
bar: {
// You could run this with concat:bar
},
}
INIT CONFIG
Multi-tasks can have multiple
configurations, defined using
arbitrarily named "targets."
gruntjs.com
concat: {
foo: {
// You could run this with concat:foo
},
bar: {
// You could run this with concat:bar
},
}
Simply running concat will integrate through all targets
TASK CONFIGURATION:
OPTIONS
Inside a task configuration, an options property
may be specified to override built-in defaults.
You can also pass options to each target.
concat: {
options: {
// Task-level options may go here, overriding task defaults.
},
foo: {
options: {
// "foo" target options may go here, overriding task-level options.
},
},
bar: {
// No options specified; this target will use task-level options.
},
}
DEALING WITH FILES
COMPACT FORMAT
grunt.initConfig({
jshint: {
foo: {
src: ['src/aa.js', 'src/aaa.js']
},
},
concat: {
bar: {
src: ['src/bb.js', 'src/bbb.js'],
dest: 'dest/b.js',
},
},
});
Typically for read-only
tasks where the dest is
not needed. Like JShint
FILE OBJECT FORMAT
grunt.initConfig({
concat: {
foo: {
files: {
'dest/a.js': ['src/aa.js', 'src/aaa.js'],
'dest/a1.js': ['src/aa1.js', 'src/aaa1.js'],
},
},
bar: {
files: {
'dest/b.js': ['src/bb.js', 'src/bbb.js'],
'dest/b1.js': ['src/bb1.js', 'src/bbb1.js'],
},
},
},
});
destination: [source files]
multiple src-dest mappings per-target
FILE ARRAY FORMAT
grunt.initConfig({
concat: {
foo: {
files: [
{src: ['src/aa.js', 'src/aaa.js'], dest: 'dest/a.js'},
{src: ['src/aa1.js', 'src/aaa1.js'], dest: 'dest/a1.js'},
],
},
bar: {
files: [
{src: ['src/bb.js', 'src/bbb.js'], dest: 'dest/b/', nonull: true},
{src: ['src/bb1.js', 'src/bbb1.js'], dest: 'dest/b1/', filter: 'isFile'},
],
},
},
}); supports multiple src-dest file mappings per-target
while also allowing additional properties per mapping.
FILTER FUNCTION
grunt.initConfig({
clean: {
foo: {
src: ['tmp/**/*'],
filter: 'isFile',
},
},
});
Will clean only if the pattern matches an actual file:
Uses nodes valid fs.Stats method names
CUSTOM FILTER FUNCTION
grunt.initConfig({
clean: {
foo: {
src: ['tmp/**/*'],
filter: function(filepath) {
return (grunt.file.isDir(filepath) &&
require('fs').readdirSync(filepath).length === 0);
},
},
},
});
The following will only clean folders that are empty
You can create custom filters for specifying files
CUSTOM FILTER FUNCTION
grunt.initConfig({
clean: {
foo: {
src: ['tmp/**/*'],
filter: function(filepath) {
return (grunt.file.isDir(filepath) &&
require('fs').readdirSync(filepath).length === 0);
},
},
},
});
The following will only clean folders that are empty
You can create custom filters for specifying files
TANGENT TIME
GLOBBING BASICS
*
?
**
{}
!
matches any number of characters, but not /
matches a single character, but not /
matches any number of characters, including /, as long as
the only thing in a path part
allows for a comma-separated list of "or" expressions
at the beginning of a pattern will negate the match
GLOBBING EXAMPLES
// You can specify single files:
{src: 'foo/this.js', dest: ...}
// Or arrays of files:
{src: ['foo/this.js', 'foo/that.js', 'foo/
the-other.js'], dest: ...}
// Or you can generalize with a glob pattern:
{src: 'foo/th*.js', dest: ...}
// All .js files, in foo/, in alpha order:
{src: ['foo/*.js'], dest: ...}
// Here, bar.js is first, followed by the
remaining files, in alpha order:
{src: ['foo/bar.js', 'foo/*.js'], dest: ...}
MORE EXAMPLES
// This single node-glob pattern:
{src: 'foo/{a,b}*.js', dest: ...}
// Could also be written like this:
{src: ['foo/a*.js', 'foo/b*.js'], dest: ...}
// All .js files, in foo/, in alpha order:
{src: ['foo/*.js'], dest: …}
// Here, bar.js is first, followed by the
remaining files, in alpha order:
{src: ['foo/bar.js', 'foo/*.js'], dest: ...}
MORE EXAMPLES
// All files except for bar.js, in alpha order:
{src: ['foo/*.js', '!foo/bar.js'], dest: ...}
// All files in alpha order, but with bar.js at the end.
{src: ['foo/*.js', '!foo/bar.js', 'foo/bar.js'], dest: ...}
// Templates may be used in filepaths or glob patterns:
{src: ['src/<%= basename %>.js'], dest: 'build/<%= basename %>.min.js'}
// But they may also reference file lists defined elsewhere in the
config:
{src: ['foo/*.js', '<%= jshint.all.src %>'], dest: ...}
TEMPLATES IN GLOBS
// Templates may be used in filepaths or glob patterns:
{src: ['src/<%= basename %>.js'], dest: 'build/<%= basename %>.min.js'}
// But they may also reference file lists defined elsewhere in the
config:
{src: ['foo/*.js', '<%= jshint.all.src %>'], dest: ...}
<% %> are delimiters to specify templates
Additionally, grunt and its methods are available inside
templates, eg. <%= grunt.template.today('yyyy-mm-dd') %>.
grunt.initConfig({
uglify: {
static_mappings: {
// Because these src-dest file mappings are manually specified, every
// time a new file is added or removed, the Gruntfile has to be updated.
files: [
{src: 'lib/a.js', dest: 'build/a.min.js'},
{src: 'lib/b.js', dest: 'build/b.min.js'},
{src: 'lib/subdir/c.js', dest: 'build/subdir/c.min.js'},
{src: 'lib/subdir/d.js', dest: 'build/subdir/d.min.js'},
],
},
dynamic_mappings: {
// Grunt will search for "**/*.js" under "lib/" when the "uglify" task
// runs and build the appropriate src-dest file mappings then, so you
// don't need to update the Gruntfile when files are added or removed.
files: [
{
expand: true, // Enable dynamic expansion. Must be set to enable these options
cwd: 'lib/', // Src matches are relative to this path.
src: ['**/*.js'], // Actual pattern(s) to match.
dest: 'build/', // Destination path prefix.
ext: '.min.js', // Dest filepaths will have this extension.
extDot: 'first' // Extensions in filenames begin after the first dot
},
],
},
},
});
DYNAMIC MAPPING
Enable dynamic expansion.
TEMPLATES EXAMPLE
grunt.initConfig({
concat: {
sample: {
options: {
banner: '/* <%= baz %> */n', // '/* abcde */n'
},
src: ['<%= qux %>', 'baz/*.js'], // [['foo/*.js', 'bar/*.js'],
'baz/*.js']
dest: 'build/<%= baz %>.js', // 'build/abcde.js'
},
},
// Arbitrary properties used in task configuration templates.
foo: 'c',
bar: 'b<%= foo %>d', // 'bcd'
baz: 'a<%= bar %>e', // 'abcde'
qux: ['foo/*.js', 'bar/*.js'],
});
IMPORTING EXTERNAL DATA
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
uglify: {
options: {
banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */n'
},
dist: {
src: 'src/<%= pkg.name %>.js',
dest: 'dist/<%= pkg.name %>.min.js'
}
}
});
Here project metadata is imported into the
Grunt config from a package.json file,
CREATING TASKS
grunt.registerTask(taskName, [description, ] taskList)
SIMPLE EXAMPLE
grunt.registerTask('default', ['jshint', 'qunit', 'concat']);
Task Arguments
grunt.registerTask('dist', ['concat:dist', 'uglify:dist']);
Here we create a task called dist
passing dist as our target
properties on each task.
Task Arguments
grunt.registerTask('dist', ['concat:dist', 'uglify:dist']);
Here we create a task called dist
passing dist as our target
properties on each task.
MULTI TASKS
grunt.registerMultiTask(taskName, [description, ] taskFunction)
MULTI TASKS
grunt.initConfig({
log: {
foo: [1, 2, 3],
bar: 'hello world',
baz: false
}
});
grunt.registerMultiTask('log', 'Log stuff.', function() {
grunt.log.writeln(this.target + ': ' + this.data);
});
What would happen if we ran grunt log?
foo: [1, 2, 3]
bar: 'hello world’
baz: false
What would happen if we ran grunt log?
foo: [1, 2, 3]
bar: 'hello world’
baz: false
What would happen if we ran grunt log?
When a basic task is run, Grunt doesn't
look at the configuration or environment
—it just runs the specified task function,
passing any specified colon-separated
arguments in as function arguments.
gruntjs.com
grunt.registerTask('foo', 'A sample task that logs stuff.',
function(arg1, arg2) {
if (arguments.length === 0) {
grunt.log.writeln(this.name + ", no args");
} else {
grunt.log.writeln(this.name + ", " + arg1 + " " + arg2);
}
});
This example task logs foo, testing 123
if Grunt is run via grunt foo:testing:123.
If the task is run without arguments as
grunt foo the task logs foo, no args.
grunt.registerTask('foo', 'My "foo" task.', function() {
// Enqueue "bar" and "baz" tasks, to run after "foo"
finishes, in-order.
grunt.task.run('bar', 'baz');
// Or:
grunt.task.run(['bar', 'baz']);
});
CUSTOM TASKS
If your tasks don't follow the "multi task" structure,
use a custom task.
grunt.registerTask('asyncfoo', 'My "asyncfoo" task.',
function() {
// Force task into async mode and grab a handle to the
"done" function.
var done = this.async();
// Run some sync stuff.
grunt.log.writeln('Processing task...');
// And some async stuff.
setTimeout(function() {
grunt.log.writeln('All done!');
done();
}, 1000);
});
CUSTOM TASKS
Example of an asynchronous Task
grunt.registerTask('asyncfoo', 'My "asyncfoo" task.',
function() {
// Force task into async mode and grab a handle to the
"done" function.
var done = this.async();
// Run some sync stuff.
grunt.log.writeln('Processing task...');
// And some async stuff.
setTimeout(function() {
grunt.log.writeln('All done!');
done();
}, 1000);
});
CUSTOM TASKS
Example of an asynchronous Task
Cool parts of tasks pt 1
Can reference their own name with this.name
Can fail if any errors were logged
// Fail by returning false if this task had errors
if (ifErrors) { return false; }
console.log(this.name)
Cool parts of tasks pt 2
Tasks can be dependent on the successful execution of other tasks.
grunt.registerTask('foo', 'My "foo" task.', function() {
return false;
});
grunt.registerTask('bar', 'My "bar" task.', function() {
// Fail task if "foo" task failed or never ran.
grunt.task.requires('foo');
// This code executes if the "foo" task ran successfully.
grunt.log.writeln('Hello, world.');
});
Tasks can access configuration properties.
grunt.registerTask('foo', 'My "foo" task.', function() {
// Log the property value. Returns null if the property is
undefined.
grunt.log.writeln('The meta.name property is: ' +
grunt.config('meta.name'));
// Also logs the property value. Returns null if the
property is undefined.
grunt.log.writeln('The meta.name property is: ' +
grunt.config(['meta', 'name']));
});
Cool parts of tasks pt 3
LET’S DIG INTO AN EXAMPLE
SHARED CHAT
https://tlk.io/gruntdemo
SETUP
npm install -g grunt-cli
npm install -g bower
https://nodejs.org/download/
Install Node
Install Grunt CLI and Bower
DEMO APP
https://github.com/spencer48/Grunt-Demo
Fork this on Git Hub
then…
git clone https://github.com/YOURUSERNAME/Grunt-Demo
INSTALL
npm install
bower install
ARCHITECTURE
app/
images/
scripts/
controllers/
services/
app.js
styles/
views/
index.html
bower_components/
node_modules/
test/
.tmp/
.saas-cache/
.bowerrc
bower.json
Gruntfile.js
package.json
README.md
GRUNTFILE
'use strict';
module.exports = function (grunt) {
require('jit-grunt')(grunt, {
});
var appConfig = {
app: require('./bower.json').appPath || 'app',
dist: 'dist'
};
grunt.initConfig({
});
grunt.registerTask('default', [
]);
};
INIT CONFIG
grunt.initConfig({
concat: {
foo: {
// concat task "foo" target options and files go here.
},
bar: {
// concat task "bar" target options and files go here.
},
},
uglify: {
bar: {
// uglify task "bar" target options and files go here.
},
},
});
When you run a task, Grunt looks here
for it’s configuration.
ADDING DEPENDENCIES
{
"name": "gruntdemo",
"devDependencies": {
"grunt": "^0.4.5",
"grunt-concurrent": "^1.0.0",
"grunt-contrib-connect": "^0.9.0",
"grunt-contrib-watch": "^0.6.1",
"grunt-newer": "^1.1.0",
"jit-grunt": "^0.9.1",
"jshint-stylish": "^1.0.0",
"time-grunt": "^1.0.0"
},
"engines": {
"node": ">=0.10.0"
}
}
…then npm install
package.json
SETTING UP CONNECT
connect: {
options: {
port: 9000,
hostname: 'localhost',
livereload: 35729
},
livereload: {
options: {
open: true,
middleware: function (connect) {
return [
connect.static('.tmp'),
connect().use(
'/bower_components',
connect.static('./bower_components')
),
connect().use(
'/app/styles',
connect.static('./app/styles')
),
connect.static(appConfig.app)
];
}
}
},
}
SETTING UP WATCHwatch: {
bower: {
files: ['bower.json'],
tasks: ['wiredep']
},
js: {
files: ['<%= yeoman.app %>/scripts/{,*/}*.js'],
tasks: ['newer:jshint:all'],
options: {
livereload: '<%= connect.options.livereload %>'
}
},
compass: {
files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
tasks: ['compass:server', 'autoprefixer:server']
},
gruntfile: {
files: ['Gruntfile.js']
},
livereload: {
options: {
livereload: '<%= connect.options.livereload %>'
},
files: [
'<%= yeoman.app %>/{,*/}*.html',
'.tmp/styles/{,*/}*.css',
'<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
]
}
}
CONFIGURING JSHINT
jshint: {
options: {
jshintrc: '.jshintrc',
reporter: require('jshint-stylish')
},
all: {
src: [
'Gruntfile.js',
'<%= yeoman.app %>/scripts/{,*/}*.js'
]
},
test: {
options: {
jshintrc: 'test/.jshintrc'
},
src: ['test/spec/{,*/}*.js']
}
}
{
"bitwise": true,
"browser": true,
"curly": true,
"eqeqeq": true,
"esnext": true,
"latedef": true,
"noarg": true,
"node": true,
"strict": true,
"undef": true,
"unused": true,
"globals": {
"angular": false
}
}
.jshintrcGruntFile.js
CONFIGURING CLEAN
clean: {
dist: {
files: [{
dot: true,
src: [
'.tmp',
'<%= yeoman.dist %>/{,*/}*',
'!<%= yeoman.dist %>/.git{,*/}*'
]
}]
},
server: '.tmp'
}
GruntFile.js
CONFIGURING WIREDEP
wiredep: {
app: {
src: ['<%= yeoman.app %>/index.html'],
ignorePath: /..//
},
test: {
devDependencies: true,
src: '<%= karma.unit.configFile %>',
ignorePath: /..//,
fileTypes:{
js: {
block: /(([st]*)/{2}s*?bower:s*?(S*))(n|r|.)*?(/{2}s*endbower)/gi,
detect: {
js: /'(.*.js)'/gi
},
replace: {
js: ''{{filePath}}','
}
}
}
},
sass: {
src: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
ignorePath: /(../){1,2}bower_components//
}
},
GruntFile.js
CONFIGURING COMPASS
compass: {
options: {
sassDir: '<%= yeoman.app %>/styles',
cssDir: '.tmp/styles',
generatedImagesDir: '.tmp/images/generated',
imagesDir: '<%= yeoman.app %>/images',
javascriptsDir: '<%= yeoman.app %>/scripts',
fontsDir: '<%= yeoman.app %>/styles/fonts',
importPath: './bower_components',
httpImagesPath: '/images',
httpGeneratedImagesPath: '/images/generated',
httpFontsPath: '/styles/fonts',
relativeAssets: false,
assetCacheBuster: false,
raw: 'Sass::Script::Number.precision = 10n'
},
dist: {
options: {
generatedImagesDir: '<%= yeoman.dist %>/images/generated'
}
},
server: {
options: {
sourcemap: true
}
}
}
GruntFile.js
CONFIGURING CLEAN
clean: {
dist: {
files: [{
dot: true,
src: [
'.tmp',
'<%= yeoman.dist %>/{,*/}*',
'!<%= yeoman.dist %>/.git{,*/}*'
]
}]
},
server: '.tmp'
}
GruntFile.js
CONFIGURING AUTOPREFIXER
// Add vendor prefixed styles
autoprefixer: {
options: {
browsers: ['last 1 version']
},
server: {
options: {
map: true,
},
files: [{
expand: true,
cwd: '.tmp/styles/',
src: '{,*/}*.css',
dest: '.tmp/styles/'
}]
},
dist: {
files: [{
expand: true,
cwd: '.tmp/styles/',
src: '{,*/}*.css',
dest: '.tmp/styles/'
}]
}
}
GruntFile.js
CONFIGURING FILE REV
// Renames files for browser caching purposes
filerev: {
dist: {
src: [
'<%= yeoman.dist %>/scripts/{,*/}*.js',
'<%= yeoman.dist %>/styles/{,*/}*.css',
'<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
'<%= yeoman.dist %>/styles/fonts/*'
]
}
},
GruntFile.js
CONFIGURING USEMIN
useminPrepare: {
html: '<%= yeoman.app %>/index.html',
options: {
dest: '<%= yeoman.dist %>',
flow: {
html: {
steps: {
js: ['concat', 'uglifyjs'],
css: ['cssmin']
},
post: {}
}
}
}
},
GruntFile.js
CONFIGURING USEMIN
// Performs rewrites based on filerev and the useminPrepare configuration
usemin: {
html: ['<%= yeoman.dist %>/{,*/}*.html'],
css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
js: ['<%= yeoman.dist %>/scripts/{,*/}*.js'],
options: {
assetsDirs: [
'<%= yeoman.dist %>',
'<%= yeoman.dist %>/images',
'<%= yeoman.dist %>/styles'
],
patterns: {
js: [[/(images/[^''""]*.(png|jpg|jpeg|gif|webp|svg))/g,
'Replacing references to images']]
}
}
},
GruntFile.js
CONFIGURING IMG/SVG MIN
imagemin: {
dist: {
files: [{
expand: true,
cwd: '<%= yeoman.app %>/images',
src: '{,*/}*.{png,jpg,jpeg,gif}',
dest: '<%= yeoman.dist %>/images'
}]
}
},
svgmin: {
dist: {
files: [{
expand: true,
cwd: '<%= yeoman.app %>/images',
src: '{,*/}*.svg',
dest: '<%= yeoman.dist %>/images'
}]
}
},
GruntFile.js
CONFIGURING HTML MIN
htmlmin: {
dist: {
options: {
collapseWhitespace: true,
conservativeCollapse: true,
collapseBooleanAttributes: true,
removeCommentsFromCDATA: true
},
files: [{
expand: true,
cwd: '<%= yeoman.dist %>',
src: ['*.html'],
dest: '<%= yeoman.dist %>'
}]
}
},
GruntFile.js
CONFIGURING NGTEMPATES
ngtemplates: {
dist: {
options: {
module: 'gruntdemoApp',
htmlmin: '<%= htmlmin.dist.options %>',
usemin: 'scripts/scripts.js'
},
cwd: '<%= yeoman.app %>',
src: 'views/{,*/}*.html',
dest: '.tmp/templateCache.js'
}
},
GruntFile.js
CONFIGURING NGANNOTATE
ngAnnotate: {
dist: {
files: [{
expand: true,
cwd: '.tmp/concat/scripts',
src: '*.js',
dest: '.tmp/concat/scripts'
}]
}
}
GruntFile.js
CONFIGURING NGANNOTATE
copy: {
dist: {
files: [{
expand: true,
dot: true,
cwd: '<%= yeoman.app %>',
dest: '<%= yeoman.dist %>',
src: [
'*.{ico,png,txt}',
'.htaccess',
'*.html',
'images/{,*/}*.{webp}',
'styles/fonts/{,*/}*.*'
]
}, {
expand: true,
cwd: '.tmp/images',
dest: '<%= yeoman.dist %>/images',
src: ['generated/*']
}, {
expand: true,
cwd: '.',
src: 'bower_components/bootstrap-sass-official/assets/fonts/bootstrap/*',
dest: '<%= yeoman.dist %>'
}]
},
styles: {
expand: true,
cwd: '<%= yeoman.app %>/styles',
dest: '.tmp/styles/',
src: '{,*/}*.css'
}
},
GruntFile.js
Sorry for the tiny font :(
CONFIGURING KARMA
karma: {
unit: {
configFile: 'test/karma.conf.js',
singleRun: true
}
}
GruntFile.js
CONFIGURING CONCURRENT
// Run some tasks in parallel to speed up the build process
concurrent: {
server: [
'compass:server'
],
test: [
'compass'
],
dist: [
'compass:dist',
'imagemin',
'svgmin'
]
},
GruntFile.js
TASK EXAMPLES
grunt.registerTask('build', [
'clean:dist',
'wiredep',
'useminPrepare',
'concurrent:dist',
'autoprefixer',
'ngtemplates',
'concat',
'ngAnnotate',
'copy:dist',
'cdnify',
'cssmin',
'uglify',
'filerev',
'usemin',
'htmlmin'
]);
BUILD
grunt.registerTask('test', [
'clean:server',
'wiredep',
'concurrent:test',
'autoprefixer',
'connect:test',
'karma'
]);
TEST
TASK EXAMPLES
grunt.registerTask('serve', 'Compile then start a connect web
server', function (target) {
if (target === 'dist') {
return grunt.task.run([‘build', 'connect:dist:keepalive']);
}
grunt.task.run([
'clean:server',
'wiredep',
'concurrent:server',
'autoprefixer:server',
'connect:livereload',
'watch'
]);
});
SERVE
THANKS
QUESTIONS?
S P E N C E R H A N D L E Y
Mastering Grunt
VIDEO SERIES
@spencer414
www.spencerhand.ly
www.podclear.com

Weitere ähnliche Inhalte

Was ist angesagt?

Advanced WordPress Development Environments
Advanced WordPress Development EnvironmentsAdvanced WordPress Development Environments
Advanced WordPress Development Environments
Beau Lebens
 
[jqconatx] Adaptive Images for Responsive Web Design
[jqconatx] Adaptive Images for Responsive Web Design[jqconatx] Adaptive Images for Responsive Web Design
[jqconatx] Adaptive Images for Responsive Web Design
Christopher Schmitt
 
Browser Wars Episode 1: The Phantom Menace
Browser Wars Episode 1: The Phantom MenaceBrowser Wars Episode 1: The Phantom Menace
Browser Wars Episode 1: The Phantom Menace
Nicholas Zakas
 
HTML5 Web Workers-unleashed
HTML5 Web Workers-unleashedHTML5 Web Workers-unleashed
HTML5 Web Workers-unleashed
Peter Lubbers
 

Was ist angesagt? (20)

Advanced WordPress Development Environments
Advanced WordPress Development EnvironmentsAdvanced WordPress Development Environments
Advanced WordPress Development Environments
 
JavaScript performance patterns
JavaScript performance patternsJavaScript performance patterns
JavaScript performance patterns
 
WordPress as the Backbone(.js)
WordPress as the Backbone(.js)WordPress as the Backbone(.js)
WordPress as the Backbone(.js)
 
JavaScript Performance Patterns
JavaScript Performance PatternsJavaScript Performance Patterns
JavaScript Performance Patterns
 
High Performance Social Plugins
High Performance Social PluginsHigh Performance Social Plugins
High Performance Social Plugins
 
Progressive Downloads and Rendering - take #2
Progressive Downloads and Rendering - take #2Progressive Downloads and Rendering - take #2
Progressive Downloads and Rendering - take #2
 
Let Grunt do the work, focus on the fun! [Open Web Camp 2013]
Let Grunt do the work, focus on the fun! [Open Web Camp 2013]Let Grunt do the work, focus on the fun! [Open Web Camp 2013]
Let Grunt do the work, focus on the fun! [Open Web Camp 2013]
 
Angular2 ecosystem
Angular2 ecosystemAngular2 ecosystem
Angular2 ecosystem
 
SocketStream
SocketStreamSocketStream
SocketStream
 
Choosing a Javascript Framework
Choosing a Javascript FrameworkChoosing a Javascript Framework
Choosing a Javascript Framework
 
CodeIgniter PHP MVC Framework
CodeIgniter PHP MVC FrameworkCodeIgniter PHP MVC Framework
CodeIgniter PHP MVC Framework
 
Instant and offline apps with Service Worker
Instant and offline apps with Service WorkerInstant and offline apps with Service Worker
Instant and offline apps with Service Worker
 
[jqconatx] Adaptive Images for Responsive Web Design
[jqconatx] Adaptive Images for Responsive Web Design[jqconatx] Adaptive Images for Responsive Web Design
[jqconatx] Adaptive Images for Responsive Web Design
 
Browser Wars Episode 1: The Phantom Menace
Browser Wars Episode 1: The Phantom MenaceBrowser Wars Episode 1: The Phantom Menace
Browser Wars Episode 1: The Phantom Menace
 
Ruby MVC from scratch with Rack
Ruby MVC from scratch with RackRuby MVC from scratch with Rack
Ruby MVC from scratch with Rack
 
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...
 
Enough with the JavaScript already!
Enough with the JavaScript already!Enough with the JavaScript already!
Enough with the JavaScript already!
 
HTML5 Web Workers-unleashed
HTML5 Web Workers-unleashedHTML5 Web Workers-unleashed
HTML5 Web Workers-unleashed
 
Rails Girls: Programming, Web Applications and Ruby on Rails
Rails Girls: Programming, Web Applications and Ruby on RailsRails Girls: Programming, Web Applications and Ruby on Rails
Rails Girls: Programming, Web Applications and Ruby on Rails
 
Building web framework with Rack
Building web framework with RackBuilding web framework with Rack
Building web framework with Rack
 

Andere mochten auch

An Introduction to AngularJs Unittesting
An Introduction to AngularJs UnittestingAn Introduction to AngularJs Unittesting
An Introduction to AngularJs Unittesting
Inthra onsap
 

Andere mochten auch (15)

An Introduction to AngularJs Unittesting
An Introduction to AngularJs UnittestingAn Introduction to AngularJs Unittesting
An Introduction to AngularJs Unittesting
 
Angular js, Yeomon & Grunt
Angular js, Yeomon & GruntAngular js, Yeomon & Grunt
Angular js, Yeomon & Grunt
 
Wrangling the WordPress Template Hierarchy Like a Boss
Wrangling the WordPress Template Hierarchy Like a BossWrangling the WordPress Template Hierarchy Like a Boss
Wrangling the WordPress Template Hierarchy Like a Boss
 
GruntJS + Wordpress
GruntJS + WordpressGruntJS + Wordpress
GruntJS + Wordpress
 
Grunt js and WordPress
Grunt js and WordPressGrunt js and WordPress
Grunt js and WordPress
 
WordPress Theme Development Workflow with Node.js, Ruby, Sass, Bower and Grunt
WordPress Theme Development Workflow with Node.js, Ruby, Sass, Bower and GruntWordPress Theme Development Workflow with Node.js, Ruby, Sass, Bower and Grunt
WordPress Theme Development Workflow with Node.js, Ruby, Sass, Bower and Grunt
 
Metadata and me
Metadata and meMetadata and me
Metadata and me
 
A Quick and Dirty D3.js Tutorial
A Quick and Dirty D3.js TutorialA Quick and Dirty D3.js Tutorial
A Quick and Dirty D3.js Tutorial
 
Come migliorare le performance di WordPress con il Visual Composer
Come migliorare le performance di WordPress con il Visual ComposerCome migliorare le performance di WordPress con il Visual Composer
Come migliorare le performance di WordPress con il Visual Composer
 
Javascript testing: tools of the trade
Javascript testing: tools of the tradeJavascript testing: tools of the trade
Javascript testing: tools of the trade
 
Using Composer to create manageable WordPress websites
Using Composer to create manageable WordPress websitesUsing Composer to create manageable WordPress websites
Using Composer to create manageable WordPress websites
 
WordPress Database: What's behind those 12 tables
WordPress Database: What's behind those 12 tablesWordPress Database: What's behind those 12 tables
WordPress Database: What's behind those 12 tables
 
JavaScript Test-Driven Development with Jasmine 2.0 and Karma
JavaScript Test-Driven Development with Jasmine 2.0 and Karma JavaScript Test-Driven Development with Jasmine 2.0 and Karma
JavaScript Test-Driven Development with Jasmine 2.0 and Karma
 
WordPress Template Hierarchy
WordPress Template HierarchyWordPress Template Hierarchy
WordPress Template Hierarchy
 
Getting Started With Grunt for WordPress Development
Getting Started With Grunt for WordPress DevelopmentGetting Started With Grunt for WordPress Development
Getting Started With Grunt for WordPress Development
 

Ähnlich wie Mastering Grunt

On secure application of PHP wrappers
On secure application  of PHP wrappersOn secure application  of PHP wrappers
On secure application of PHP wrappers
Positive Hack Days
 
Node.js basics
Node.js basicsNode.js basics
Node.js basics
Ben Lin
 
Functional programming using underscorejs
Functional programming using underscorejsFunctional programming using underscorejs
Functional programming using underscorejs
偉格 高
 
Couch db 浅漫游.
Couch db 浅漫游.Couch db 浅漫游.
Couch db 浅漫游.
shyboyzk
 
Easy deployment & management of cloud apps
Easy deployment & management of cloud appsEasy deployment & management of cloud apps
Easy deployment & management of cloud apps
David Cunningham
 
Deploying Rails Applications with Capistrano
Deploying Rails Applications with CapistranoDeploying Rails Applications with Capistrano
Deploying Rails Applications with Capistrano
Almir Mendes
 
case3h231diamond.gifcase3h231energy.jpgcase3h231moder.docx
case3h231diamond.gifcase3h231energy.jpgcase3h231moder.docxcase3h231diamond.gifcase3h231energy.jpgcase3h231moder.docx
case3h231diamond.gifcase3h231energy.jpgcase3h231moder.docx
tidwellveronique
 

Ähnlich wie Mastering Grunt (20)

On secure application of PHP wrappers
On secure application  of PHP wrappersOn secure application  of PHP wrappers
On secure application of PHP wrappers
 
Get Grulping with JavaScript Task Runners (Matt Gifford)
Get Grulping with JavaScript Task Runners (Matt Gifford)Get Grulping with JavaScript Task Runners (Matt Gifford)
Get Grulping with JavaScript Task Runners (Matt Gifford)
 
Node.js basics
Node.js basicsNode.js basics
Node.js basics
 
Txjs
TxjsTxjs
Txjs
 
Functional programming using underscorejs
Functional programming using underscorejsFunctional programming using underscorejs
Functional programming using underscorejs
 
Couch db 浅漫游.
Couch db 浅漫游.Couch db 浅漫游.
Couch db 浅漫游.
 
Automating WordPress Theme Development
Automating WordPress Theme DevelopmentAutomating WordPress Theme Development
Automating WordPress Theme Development
 
Easy deployment & management of cloud apps
Easy deployment & management of cloud appsEasy deployment & management of cloud apps
Easy deployment & management of cloud apps
 
Practical Chef and Capistrano for Your Rails App
Practical Chef and Capistrano for Your Rails AppPractical Chef and Capistrano for Your Rails App
Practical Chef and Capistrano for Your Rails App
 
Javascript is your (Auto)mate
Javascript is your (Auto)mateJavascript is your (Auto)mate
Javascript is your (Auto)mate
 
Rush, a shell that will yield to you
Rush, a shell that will yield to youRush, a shell that will yield to you
Rush, a shell that will yield to you
 
Zero-config JavaScript apps with RaveJS -- SVCC fall 2014
Zero-config JavaScript apps with RaveJS -- SVCC fall 2014Zero-config JavaScript apps with RaveJS -- SVCC fall 2014
Zero-config JavaScript apps with RaveJS -- SVCC fall 2014
 
Deploying Rails Applications with Capistrano
Deploying Rails Applications with CapistranoDeploying Rails Applications with Capistrano
Deploying Rails Applications with Capistrano
 
Build Web Apps using Node.js
Build Web Apps using Node.jsBuild Web Apps using Node.js
Build Web Apps using Node.js
 
2017-03-11 02 Денис Нелюбин. Docker & Ansible - лучшие друзья DevOps
2017-03-11 02 Денис Нелюбин. Docker & Ansible - лучшие друзья DevOps2017-03-11 02 Денис Нелюбин. Docker & Ansible - лучшие друзья DevOps
2017-03-11 02 Денис Нелюбин. Docker & Ansible - лучшие друзья DevOps
 
Workflow para desenvolvimento Web & Mobile usando grunt.js
Workflow para desenvolvimento Web & Mobile usando grunt.jsWorkflow para desenvolvimento Web & Mobile usando grunt.js
Workflow para desenvolvimento Web & Mobile usando grunt.js
 
Intro to Ember.JS 2016
Intro to Ember.JS 2016Intro to Ember.JS 2016
Intro to Ember.JS 2016
 
DrupalCon jQuery
DrupalCon jQueryDrupalCon jQuery
DrupalCon jQuery
 
case3h231diamond.gifcase3h231energy.jpgcase3h231moder.docx
case3h231diamond.gifcase3h231energy.jpgcase3h231moder.docxcase3h231diamond.gifcase3h231energy.jpgcase3h231moder.docx
case3h231diamond.gifcase3h231energy.jpgcase3h231moder.docx
 
Couchdb
CouchdbCouchdb
Couchdb
 

Kürzlich hochgeladen

Call for Papers - African Journal of Biological Sciences, E-ISSN: 2663-2187, ...
Call for Papers - African Journal of Biological Sciences, E-ISSN: 2663-2187, ...Call for Papers - African Journal of Biological Sciences, E-ISSN: 2663-2187, ...
Call for Papers - African Journal of Biological Sciences, E-ISSN: 2663-2187, ...
Christo Ananth
 
Call Now ≽ 9953056974 ≼🔝 Call Girls In New Ashok Nagar ≼🔝 Delhi door step de...
Call Now ≽ 9953056974 ≼🔝 Call Girls In New Ashok Nagar  ≼🔝 Delhi door step de...Call Now ≽ 9953056974 ≼🔝 Call Girls In New Ashok Nagar  ≼🔝 Delhi door step de...
Call Now ≽ 9953056974 ≼🔝 Call Girls In New Ashok Nagar ≼🔝 Delhi door step de...
9953056974 Low Rate Call Girls In Saket, Delhi NCR
 
Call for Papers - Educational Administration: Theory and Practice, E-ISSN: 21...
Call for Papers - Educational Administration: Theory and Practice, E-ISSN: 21...Call for Papers - Educational Administration: Theory and Practice, E-ISSN: 21...
Call for Papers - Educational Administration: Theory and Practice, E-ISSN: 21...
Christo Ananth
 
Call Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
Call Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort ServiceCall Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
Call Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
9953056974 Low Rate Call Girls In Saket, Delhi NCR
 

Kürzlich hochgeladen (20)

Extrusion Processes and Their Limitations
Extrusion Processes and Their LimitationsExtrusion Processes and Their Limitations
Extrusion Processes and Their Limitations
 
Call Girls Walvekar Nagar Call Me 7737669865 Budget Friendly No Advance Booking
Call Girls Walvekar Nagar Call Me 7737669865 Budget Friendly No Advance BookingCall Girls Walvekar Nagar Call Me 7737669865 Budget Friendly No Advance Booking
Call Girls Walvekar Nagar Call Me 7737669865 Budget Friendly No Advance Booking
 
Call for Papers - African Journal of Biological Sciences, E-ISSN: 2663-2187, ...
Call for Papers - African Journal of Biological Sciences, E-ISSN: 2663-2187, ...Call for Papers - African Journal of Biological Sciences, E-ISSN: 2663-2187, ...
Call for Papers - African Journal of Biological Sciences, E-ISSN: 2663-2187, ...
 
Top Rated Pune Call Girls Budhwar Peth ⟟ 6297143586 ⟟ Call Me For Genuine Se...
Top Rated  Pune Call Girls Budhwar Peth ⟟ 6297143586 ⟟ Call Me For Genuine Se...Top Rated  Pune Call Girls Budhwar Peth ⟟ 6297143586 ⟟ Call Me For Genuine Se...
Top Rated Pune Call Girls Budhwar Peth ⟟ 6297143586 ⟟ Call Me For Genuine Se...
 
Roadmap to Membership of RICS - Pathways and Routes
Roadmap to Membership of RICS - Pathways and RoutesRoadmap to Membership of RICS - Pathways and Routes
Roadmap to Membership of RICS - Pathways and Routes
 
ONLINE FOOD ORDER SYSTEM PROJECT REPORT.pdf
ONLINE FOOD ORDER SYSTEM PROJECT REPORT.pdfONLINE FOOD ORDER SYSTEM PROJECT REPORT.pdf
ONLINE FOOD ORDER SYSTEM PROJECT REPORT.pdf
 
The Most Attractive Pune Call Girls Manchar 8250192130 Will You Miss This Cha...
The Most Attractive Pune Call Girls Manchar 8250192130 Will You Miss This Cha...The Most Attractive Pune Call Girls Manchar 8250192130 Will You Miss This Cha...
The Most Attractive Pune Call Girls Manchar 8250192130 Will You Miss This Cha...
 
Call Now ≽ 9953056974 ≼🔝 Call Girls In New Ashok Nagar ≼🔝 Delhi door step de...
Call Now ≽ 9953056974 ≼🔝 Call Girls In New Ashok Nagar  ≼🔝 Delhi door step de...Call Now ≽ 9953056974 ≼🔝 Call Girls In New Ashok Nagar  ≼🔝 Delhi door step de...
Call Now ≽ 9953056974 ≼🔝 Call Girls In New Ashok Nagar ≼🔝 Delhi door step de...
 
Call for Papers - Educational Administration: Theory and Practice, E-ISSN: 21...
Call for Papers - Educational Administration: Theory and Practice, E-ISSN: 21...Call for Papers - Educational Administration: Theory and Practice, E-ISSN: 21...
Call for Papers - Educational Administration: Theory and Practice, E-ISSN: 21...
 
Call Girls Pimpri Chinchwad Call Me 7737669865 Budget Friendly No Advance Boo...
Call Girls Pimpri Chinchwad Call Me 7737669865 Budget Friendly No Advance Boo...Call Girls Pimpri Chinchwad Call Me 7737669865 Budget Friendly No Advance Boo...
Call Girls Pimpri Chinchwad Call Me 7737669865 Budget Friendly No Advance Boo...
 
The Most Attractive Pune Call Girls Budhwar Peth 8250192130 Will You Miss Thi...
The Most Attractive Pune Call Girls Budhwar Peth 8250192130 Will You Miss Thi...The Most Attractive Pune Call Girls Budhwar Peth 8250192130 Will You Miss Thi...
The Most Attractive Pune Call Girls Budhwar Peth 8250192130 Will You Miss Thi...
 
KubeKraft presentation @CloudNativeHooghly
KubeKraft presentation @CloudNativeHooghlyKubeKraft presentation @CloudNativeHooghly
KubeKraft presentation @CloudNativeHooghly
 
CCS335 _ Neural Networks and Deep Learning Laboratory_Lab Complete Record
CCS335 _ Neural Networks and Deep Learning Laboratory_Lab Complete RecordCCS335 _ Neural Networks and Deep Learning Laboratory_Lab Complete Record
CCS335 _ Neural Networks and Deep Learning Laboratory_Lab Complete Record
 
BSides Seattle 2024 - Stopping Ethan Hunt From Taking Your Data.pptx
BSides Seattle 2024 - Stopping Ethan Hunt From Taking Your Data.pptxBSides Seattle 2024 - Stopping Ethan Hunt From Taking Your Data.pptx
BSides Seattle 2024 - Stopping Ethan Hunt From Taking Your Data.pptx
 
Thermal Engineering Unit - I & II . ppt
Thermal Engineering  Unit - I & II . pptThermal Engineering  Unit - I & II . ppt
Thermal Engineering Unit - I & II . ppt
 
PVC VS. FIBERGLASS (FRP) GRAVITY SEWER - UNI BELL
PVC VS. FIBERGLASS (FRP) GRAVITY SEWER - UNI BELLPVC VS. FIBERGLASS (FRP) GRAVITY SEWER - UNI BELL
PVC VS. FIBERGLASS (FRP) GRAVITY SEWER - UNI BELL
 
Vivazz, Mieres Social Housing Design Spain
Vivazz, Mieres Social Housing Design SpainVivazz, Mieres Social Housing Design Spain
Vivazz, Mieres Social Housing Design Spain
 
Generative AI or GenAI technology based PPT
Generative AI or GenAI technology based PPTGenerative AI or GenAI technology based PPT
Generative AI or GenAI technology based PPT
 
UNIT-IFLUID PROPERTIES & FLOW CHARACTERISTICS
UNIT-IFLUID PROPERTIES & FLOW CHARACTERISTICSUNIT-IFLUID PROPERTIES & FLOW CHARACTERISTICS
UNIT-IFLUID PROPERTIES & FLOW CHARACTERISTICS
 
Call Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
Call Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort ServiceCall Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
Call Girls in Ramesh Nagar Delhi 💯 Call Us 🔝9953056974 🔝 Escort Service
 

Mastering Grunt

  • 1. MASTERING GRUNT S p e n c e r H a n d l e y
  • 2. ABOUT ME S P E N C E R H A N D L E Y Mastering Grunt VIDEO SERIES @spencer414 www.spencerhand.ly
  • 5. REASONS TO USE IT • Huge Community • Strong Adoption • Valuable Resume Boost • Highly in Demand • Easy and, dare I say fun to use • All the cool kids use it
  • 7. GRUNTFILE 'use strict'; module.exports = function (grunt) { require('jit-grunt')(grunt, { }); var appConfig = { app: require('./bower.json').appPath || 'app', dist: 'dist' }; grunt.initConfig({ }); grunt.registerTask('default', [ ]); };
  • 8. INIT CONFIG grunt.initConfig({ concat: { foo: { // concat task "foo" target options and files go here. }, bar: { // concat task "bar" target options and files go here. }, }, uglify: { bar: { // uglify task "bar" target options and files go here. }, }, }); When you run a task, Grunt looks here for it’s configuration.
  • 9. INIT CONFIG Multi-tasks can have multiple configurations, defined using arbitrarily named "targets." gruntjs.com concat: { foo: { // You could run this with concat:foo }, bar: { // You could run this with concat:bar }, }
  • 10. INIT CONFIG Multi-tasks can have multiple configurations, defined using arbitrarily named "targets." gruntjs.com concat: { foo: { // You could run this with concat:foo }, bar: { // You could run this with concat:bar }, } Simply running concat will integrate through all targets
  • 11. TASK CONFIGURATION: OPTIONS Inside a task configuration, an options property may be specified to override built-in defaults. You can also pass options to each target. concat: { options: { // Task-level options may go here, overriding task defaults. }, foo: { options: { // "foo" target options may go here, overriding task-level options. }, }, bar: { // No options specified; this target will use task-level options. }, }
  • 13. COMPACT FORMAT grunt.initConfig({ jshint: { foo: { src: ['src/aa.js', 'src/aaa.js'] }, }, concat: { bar: { src: ['src/bb.js', 'src/bbb.js'], dest: 'dest/b.js', }, }, }); Typically for read-only tasks where the dest is not needed. Like JShint
  • 14. FILE OBJECT FORMAT grunt.initConfig({ concat: { foo: { files: { 'dest/a.js': ['src/aa.js', 'src/aaa.js'], 'dest/a1.js': ['src/aa1.js', 'src/aaa1.js'], }, }, bar: { files: { 'dest/b.js': ['src/bb.js', 'src/bbb.js'], 'dest/b1.js': ['src/bb1.js', 'src/bbb1.js'], }, }, }, }); destination: [source files] multiple src-dest mappings per-target
  • 15. FILE ARRAY FORMAT grunt.initConfig({ concat: { foo: { files: [ {src: ['src/aa.js', 'src/aaa.js'], dest: 'dest/a.js'}, {src: ['src/aa1.js', 'src/aaa1.js'], dest: 'dest/a1.js'}, ], }, bar: { files: [ {src: ['src/bb.js', 'src/bbb.js'], dest: 'dest/b/', nonull: true}, {src: ['src/bb1.js', 'src/bbb1.js'], dest: 'dest/b1/', filter: 'isFile'}, ], }, }, }); supports multiple src-dest file mappings per-target while also allowing additional properties per mapping.
  • 16. FILTER FUNCTION grunt.initConfig({ clean: { foo: { src: ['tmp/**/*'], filter: 'isFile', }, }, }); Will clean only if the pattern matches an actual file: Uses nodes valid fs.Stats method names
  • 17. CUSTOM FILTER FUNCTION grunt.initConfig({ clean: { foo: { src: ['tmp/**/*'], filter: function(filepath) { return (grunt.file.isDir(filepath) && require('fs').readdirSync(filepath).length === 0); }, }, }, }); The following will only clean folders that are empty You can create custom filters for specifying files
  • 18. CUSTOM FILTER FUNCTION grunt.initConfig({ clean: { foo: { src: ['tmp/**/*'], filter: function(filepath) { return (grunt.file.isDir(filepath) && require('fs').readdirSync(filepath).length === 0); }, }, }, }); The following will only clean folders that are empty You can create custom filters for specifying files
  • 20. GLOBBING BASICS * ? ** {} ! matches any number of characters, but not / matches a single character, but not / matches any number of characters, including /, as long as the only thing in a path part allows for a comma-separated list of "or" expressions at the beginning of a pattern will negate the match
  • 21. GLOBBING EXAMPLES // You can specify single files: {src: 'foo/this.js', dest: ...} // Or arrays of files: {src: ['foo/this.js', 'foo/that.js', 'foo/ the-other.js'], dest: ...} // Or you can generalize with a glob pattern: {src: 'foo/th*.js', dest: ...} // All .js files, in foo/, in alpha order: {src: ['foo/*.js'], dest: ...} // Here, bar.js is first, followed by the remaining files, in alpha order: {src: ['foo/bar.js', 'foo/*.js'], dest: ...}
  • 22. MORE EXAMPLES // This single node-glob pattern: {src: 'foo/{a,b}*.js', dest: ...} // Could also be written like this: {src: ['foo/a*.js', 'foo/b*.js'], dest: ...} // All .js files, in foo/, in alpha order: {src: ['foo/*.js'], dest: …} // Here, bar.js is first, followed by the remaining files, in alpha order: {src: ['foo/bar.js', 'foo/*.js'], dest: ...}
  • 23. MORE EXAMPLES // All files except for bar.js, in alpha order: {src: ['foo/*.js', '!foo/bar.js'], dest: ...} // All files in alpha order, but with bar.js at the end. {src: ['foo/*.js', '!foo/bar.js', 'foo/bar.js'], dest: ...} // Templates may be used in filepaths or glob patterns: {src: ['src/<%= basename %>.js'], dest: 'build/<%= basename %>.min.js'} // But they may also reference file lists defined elsewhere in the config: {src: ['foo/*.js', '<%= jshint.all.src %>'], dest: ...}
  • 24. TEMPLATES IN GLOBS // Templates may be used in filepaths or glob patterns: {src: ['src/<%= basename %>.js'], dest: 'build/<%= basename %>.min.js'} // But they may also reference file lists defined elsewhere in the config: {src: ['foo/*.js', '<%= jshint.all.src %>'], dest: ...} <% %> are delimiters to specify templates Additionally, grunt and its methods are available inside templates, eg. <%= grunt.template.today('yyyy-mm-dd') %>.
  • 25. grunt.initConfig({ uglify: { static_mappings: { // Because these src-dest file mappings are manually specified, every // time a new file is added or removed, the Gruntfile has to be updated. files: [ {src: 'lib/a.js', dest: 'build/a.min.js'}, {src: 'lib/b.js', dest: 'build/b.min.js'}, {src: 'lib/subdir/c.js', dest: 'build/subdir/c.min.js'}, {src: 'lib/subdir/d.js', dest: 'build/subdir/d.min.js'}, ], }, dynamic_mappings: { // Grunt will search for "**/*.js" under "lib/" when the "uglify" task // runs and build the appropriate src-dest file mappings then, so you // don't need to update the Gruntfile when files are added or removed. files: [ { expand: true, // Enable dynamic expansion. Must be set to enable these options cwd: 'lib/', // Src matches are relative to this path. src: ['**/*.js'], // Actual pattern(s) to match. dest: 'build/', // Destination path prefix. ext: '.min.js', // Dest filepaths will have this extension. extDot: 'first' // Extensions in filenames begin after the first dot }, ], }, }, }); DYNAMIC MAPPING Enable dynamic expansion.
  • 26. TEMPLATES EXAMPLE grunt.initConfig({ concat: { sample: { options: { banner: '/* <%= baz %> */n', // '/* abcde */n' }, src: ['<%= qux %>', 'baz/*.js'], // [['foo/*.js', 'bar/*.js'], 'baz/*.js'] dest: 'build/<%= baz %>.js', // 'build/abcde.js' }, }, // Arbitrary properties used in task configuration templates. foo: 'c', bar: 'b<%= foo %>d', // 'bcd' baz: 'a<%= bar %>e', // 'abcde' qux: ['foo/*.js', 'bar/*.js'], });
  • 27. IMPORTING EXTERNAL DATA grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), uglify: { options: { banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */n' }, dist: { src: 'src/<%= pkg.name %>.js', dest: 'dist/<%= pkg.name %>.min.js' } } }); Here project metadata is imported into the Grunt config from a package.json file,
  • 30. Task Arguments grunt.registerTask('dist', ['concat:dist', 'uglify:dist']); Here we create a task called dist passing dist as our target properties on each task.
  • 31. Task Arguments grunt.registerTask('dist', ['concat:dist', 'uglify:dist']); Here we create a task called dist passing dist as our target properties on each task.
  • 33. MULTI TASKS grunt.initConfig({ log: { foo: [1, 2, 3], bar: 'hello world', baz: false } }); grunt.registerMultiTask('log', 'Log stuff.', function() { grunt.log.writeln(this.target + ': ' + this.data); }); What would happen if we ran grunt log?
  • 34. foo: [1, 2, 3] bar: 'hello world’ baz: false What would happen if we ran grunt log?
  • 35. foo: [1, 2, 3] bar: 'hello world’ baz: false What would happen if we ran grunt log?
  • 36. When a basic task is run, Grunt doesn't look at the configuration or environment —it just runs the specified task function, passing any specified colon-separated arguments in as function arguments. gruntjs.com
  • 37. grunt.registerTask('foo', 'A sample task that logs stuff.', function(arg1, arg2) { if (arguments.length === 0) { grunt.log.writeln(this.name + ", no args"); } else { grunt.log.writeln(this.name + ", " + arg1 + " " + arg2); } }); This example task logs foo, testing 123 if Grunt is run via grunt foo:testing:123. If the task is run without arguments as grunt foo the task logs foo, no args.
  • 38. grunt.registerTask('foo', 'My "foo" task.', function() { // Enqueue "bar" and "baz" tasks, to run after "foo" finishes, in-order. grunt.task.run('bar', 'baz'); // Or: grunt.task.run(['bar', 'baz']); }); CUSTOM TASKS If your tasks don't follow the "multi task" structure, use a custom task.
  • 39. grunt.registerTask('asyncfoo', 'My "asyncfoo" task.', function() { // Force task into async mode and grab a handle to the "done" function. var done = this.async(); // Run some sync stuff. grunt.log.writeln('Processing task...'); // And some async stuff. setTimeout(function() { grunt.log.writeln('All done!'); done(); }, 1000); }); CUSTOM TASKS Example of an asynchronous Task
  • 40. grunt.registerTask('asyncfoo', 'My "asyncfoo" task.', function() { // Force task into async mode and grab a handle to the "done" function. var done = this.async(); // Run some sync stuff. grunt.log.writeln('Processing task...'); // And some async stuff. setTimeout(function() { grunt.log.writeln('All done!'); done(); }, 1000); }); CUSTOM TASKS Example of an asynchronous Task
  • 41. Cool parts of tasks pt 1 Can reference their own name with this.name Can fail if any errors were logged // Fail by returning false if this task had errors if (ifErrors) { return false; } console.log(this.name)
  • 42. Cool parts of tasks pt 2 Tasks can be dependent on the successful execution of other tasks. grunt.registerTask('foo', 'My "foo" task.', function() { return false; }); grunt.registerTask('bar', 'My "bar" task.', function() { // Fail task if "foo" task failed or never ran. grunt.task.requires('foo'); // This code executes if the "foo" task ran successfully. grunt.log.writeln('Hello, world.'); });
  • 43. Tasks can access configuration properties. grunt.registerTask('foo', 'My "foo" task.', function() { // Log the property value. Returns null if the property is undefined. grunt.log.writeln('The meta.name property is: ' + grunt.config('meta.name')); // Also logs the property value. Returns null if the property is undefined. grunt.log.writeln('The meta.name property is: ' + grunt.config(['meta', 'name'])); }); Cool parts of tasks pt 3
  • 44. LET’S DIG INTO AN EXAMPLE
  • 46. SETUP npm install -g grunt-cli npm install -g bower https://nodejs.org/download/ Install Node Install Grunt CLI and Bower
  • 47. DEMO APP https://github.com/spencer48/Grunt-Demo Fork this on Git Hub then… git clone https://github.com/YOURUSERNAME/Grunt-Demo
  • 50. GRUNTFILE 'use strict'; module.exports = function (grunt) { require('jit-grunt')(grunt, { }); var appConfig = { app: require('./bower.json').appPath || 'app', dist: 'dist' }; grunt.initConfig({ }); grunt.registerTask('default', [ ]); };
  • 51. INIT CONFIG grunt.initConfig({ concat: { foo: { // concat task "foo" target options and files go here. }, bar: { // concat task "bar" target options and files go here. }, }, uglify: { bar: { // uglify task "bar" target options and files go here. }, }, }); When you run a task, Grunt looks here for it’s configuration.
  • 52. ADDING DEPENDENCIES { "name": "gruntdemo", "devDependencies": { "grunt": "^0.4.5", "grunt-concurrent": "^1.0.0", "grunt-contrib-connect": "^0.9.0", "grunt-contrib-watch": "^0.6.1", "grunt-newer": "^1.1.0", "jit-grunt": "^0.9.1", "jshint-stylish": "^1.0.0", "time-grunt": "^1.0.0" }, "engines": { "node": ">=0.10.0" } } …then npm install package.json
  • 53. SETTING UP CONNECT connect: { options: { port: 9000, hostname: 'localhost', livereload: 35729 }, livereload: { options: { open: true, middleware: function (connect) { return [ connect.static('.tmp'), connect().use( '/bower_components', connect.static('./bower_components') ), connect().use( '/app/styles', connect.static('./app/styles') ), connect.static(appConfig.app) ]; } } }, }
  • 54. SETTING UP WATCHwatch: { bower: { files: ['bower.json'], tasks: ['wiredep'] }, js: { files: ['<%= yeoman.app %>/scripts/{,*/}*.js'], tasks: ['newer:jshint:all'], options: { livereload: '<%= connect.options.livereload %>' } }, compass: { files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'], tasks: ['compass:server', 'autoprefixer:server'] }, gruntfile: { files: ['Gruntfile.js'] }, livereload: { options: { livereload: '<%= connect.options.livereload %>' }, files: [ '<%= yeoman.app %>/{,*/}*.html', '.tmp/styles/{,*/}*.css', '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' ] } }
  • 55. CONFIGURING JSHINT jshint: { options: { jshintrc: '.jshintrc', reporter: require('jshint-stylish') }, all: { src: [ 'Gruntfile.js', '<%= yeoman.app %>/scripts/{,*/}*.js' ] }, test: { options: { jshintrc: 'test/.jshintrc' }, src: ['test/spec/{,*/}*.js'] } } { "bitwise": true, "browser": true, "curly": true, "eqeqeq": true, "esnext": true, "latedef": true, "noarg": true, "node": true, "strict": true, "undef": true, "unused": true, "globals": { "angular": false } } .jshintrcGruntFile.js
  • 56. CONFIGURING CLEAN clean: { dist: { files: [{ dot: true, src: [ '.tmp', '<%= yeoman.dist %>/{,*/}*', '!<%= yeoman.dist %>/.git{,*/}*' ] }] }, server: '.tmp' } GruntFile.js
  • 57. CONFIGURING WIREDEP wiredep: { app: { src: ['<%= yeoman.app %>/index.html'], ignorePath: /..// }, test: { devDependencies: true, src: '<%= karma.unit.configFile %>', ignorePath: /..//, fileTypes:{ js: { block: /(([st]*)/{2}s*?bower:s*?(S*))(n|r|.)*?(/{2}s*endbower)/gi, detect: { js: /'(.*.js)'/gi }, replace: { js: ''{{filePath}}',' } } } }, sass: { src: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'], ignorePath: /(../){1,2}bower_components// } }, GruntFile.js
  • 58. CONFIGURING COMPASS compass: { options: { sassDir: '<%= yeoman.app %>/styles', cssDir: '.tmp/styles', generatedImagesDir: '.tmp/images/generated', imagesDir: '<%= yeoman.app %>/images', javascriptsDir: '<%= yeoman.app %>/scripts', fontsDir: '<%= yeoman.app %>/styles/fonts', importPath: './bower_components', httpImagesPath: '/images', httpGeneratedImagesPath: '/images/generated', httpFontsPath: '/styles/fonts', relativeAssets: false, assetCacheBuster: false, raw: 'Sass::Script::Number.precision = 10n' }, dist: { options: { generatedImagesDir: '<%= yeoman.dist %>/images/generated' } }, server: { options: { sourcemap: true } } } GruntFile.js
  • 59. CONFIGURING CLEAN clean: { dist: { files: [{ dot: true, src: [ '.tmp', '<%= yeoman.dist %>/{,*/}*', '!<%= yeoman.dist %>/.git{,*/}*' ] }] }, server: '.tmp' } GruntFile.js
  • 60. CONFIGURING AUTOPREFIXER // Add vendor prefixed styles autoprefixer: { options: { browsers: ['last 1 version'] }, server: { options: { map: true, }, files: [{ expand: true, cwd: '.tmp/styles/', src: '{,*/}*.css', dest: '.tmp/styles/' }] }, dist: { files: [{ expand: true, cwd: '.tmp/styles/', src: '{,*/}*.css', dest: '.tmp/styles/' }] } } GruntFile.js
  • 61. CONFIGURING FILE REV // Renames files for browser caching purposes filerev: { dist: { src: [ '<%= yeoman.dist %>/scripts/{,*/}*.js', '<%= yeoman.dist %>/styles/{,*/}*.css', '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}', '<%= yeoman.dist %>/styles/fonts/*' ] } }, GruntFile.js
  • 62. CONFIGURING USEMIN useminPrepare: { html: '<%= yeoman.app %>/index.html', options: { dest: '<%= yeoman.dist %>', flow: { html: { steps: { js: ['concat', 'uglifyjs'], css: ['cssmin'] }, post: {} } } } }, GruntFile.js
  • 63. CONFIGURING USEMIN // Performs rewrites based on filerev and the useminPrepare configuration usemin: { html: ['<%= yeoman.dist %>/{,*/}*.html'], css: ['<%= yeoman.dist %>/styles/{,*/}*.css'], js: ['<%= yeoman.dist %>/scripts/{,*/}*.js'], options: { assetsDirs: [ '<%= yeoman.dist %>', '<%= yeoman.dist %>/images', '<%= yeoman.dist %>/styles' ], patterns: { js: [[/(images/[^''""]*.(png|jpg|jpeg|gif|webp|svg))/g, 'Replacing references to images']] } } }, GruntFile.js
  • 64. CONFIGURING IMG/SVG MIN imagemin: { dist: { files: [{ expand: true, cwd: '<%= yeoman.app %>/images', src: '{,*/}*.{png,jpg,jpeg,gif}', dest: '<%= yeoman.dist %>/images' }] } }, svgmin: { dist: { files: [{ expand: true, cwd: '<%= yeoman.app %>/images', src: '{,*/}*.svg', dest: '<%= yeoman.dist %>/images' }] } }, GruntFile.js
  • 65. CONFIGURING HTML MIN htmlmin: { dist: { options: { collapseWhitespace: true, conservativeCollapse: true, collapseBooleanAttributes: true, removeCommentsFromCDATA: true }, files: [{ expand: true, cwd: '<%= yeoman.dist %>', src: ['*.html'], dest: '<%= yeoman.dist %>' }] } }, GruntFile.js
  • 66. CONFIGURING NGTEMPATES ngtemplates: { dist: { options: { module: 'gruntdemoApp', htmlmin: '<%= htmlmin.dist.options %>', usemin: 'scripts/scripts.js' }, cwd: '<%= yeoman.app %>', src: 'views/{,*/}*.html', dest: '.tmp/templateCache.js' } }, GruntFile.js
  • 67. CONFIGURING NGANNOTATE ngAnnotate: { dist: { files: [{ expand: true, cwd: '.tmp/concat/scripts', src: '*.js', dest: '.tmp/concat/scripts' }] } } GruntFile.js
  • 68. CONFIGURING NGANNOTATE copy: { dist: { files: [{ expand: true, dot: true, cwd: '<%= yeoman.app %>', dest: '<%= yeoman.dist %>', src: [ '*.{ico,png,txt}', '.htaccess', '*.html', 'images/{,*/}*.{webp}', 'styles/fonts/{,*/}*.*' ] }, { expand: true, cwd: '.tmp/images', dest: '<%= yeoman.dist %>/images', src: ['generated/*'] }, { expand: true, cwd: '.', src: 'bower_components/bootstrap-sass-official/assets/fonts/bootstrap/*', dest: '<%= yeoman.dist %>' }] }, styles: { expand: true, cwd: '<%= yeoman.app %>/styles', dest: '.tmp/styles/', src: '{,*/}*.css' } }, GruntFile.js Sorry for the tiny font :(
  • 69. CONFIGURING KARMA karma: { unit: { configFile: 'test/karma.conf.js', singleRun: true } } GruntFile.js
  • 70. CONFIGURING CONCURRENT // Run some tasks in parallel to speed up the build process concurrent: { server: [ 'compass:server' ], test: [ 'compass' ], dist: [ 'compass:dist', 'imagemin', 'svgmin' ] }, GruntFile.js
  • 72. TASK EXAMPLES grunt.registerTask('serve', 'Compile then start a connect web server', function (target) { if (target === 'dist') { return grunt.task.run([‘build', 'connect:dist:keepalive']); } grunt.task.run([ 'clean:server', 'wiredep', 'concurrent:server', 'autoprefixer:server', 'connect:livereload', 'watch' ]); }); SERVE
  • 74. QUESTIONS? S P E N C E R H A N D L E Y Mastering Grunt VIDEO SERIES @spencer414 www.spencerhand.ly www.podclear.com