Awesome
grunt-task-helper
taskHelper helps selecting files for other tasks.
For example, you want to minify only changed JS files. Then taskHelper selects files which are newer than dest
from src
(or newer than the time when this ran last time), and these files are passed to grunt-contrib-uglify task.
And, taskHelper helps you do something small to files (or file's contents).
For example, rename file, replace text, etc...
You can create your custom task to do something easily via grunt.registerTask()
. Or, writing new plugin is easy too. Using taskHelper is more easy.
Getting Started
This plugin requires Grunt ~0.4.1
If you haven't used Grunt before, be sure to check out the Getting Started guide, as it explains how to create a Gruntfile as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command:
npm install grunt-task-helper --save-dev
Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript:
grunt.loadNpmTasks('grunt-task-helper');
The "taskHelper" task
taskHelper accepts standard Grunt files
components (see Files) and handlers.
The handler is a JavaScript Function which you wrote, or a name of builtin handler. These handlers are called in some timings to select files or do something to files or file's contents. If you want, taskHelper creates standard Grunt files
components which are new for other tasks.
Overview
In your project's Gruntfile, add a section named taskHelper
to the data object passed into grunt.initConfig()
(see Configuring tasks).
You specify the files
and some handlers (e.g. options.handlerByFile
). taskHelper accepts these all files, and some files are selected or done something via handler, and only selected files (filtered files) are passed to other tasks via options.filesArray
.
Example: Copy only CSS files which are needed. This handler works like expanded Custom Filter Function.
Gruntfile.js
grunt.initConfig({
taskHelper: {
deploy: {
options: {
handlerByFileSrc: function(src, dest, options) {
var pathElms = src.match(/^(.+)\.css$/), filepath;
if (pathElms) {
// CSS file from SCSS file is not needed.
if (grunt.file.exists(pathElms[1] + '.scss'))
{ return false; }
// Give priority to souces directory.
filepath = src.replace(/^develop/, 'souces');
if (grunt.file.exists(filepath)) { return filepath; }
}
},
filesArray: []
},
src: 'develop/**/*.{css,scss}',
dest: 'backup/'
}
},
copy: {
deploy: {
// Copy files which are selected via taskHelper.
files: '<%= taskHelper.deploy.options.filesArray %>'
}
}
});
Example: Minify only changed JS files. (see Builtin handler "newFile".)
Gruntfile.js
grunt.initConfig({
taskHelper: {
deploy: {
options: {
// Select files which are newer than `dest`.
handlerByFile: 'newFile',
filesArray: []
},
expand: true,
cwd: 'develop/',
src: '**/*.js',
dest: 'public_html/'
}
},
uglify: {
deploy: {
// Minify files which are selected via taskHelper.
files: '<%= taskHelper.deploy.options.filesArray %>'
}
}
});
Example: Insert menu navigation into HTML files.
Gruntfile.js
var htmlMenu = grunt.file.read('../menu.html');
grunt.initConfig({
taskHelper: {
deploy: {
options: {
handlerByContent: function(contentSrc, options) {
// Insert HTML content to placeholder.
return contentSrc.replace(/<%MENU%>/, htmlMenu);
}
},
expand: true,
cwd: 'develop/',
src: '**/*.html',
dest: 'public_html/'
}
}
});
Example: Edit HTML files to refer to minified JS files (e.g. file.js
to file.min.js
).
Gruntfile.js
var jsFiles = [];
grunt.initConfig({
taskHelper: {
// Get source JS files and minified JS files via globbing pattern.
getJs: {
options: {
filesArray: jsFiles
},
expand: true,
cwd: 'develop/',
src: '**/*.js',
dest: 'public_html/',
ext: '.min.js'
},
// Edit HTML files. <script src="file.js"> to <script src="file.min.js">
editHtml: {
options: {
handlerByContent: function(contentSrc, options) {
jsFiles.forEach(function(f) {
// Ignore directory path, but not doing it is better.
var src = f.src[0].replace(/^.*\//, ''),
dest = f.dest.replace(/^.*\//, ''),
reSrc = new RegExp('(<script\\b[^>]+\\bsrc="[^"]*)' +
src.replace(/(\W)/g, '\\$1') + '(")', 'ig');
// Make these in every calling this function,
// but keeping these is better.
contentSrc = contentSrc.replace(reSrc, '$1' + dest + '$2');
});
return contentSrc;
}
},
expand: true,
cwd: 'develop/',
src: '**/*.html',
dest: 'public_html/'
}
},
uglify: {
minifyJs: {
// Minify files which are selected via taskHelper:getJs.
files: jsFiles
}
}
});
// taskHelper:getJs must be first.
grunt.registerTask('default',
['taskHelper:getJs', 'uglify:minifyJs', 'taskHelper:editHtml']);
<a name ="handlers">Handlers</a>
taskHelper parses files
components, and calls handlers at some timings. The flow may be changed by return value of handlers.
You can specify JavaScript Function which you wrote, or a name of builtin handler. If you want, you can specify multiple handlers into a timing by specifying array of these.
Below are timings which call handlers, and how to specify each handlers.
handlerByTask
The handlers which were specified via options.handlerByTask
are called per a task(target) before files
components are parsed. This may be a handler, or an array which includes multiple handlers. (see Cycle of handlers.)
If JavaScript Function is specified, following arguments are assigned, and return value is the following meaning.
handlerByTask(options)
-
<strong>
options
Type: Object</strong>
Copy ofoptions
which is passed togrunt.initConfig()
. -
<strong>Return value</strong>
If the handler returnsfalse
, exit current task immediately, and the remaining handlers are not called.
NOTE: Any value exceptfalse
(e.g.undefined
or returns with no value) is ignored. i.e. it means the same asreturn true
.
Example:
Gruntfile.js
grunt.initConfig({
// Configuring httpd.
concat: {
setup: {
src: ['/lib/httpd/common.conf', 'httpd/project.conf'],
dest: '/etc/httpd/conf/httpd.conf'
}
},
taskHelper: {
setup: {
options: {
handlerByTask: function(options) {
// Restart httpd.
var exec = require('child_process').exec;
exec('service node-httpd restart',
function (error, stdout, stderr) {
console.log('stdout: ' + stdout);
console.log('stderr: ' + stderr);
if (error !== null)
{ console.log('exec error: ' + error); }
}
);
}
}
}
}
});
handlerByFileSrc
The handlers which were specified via options.handlerByFileSrc
are called per a src
file in specified files
components (and parsed by Grunt). This may be a handler, or an array which includes multiple handlers. (see Cycle of handlers.)
If JavaScript Function is specified, following arguments are assigned, and return value is the following meaning.
handlerByFileSrc(src, dest, options)
-
<strong>
src
Type: String</strong>
The file path of asrc
file in specifiedfiles
components. This was parsed by Grunt, therefore Globbing patterns (e.g.foo/*.js
) which was specified tosrc
became real path that was found (e.g.foo/file-1.js
).
Ifsrc
includes multiple files (e.g. array of files, or filesfile-1.js
andfile-2.js
which was found via globbing pattern*.js
), all handlers inhandlerByFileSrc
are called with an assigned first file insrc
, and these handlers are called with an assigned second file, and continue next. (see Cycle of handlers.) Note that this flow may be changed by handler. (see Return value below.) -
<strong>
dest
Type: String</strong>
The file path of adest
file in specifiedfiles
components. -
<strong>
options
Type: Object</strong>
Copy ofoptions
which is passed togrunt.initConfig()
. -
<strong>Return value</strong>
If the handler returnsfalse
, remove current file path fromsrc
, and the remaining handlers inhandlerByFileSrc
are not called. This is like afilter
(see Custom Filter Function). If other tasks will take these selected files (filtered files), an Array must be specified tooptions.filesArray
. taskHelper addsfiles
components to this array, and you let other task refer to this array.
NOTE: Any value exceptfalse
(e.g.undefined
or returns with no value) is ignored. i.e. it means the same asreturn true
.
If the handler returns String, the currentsrc
file path is replaced with this String. Note that other task may ignore thesrc
file which doesn't exist.
Example:
Gruntfile.js
var cssFiles = [ {
src: 'theme-base/css/base.css',
dest: 'public_html/css/base.css'
} ],
path = require('path');
grunt.initConfig({
taskHelper: {
deploy: {
options: {
handlerByFileSrc: function(src, dest, options) {
var pathElms = path.basename(src).match(/^(.+?)\.test(\.css)$/), newSrc;
if (pathElms) {
newSrc = 'theme-dark/css/' + pathElms[1] + pathElms[2];
return grunt.file.exists(newSrc) ?
newSrc : // Change theme to dark.
false; // Exclude this.
}
},
filesArray: cssFiles
},
expand: true,
cwd: 'develop/',
src: '**/*.css',
dest: 'public_html/'
}
},
cssmin: {
deploy: {
files: cssFiles
}
}
});
handlerByFile
The handlers which were specified via options.handlerByFile
are called per an element in specified files
components (and parsed by Grunt, and might have changed by handlerByFileSrc
). This may be a handler, or an array which includes multiple handlers. (see Cycle of handlers.)
If JavaScript Function is specified, following arguments are assigned, and return value is the following meaning.
handlerByFile(srcArray, dest, options)
-
<strong>
srcArray
Type: Array</strong>
The array which includes the file path ofsrc
files in specifiedfiles
components. This was parsed by Grunt, therefore Globbing patterns (e.g.foo/*.js
) which was specified tosrc
became real path that was found (e.g.foo/file-1.js
). And, these might have changed by handlers inhandlerByFileSrc
. (see above.)
Ifsrc
is a file path (e.g. String'foo/file.js'
), this array includes an element (e.g. Array['foo/file.js']
). Ifsrc
files were all not found, array is empty. -
<strong>
dest
Type: String</strong>
The file path of adest
file in specifiedfiles
components. -
<strong>
options
Type: Object</strong>
Copy ofoptions
which is passed togrunt.initConfig()
. -
<strong>Return value</strong>
If the handler returnsfalse
, remove current element fromfiles
components, and the remaining handlers inhandlerByFile
are not called. If other tasks will take these selected files (filtered files), an Array must be specified tooptions.filesArray
. taskHelper addsfiles
components to this array, and you let other task refer to this array.
NOTE: Any value exceptfalse
(e.g.undefined
or returns with no value) is ignored. i.e. it means the same asreturn true
.
If the handler returns String, the currentdest
file path is replaced with this String.
Example:
Gruntfile.js
var path = require('path');
grunt.initConfig({
taskHelper: {
deploy: {
options: {
handlerByFile: function(srcArray, dest, options) {
if (!srcArray.length) {
// Exclude this.
return false;
} else if (srcArray.length > 1) {
// Multiple photos in a page.
return path.dirname(dest) + '/thumbnails.html';
}
},
filesArray: []
},
files: [
{
src: 'public_html/jon/photos/*.jpg',
dest: 'public_html/jon/photos/photo.html'
},
{
src: 'public_html/kim/photos/*.jpg',
dest: 'public_html/kim/photos/photo.html'
}
]
}
},
makeGallery: {
deploy: {
files: '<%= taskHelper.deploy.options.filesArray %>'
}
}
});
handlerByContent
The handlers which were specified via options.handlerByContent
are called per a dest
file in specified files
components (and parsed by Grunt, and might have changed by handlerByFileSrc
). This may be a handler, or an array which includes multiple handlers. (see Cycle of handlers.)
These are handlers to edit (or check) the content that should be written to dest
file.
If JavaScript Function is specified, following arguments are assigned, and return value is the following meaning.
handlerByContent(contentSrc, options)
-
<strong>
contentSrc
Type: String</strong>
The currentsrc
file's contents to first handler. To remaining handlers, the text which returned by previous handler.
All currentsrc
files (which were parsed by Grunt, and might have changed byhandlerByFileSrc
) are loaded, and these concatenated contents is passed to first handler. The text which was returned via the end handler is written todest
file. -
<strong>
options
Type: Object</strong>
Copy ofoptions
which is passed togrunt.initConfig()
. -
<strong>Return value</strong>
This value is passed to next handler. The end handler returns value to write todest
file.
If the handler returnsfalse
, the remaining handlers inhandlerByContent
are not called, anddest
file is done nothing.
NOTE: Any value exceptfalse
(e.g.undefined
or returns with no value) is content. i.e. if nothing was returned, empty text is written todest
file.
options.separator
All contents of current src
files are concatenated with options.separator
. For example, line-break-character \n
can be specified.
If this option was not specified, taskHelper uses line-break-character which was found first in current src
file's contents. If that was not found, grunt.util.linefeed
is used.
Priority Order:
options.separator
- line-break-character in
src
file's contents (found first) grunt.util.linefeed
NOTE: grunt.util.linefeed
is chosen via operating system which executes this task. Not operating system which uses the generated files. If this task is executed on Windows, \n
for others will be specified to options.separator
or src
files may include this.
Example:
Gruntfile.js
var htmlMenu = grunt.file.read('../menu.html');
grunt.initConfig({
taskHelper: {
deploy: {
options: {
handlerByContent: [
// 1st handler
function(contentSrc, options) {
// Insert HTML content to placeholder.
return contentSrc.replace(/<%MENU%>/, htmlMenu);
},
// End handler
function(contentSrc, options) {
var pathElms = contentSrc.match(/<h2\b.*?>(.+?)<\/h2>/), pageTitle;
if (pathElms) {
// Content title
pageTitle = pathElms[1];
// Style menu item of current page.
// (a part of HTML inserted by 1st handler)
return contentSrc.replace(
new RegExp('<li class="menu-item">(?=' + pageTitle + ')'),
'<li class="menu-item current">');
} else return contentSrc;
}
]
},
expand: true,
cwd: 'develop/',
src: '**/*.html',
dest: 'public_html/'
}
}
});
handlerByAllFiles
The handlers which were specified via options.handlerByAllFiles
are called per a task(target) after files
components are parsed (and might have changed by handlerByFileSrc
and handlerByFile
). This may be a handler, or an array which includes multiple handlers. (see Cycle of handlers.)
If JavaScript Function is specified, following arguments are assigned, and return value is the following meaning.
handlerByAllFiles(files, options)
-
<strong>
files
Type: Array</strong>
This is specifiedfiles
components. This was parsed by Grunt, therefore Globbing patterns (e.g.foo/*.js
) which was specified tosrc
became real path that was found (e.g.foo/file-1.js
). And, these might have changed by handlers inhandlerByFileSrc
andhandlerByFile
. (see above.)
If the files were all not found or these were all removed byhandlerByFileSrc
andhandlerByFile
, array is empty.
This array may be different fromoptions.filesArray
. This array includes only files which were selected in current task(target), but theoptions.filesArray
may include files which were added by other tasks(targets) too. -
<strong>
options
Type: Object</strong>
Copy ofoptions
which is passed togrunt.initConfig()
. -
<strong>Return value</strong>
If the handler returnsfalse
, exit current task immediately, and the remaining handlers are not called.
NOTE: Any value exceptfalse
(e.g.undefined
or returns with no value) is ignored. i.e. it means the same asreturn true
.
Example:
Gruntfile.js
var path = require('path');
grunt.initConfig({
taskHelper: {
pack: {
options: {
// Concatenate all CSS into all.css, and all JS into all.js
handlerByContent: function(content) { return content; },
// Pack concatenated files into assets.zip
handlerByAllFiles: function(files, options){
var exec = require('child_process').exec;
exec('zip assets.zip ' +
files.map(function(f) { return f.dest; }).join(' '),
function (error) {
if (error !== null)
{ console.log('exec error: ' + error); }
}
);
},
},
files: [
{
src: 'css/*.css',
dest: 'assets/all.css'
},
{
src: 'js/*.js',
dest: 'assets/all.js'
}
]
}
}
});
<a name ="cycle-handlers">Cycle of handlers</a>
The handlers are called at some timings as follows unless it is aborted by returning false.
---------------------------------- <Task 1>
| CALL handlerByTask 1
| CALL handlerByTask 2
| :
|
| ------------------------------- <Element 1 of files>
| |
| | ---------------------------- <src file 1>
| | | CALL handlerByFileSrc 1
| | | CALL handlerByFileSrc 2
| | | :
| | ----------------------------
| |
| | ---------------------------- <src file 2>
| | | CALL handlerByFileSrc 1
| | | CALL handlerByFileSrc 2
| | | :
| | ----------------------------
| | :
| |
| | CALL handlerByFile 1
| | CALL handlerByFile 2
| | :
| |
| | CALL handlerByContent 1
| | CALL handlerByContent 2
| | :
| -------------------------------
|
| ------------------------------- <Element 2 of files>
| | (Same as <Element 1 of files>)
| -------------------------------
| :
|
| CALL handlerByAllFiles 1
| CALL handlerByAllFiles 2
| :
----------------------------------
---------------------------------- <Task 2>
| (Same as <Task 1>)
----------------------------------
:
<a name ="builtin-handlers">Builtin handlers</a>
You can specify name of builtin handler instead of JavaScript Function.
Now, taskHelper has following builtin handlers.
<a name ="builtin-handlers-newfile">newFile</a>
This is a handler for handlerByFile
to select files which are newer than dest
from src
(or newer than the time when this ran last time).
- In the basic case,
srcArray
includes a file path and adest
file path was specified. The modification times of both files are compared, and ifsrc
file is newer thandest
file, return true.
One src file : One dest file
Example: Minify only changed JS files.
Gruntfile.js
grunt.initConfig({
taskHelper: {
deploy: {
options: {
handlerByFile: 'newFile',
filesArray: []
},
files: [
{
src: 'develop/js/a.js',
dest: 'public_html/js/a.min.js'
},
{
src: 'develop/js/b.js',
dest: 'public_html/js/b.min.js'
}
]
}
},
uglify: {
deploy: {
files: '<%= taskHelper.deploy.options.filesArray %>'
}
}
});
- When the
srcArray
includes multiple files path and adest
file path was specified, if there is anysrc
file in which is newer thandest
file, return true.
Some src files : One dest file
Example: Concatenate files if one or more source files were updated. (More better way in Tips)
Gruntfile.js
grunt.initConfig({
taskHelper: {
deploy: {
options: {
handlerByFile: 'newFile',
filesArray: []
},
src: 'develop/css/*.css',
dest: 'public_html/css/all.css'
}
},
concat: {
deploy: {
files: '<%= taskHelper.deploy.options.filesArray %>'
}
}
});
- When the file to compare was not specified (e.g.
dest
was not specified, ordest
is same file assrc
), ifsrc
file is newer than the time when this ran last time, return true.
taskHelper saves log file.grunt/grunt-task-helper/fileUpdates.json
for compare.
One or more src files : No dest file
Example: You don't need to keep PNG files that is source of minifying, because you have PSD files which is editable always and outputable to PNG.
Gruntfile.js
grunt.initConfig({
taskHelper: {
deploy: {
options: {
handlerByFile: 'newFile',
filesArray: []
},
expand: true,
src: 'public_html/img/*.png'
}
},
imagemin: {
deploy: {
files: '<%= taskHelper.deploy.options.filesArray %>'
}
}
});
NOTE: Logging the modification time of the file after task is best way, but current Grunt can't do it. Therefore the time is got via using options.mtimeOffset
. (see source code.)
<a name ="builtin-handlers-size">size</a>
This is a handler for handlerByFileSrc
to select files which match specified size.
You can specify minimum file size to options.minSize
, and maximum file size to options.maxSize
. One or both of these must be specified.
Example: Compress only big files.
Gruntfile.js
grunt.initConfig({
taskHelper: {
deploy: {
options: {
handlerByFileSrc: 'size',
minSize: 1024 * 64,
filesArray: []
},
expand: true,
src: 'public_html/download/*.csv',
}
},
compress: {
options: {
mode: 'gzip'
},
deploy: {
files: '<%= taskHelper.deploy.options.filesArray %>'
}
}
});
<a name ="tips">Tips</a>
Task efficiency
Some grunt plugins are wrapper of something which provide a method. And the method accepts src
file's contents and returns contents to write to dest
file.
If you want to give selected files by taskHelper to the plugin, specifying the method into handlerByContent
instead of the using the plugin is better. Because grunt parses files
components in every tasks(targets). handlerByContent
can be included to one task with other handlers.
Example: Minify CSS files if one or more source files were updated.
clean-css is wrapped by grunt-contrib-cssmin plugin.
Gruntfile.js
grunt.initConfig({
taskHelper: {
cssmin: {
options: {
handlerByFile: 'newFile',
handlerByContent: (new require('clean-css')({relativeTo: 'public_html/css'})).minify,
},
src: 'develop/css/*.css',
dest: 'public_html/css/all.css'
}
}
});
Example: Minify only changed HTML files.
htmlclean is wrapped by grunt-htmlclean plugin.
Gruntfile.js
grunt.initConfig({
taskHelper: {
deploy: {
options: {
handlerByFile: 'newFile',
handlerByContent: require('htmlclean')
},
expand: true,
cwd: 'develop/',
src: '**/*.html',
dest: 'public_html/'
}
}
});
Example: Concatenate files if one or more source files were updated.
handlerByContent
works like grunt-contrib-concat plugin. (Another plugin or module is unneeded.)
Gruntfile.js
grunt.initConfig({
taskHelper: {
deploy: {
options: {
handlerByFile: 'newFile',
handlerByContent: function(content) { return content; }, // Do nothing.
separator: ';'
},
src: 'develop/js/*.js',
dest: 'public_html/js/all.js'
}
}
});
Log messages
Grunt outputs log to STDOUT. Handlers can output messages.
Example: When there are a lot of targets, handlers output messages outstanding more than message Running "..." task
by Grunt.
Gruntfile.js
var head = '==================================== ';
grunt.initConfig({
taskHelper: {
options: {
handlerByTask: function()
{ grunt.log.writeln((head + grunt.task.current.target.bold).cyan); }
},
// Even if there is not work, a {} is necessary.
html: { /* Do something if needed */ },
css: { /* Do something if needed */ },
js: { /* Do something if needed */ },
img: { /* Do something if needed */ }
},
fooTask: {
html: { /* Do something */ },
css: { /* Do something */ },
js: { /* Do something */ },
img: { /* Do something */ }
},
barTask: {
html: { /* Do something */ },
css: { /* Do something */ },
//js: { /* Do something */ },
img: { /* Do something */ }
}
});
grunt.registerTask('default', [
'taskHelper:html', 'fooTask:html', 'barTask:html',
'taskHelper:css', 'fooTask:css', 'barTask:css',
'taskHelper:js', 'fooTask:js',
'taskHelper:img', 'fooTask:img', 'barTask:img'
]);
"Continue?", "Overwrite?" - Wizard style
The handlers work via user's response for interactively running.
Example: Interactively running via readlineSync.
Install readlineSync.
npm install readline-sync
Gruntfile.js
var readlineSync = require('readline-sync');
grunt.initConfig({
taskHelper: {
confirm: {
options: {
handlerByTask: function() {
// Abort the task if user don't want.
return readlineSync.question('The HTML files are copied. Continue? :')
.toLowerCase() === 'y';
// Or process.exit()
},
handlerByFile: function(srcArray, dest) {
if (grunt.file.exists(dest)) {
// Exclude this file if user want to keep it.
return readlineSync.question('This file already exists.\n' + dest + '\n' +
'Overwrite this file? :').toLowerCase() === 'y';
}
},
filesArray: []
},
expand: true,
cwd: 'develop/',
src: '**/*.html',
dest: 'public_html/'
}
},
copy: {
confirm: {
files: '<%= taskHelper.confirm.options.filesArray %>'
}
}
});