jquery-ui/grunt.js
2012-04-02 22:20:18 -04:00

606 lines
18 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

module.exports = function( grunt ) {
var // modules
fs = require( "fs" ),
path = require( "path" ),
request = require( "request" ),
util = require( "util" ),
inspect = util.inspect,
// files
coreFiles = [
"jquery.ui.core.js",
"jquery.ui.widget.js",
"jquery.ui.mouse.js",
"jquery.ui.draggable.js",
"jquery.ui.droppable.js",
"jquery.ui.resizable.js",
"jquery.ui.selectable.js",
"jquery.ui.sortable.js",
"jquery.effects.core.js"
],
uiFiles = coreFiles.map(function( file ) {
return "ui/" + file;
}).concat( grunt.file.expandFiles( "ui/*.js" ).filter(function( file ) {
return coreFiles.indexOf( file.substring(3) ) === -1;
})),
allI18nFiles = grunt.file.expandFiles( "ui/i18n/*.js" ),
cssFiles = [
"core",
"accordion",
"autocomplete",
"button",
"datepicker",
"dialog",
"menu",
"progressbar",
"resizable",
"selectable",
"slider",
"spinner",
"tabs",
"tooltip",
"theme"
].map(function( component ) {
return "themes/base/jquery.ui." + component + ".css";
}),
// minified files
minify = {
"dist/jquery-ui.min.js": [ "<banner:meta.bannerAll>", "dist/jquery-ui.js" ],
"dist/i18n/jquery-ui-i18n.min.js": [ "<banner:meta.bannerI18n>", "dist/i18n/jquery-ui-i18n.js" ]
},
minifyCSS = {
"dist/jquery-ui.min.css": "dist/jquery-ui.css"
};
uiFiles.concat( allI18nFiles ).forEach(function( file ) {
minify[ "dist/" + file.replace( /\.js$/, ".min.js" ).replace( /ui\//, "minified/" ) ] = [ "<banner>", file ];
});
cssFiles.forEach(function( file ) {
minifyCSS[ "dist/" + file.replace( /\.css$/, ".min.css" ).replace( /themes\/base\//, "themes/base/minified/" ) ] = [ "<banner>", "<strip_all_banners:" + file + ">" ];
});
// csslint and cssmin tasks
grunt.loadNpmTasks( "grunt-css" );
grunt.registerHelper( "strip_all_banners", function( filepath ) {
return grunt.file.read( filepath ).replace( /^\s*\/\*[\s\S]*?\*\/\s*/g, "" );
});
function stripBanner( files ) {
return files.map(function( file ) {
return "<strip_all_banners:" + file + ">";
});
}
function stripDirectory( file ) {
// TODO: we're receiving the directive, so we need to strip the trailing >
// we should be receving a clean path without the directive
return file.replace( /.+\/(.+?)>?$/, "$1" );
}
// allow access from banner template
global.stripDirectory = stripDirectory;
function createBanner( files ) {
// strip folders
var fileNames = files && files.map( stripDirectory );
return "/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - " +
"<%= grunt.template.today('isoDate') %>\n" +
"<%= pkg.homepage ? '* ' + pkg.homepage + '\n' : '' %>" +
"* Includes: " + (files ? fileNames.join(", ") : "<%= stripDirectory(grunt.task.current.file.src[1]) %>") + "\n" +
"* Copyright (c) <%= grunt.template.today('yyyy') %> <%= pkg.author.name %>;" +
" Licensed <%= _.pluck(pkg.licenses, 'type').join(', ') %> */";
}
grunt.initConfig({
pkg: "<json:package.json>",
files: {
dist: "<%= pkg.name %>-<%= pkg.version %>",
cdn: "<%= pkg.name %>-<%= pkg.version %>-cdn",
themes: "<%= pkg.name %>-themes-<%= pkg.version %>"
},
meta: {
banner: createBanner(),
bannerAll: createBanner( uiFiles ),
bannerI18n: createBanner( allI18nFiles ),
bannerCSS: createBanner( cssFiles )
},
compare_size: {
files: [
"dist/jquery-ui.js",
"dist/jquery-ui.min.js"
]
},
concat: {
ui: {
src: [ "<banner:meta.bannerAll>", stripBanner( uiFiles ) ],
dest: "dist/jquery-ui.js"
},
i18n: {
src: [ "<banner:meta.bannerI18n>", allI18nFiles ],
dest: "dist/i18n/jquery-ui-i18n.js"
},
css: {
src: [ "<banner:meta.bannerCSS>", stripBanner( cssFiles ) ],
dest: "dist/jquery-ui.css"
}
},
min: minify,
cssmin: minifyCSS,
copy: {
dist: {
src: [
"AUTHORS.txt",
"GPL-LICENSE.txt",
"jquery-*.js",
"MIT-LICENSE.txt",
"README.md",
"grunt.js",
"package.json",
"ui/**/*",
"demos/**/*",
"themes/**/*",
"external/**/*",
"tests/**/*"
],
renames: {
"dist/jquery-ui.js": "ui/jquery-ui.js",
"dist/jquery-ui.min.js": "ui/minified/jquery-ui.min.js",
"dist/i18n/jquery-ui-i18n.js": "ui/i18n/jquery-ui-i18n.js",
"dist/i18n/jquery-ui-i18n.min.js": "ui/minified/i18n/jquery-ui-i18n.min.js",
"dist/jquery-ui.css": "themes/base/jquery-ui.css",
"dist/jquery-ui.min.css": "themes/base/minified/jquery-ui.min.css"
},
dest: "dist/<%= files.dist %>"
},
dist_min: {
src: "dist/minified/**/*",
strip: /^dist/,
dest: "dist/<%= files.dist %>/ui"
},
dist_css_min: {
src: "dist/themes/base/minified/*.css",
strip: /^dist/,
dest: "dist/<%= files.dist %>"
},
dist_min_images: {
src: "themes/base/images/*",
strip: /^themes\/base\//,
dest: "dist/<%= files.dist %>/themes/base/minified"
},
cdn: {
src: [
"AUTHORS.txt",
"GPL-LICENSE.txt",
"MIT-LICENSE.txt",
"ui/*.js",
"package.json"
],
renames: {
"dist/jquery-ui.js": "jquery-ui.js",
"dist/jquery-ui.min.js": "jquery-ui.min.js",
"dist/i18n/jquery-ui-i18n.js": "i18n/jquery-ui-i18n.js",
"dist/i18n/jquery-ui-i18n.min.js": "i18n/jquery-ui-i18n.min.js",
"dist/jquery-ui.css": "themes/base/jquery-ui.css",
"dist/jquery-ui.min.css": "themes/base/minified/jquery-ui.min.css"
},
dest: "dist/<%= files.cdn %>"
},
cdn_i18n: {
src: "ui/i18n/jquery.ui.datepicker-*.js",
strip: "ui/",
dest: "dist/<%= files.cdn %>"
},
cdn_i18n_min: {
src: "dist/minified/i18n/jquery.ui.datepicker-*.js",
strip: "dist/minified",
dest: "dist/<%= files.cdn %>"
},
cdn_min: {
src: "dist/minified/*.js",
strip: /^dist\/minified/,
dest: "dist/<%= files.cdn %>/ui"
},
cdn_min_images: {
src: "themes/base/images/*",
strip: /^themes\/base\//,
dest: "dist/<%= files.cdn %>/themes/base/minified"
},
cdn_themes: {
src: "dist/<%= files.themes %>/themes/**/*",
strip: "dist/<%= files.themes %>",
dest: "dist/<%= files.cdn %>"
},
themes: {
src: [
"AUTHORS.txt",
"GPL-LICENSE.txt",
"MIT-LICENSE.txt",
"package.json"
],
dest: "dist/<%= files.themes %>"
}
},
zip: {
dist: {
src: "<%= files.dist %>",
dest: "<%= files.dist %>.zip"
},
cdn: {
src: "<%= files.cdn %>",
dest: "<%= files.cdn %>.zip"
},
themes: {
src: "<%= files.themes %>",
dest: "<%= files.themes %>.zip"
}
},
md5: {
dist: {
src: "dist/<%= files.dist %>",
dest: "dist/<%= files.dist %>/MANIFEST"
},
cdn: {
src: "dist/<%= files.cdn %>",
dest: "dist/<%= files.cdn %>/MANIFEST"
},
themes: {
src: "dist/<%= files.themes %>",
dest: "dist/<%= files.themes %>/MANIFEST"
}
},
qunit: {
files: grunt.file.expandFiles( "tests/unit/**/*.html" ).filter(function( file ) {
// disabling everything that doesn't (quite) work with PhantomJS for now
// except for all|index|test, try to include more as we go
return !( /(all|index|test|draggable|droppable|selectable|resizable|sortable|dialog|slider|datepicker|tabs|tabs_deprecated)\.html/ ).test( file );
})
},
lint: {
ui: "ui/*",
grunt: "grunt.js",
tests: "tests/unit/**/*.js"
},
csslint: {
base_theme: {
src: "themes/base/*.css",
rules: {
"import": false,
"overqualified-elements": 2
}
}
},
jshint: (function() {
var defaults = {
curly: true,
eqnull: true,
eqeqeq: true,
expr: true,
latedef: true,
noarg: true,
onevar: true,
// TODO: limit to multi-line comments https://github.com/jshint/jshint/issues/503
smarttabs: true,
// TODO: use "faux strict mode" https://github.com/jshint/jshint/issues/504
// strict: true,
// TODO: enable trailing
// trailing: true,
undef: true
};
function extend( a, b ) {
for ( var prop in b ) {
a[ prop ] = b[ prop ];
}
return a;
}
return {
options: defaults,
grunt: {
options: extend({
node: true
}, defaults ),
globals: {
task: true,
config: true,
file: true,
log: true,
template: true
}
},
ui: {
options: extend({
browser: true,
jquery: true
}, defaults ),
globals: {
Globalize: true
}
},
tests: {
options: extend({
browser: true,
jquery: true
}, defaults ),
globals: {
module: true,
test: true,
ok: true,
equal: true,
deepEqual: true,
QUnit: true
}
}
};
})()
});
grunt.registerMultiTask( "copy", "Copy files to destination folder and replace @VERSION with pkg.version", function() {
function replaceVersion( source ) {
return source.replace( "@VERSION", grunt.config( "pkg.version" ) );
}
var files = grunt.file.expandFiles( this.file.src );
var target = this.file.dest + "/";
var strip = this.data.strip;
if ( typeof strip === "string" ) {
strip = new RegExp( "^" + grunt.template.process( strip, grunt.config() ).replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ) );
}
files.forEach(function( fileName ) {
var targetFile = strip ? fileName.replace( strip, "" ) : fileName;
if ( /(js|css)$/.test( fileName ) ) {
grunt.file.copy( fileName, target + targetFile, {
process: replaceVersion
});
} else {
grunt.file.copy( fileName, target + targetFile );
}
});
grunt.log.writeln( "Copied " + files.length + " files." );
var renameCount = 0;
for ( var fileName in this.data.renames ) {
renameCount += 1;
grunt.file.copy( fileName, target + grunt.template.process( this.data.renames[ fileName ], grunt.config() ) );
}
if ( renameCount ) {
grunt.log.writeln( "Renamed " + renameCount + " files." );
}
});
grunt.registerMultiTask( "zip", "Create a zip file for release", function() {
// TODO switch back to adm-zip for better cross-platform compability once it actually works
// 0.1.3 works, but result can't be unzipped
// its also a lot slower then zip program, probably due to how its used...
// var files = grunt.file.expandFiles( "dist/" + this.file.src + "/**/*" );
// grunt.log.writeln( "Creating zip file " + this.file.dest );
//var AdmZip = require( "adm-zip" );
//var zip = new AdmZip();
//files.forEach(function( file ) {
// grunt.verbose.writeln( "Zipping " + file );
// // rewrite file names from dist folder (created by build), drop the /dist part
// zip.addFile(file.replace(/^dist/, "" ), fs.readFileSync( file ) );
//});
//zip.writeZip( "dist/" + this.file.dest );
//grunt.log.writeln( "Wrote " + files.length + " files to " + this.file.dest );
var done = this.async();
var dest = this.file.dest;
var src = grunt.template.process( this.file.src, grunt.config() );
grunt.utils.spawn({
cmd: "zip",
args: [ "-r", dest, src ],
opts: {
cwd: 'dist'
}
}, function( err, result ) {
if ( err ) {
grunt.log.error( err );
done();
return;
}
grunt.log.writeln( "Zipped " + dest );
done();
});
});
grunt.registerMultiTask( "md5", "Create list of md5 hashes for CDN uploads", function() {
// remove dest file before creating it, to make sure itself is not included
if ( path.existsSync( this.file.dest ) ) {
fs.unlinkSync( this.file.dest );
}
var crypto = require( "crypto" );
var dir = this.file.src + "/";
var hashes = [];
grunt.file.expandFiles( dir + "**/*" ).forEach(function( fileName ) {
var hash = crypto.createHash( "md5" );
hash.update( grunt.file.read( fileName, "ascii" ) );
hashes.push( fileName.replace( dir, "" ) + " " + hash.digest( "hex" ) );
});
grunt.file.write( this.file.dest, hashes.join( "\n" ) + "\n" );
grunt.log.writeln( "Wrote " + this.file.dest + " with " + hashes.length + " hashes" );
});
// only needed for 1.8
grunt.registerTask( "download_docs", function() {
function capitalize(value) {
return value[0].toUpperCase() + value.slice(1);
}
// should be grunt.config("pkg.version")?
var version = "1.8";
var docsDir = "dist/docs";
var files = "draggable droppable resizable selectable sortable accordion autocomplete button datepicker dialog progressbar slider tabs position"
.split(" ").map(function(widget) {
return {
url: "http://docs.jquery.com/action/render/UI/API/" + version + "/" + capitalize(widget),
dest: docsDir + '/' + widget + '.html'
};
});
files = files.concat("animate addClass effect hide removeClass show switchClass toggle toggleClass".split(" ").map(function(widget) {
return {
url: "http://docs.jquery.com/action/render/UI/Effects/" + widget,
dest: docsDir + '/' + widget + '.html'
};
}));
files = files.concat("Blind Clip Drop Explode Fade Fold Puff Slide Scale Bounce Highlight Pulsate Shake Size Transfer".split(" ").map(function(widget) {
return {
url: "http://docs.jquery.com/action/render/UI/Effects/" + widget,
dest: docsDir + '/effect-' + widget.toLowerCase() + '.html'
};
}));
grunt.file.mkdir( "dist/docs" );
grunt.utils.async.forEach( files, function( file, done ) {
var out = fs.createWriteStream( file.dest );
out.on( "close", done );
request( file.url ).pipe( out );
}, this.async() );
});
grunt.registerTask( "download_themes", function() {
// var AdmZip = require('adm-zip');
var done = this.async();
var themes = grunt.file.read( "build/themes" ).split(",");
var requests = 0;
grunt.file.mkdir( "dist/tmp" );
themes.forEach(function( theme, index ) {
requests += 1;
grunt.file.mkdir( "dist/tmp/" + index );
var zipFileName = "dist/tmp/" + index + ".zip";
var out = fs.createWriteStream( zipFileName );
out.on( "close", function() {
grunt.log.writeln( "done downloading " + zipFileName );
// TODO AdmZip produces "crc32 checksum failed", need to figure out why
// var zip = new AdmZip(zipFileName);
// zip.extractAllTo('dist/tmp/' + index + '/');
// until then, using cli unzip...
grunt.utils.spawn({
cmd: "unzip",
args: [ "-d", "dist/tmp/" + index, zipFileName ]
}, function( err, result ) {
grunt.log.writeln( "Unzipped " + zipFileName + ", deleting it now" );
fs.unlinkSync( zipFileName );
requests -= 1;
if (requests === 0) {
done();
}
});
});
request( "http://ui-dev.jquery.com/download/?" + theme ).pipe( out );
});
});
grunt.registerTask( "copy_themes", function() {
// each package includes the base theme, ignore that
var filter = /themes\/base/;
var files = grunt.file.expandFiles( "dist/tmp/*/development-bundle/themes/**/*" ).filter(function( file ) {
return !filter.test( file );
});
// TODO the grunt.template.process call shouldn't be necessary
var target = "dist/" + grunt.template.process( grunt.config( "files.themes" ), grunt.config() ) + "/";
files.forEach(function( fileName ) {
var targetFile = fileName.replace( /dist\/tmp\/\d+\/development-bundle\//, "" ).replace( "jquery-ui-.custom", "jquery-ui" );
grunt.file.copy( fileName, target + targetFile );
});
// copy minified base theme from regular release
// TODO same as the one above
var distFolder = "dist/" + grunt.template.process( grunt.config( "files.dist" ), grunt.config() );
files = grunt.file.expandFiles( distFolder + "/themes/base/**/*" );
files.forEach(function( fileName ) {
grunt.file.copy( fileName, target + fileName.replace( distFolder, "" ) );
});
});
grunt.registerTask( "clean", function() {
require( "rimraf" ).sync( "dist" );
});
// TODO merge with code in jQuery Core, share as grunt plugin/npm
// this here actually uses the provided filenames in the output
// the helpers should just be regular functions, no need to share those with the world
grunt.registerMultiTask( "compare_size", "Compare size of this branch to master", function() {
var files = grunt.file.expandFiles( this.file.src ),
done = this.async(),
sizecache = __dirname + "/dist/.sizecache.json",
sources = {
min: grunt.file.read( files[1] ),
max: grunt.file.read( files[0] )
},
oldsizes = {},
sizes = {};
try {
oldsizes = JSON.parse( grunt.file.read( sizecache ) );
} catch( e ) {
oldsizes = {};
}
// Obtain the current branch and continue...
grunt.helper( "git_current_branch", function( err, branch ) {
var key, diff;
// Derived and adapted from Corey Frang's original `sizer`
grunt.log.writeln( "sizes - compared to master" );
sizes[ files[0] ] = sources.max.length;
sizes[ files[1] ] = sources.min.length;
sizes[ files[1] + ".gz" ] = grunt.helper( "gzip", sources.min ).length;
for ( key in sizes ) {
diff = oldsizes[ key ] && ( sizes[ key ] - oldsizes[ key ] );
if ( diff > 0 ) {
diff = "+" + diff;
}
console.log( "%s %s %s",
grunt.helper("lpad", sizes[ key ], 8 ),
grunt.helper("lpad", diff ? "(" + diff + ")" : "(-)", 8 ),
key
);
}
if ( branch === "master" ) {
// If master, write to file - this makes it easier to compare
// the size of your current code state to the master branch,
// without returning to the master to reset the cache
grunt.file.write( sizecache, JSON.stringify(sizes) );
}
done();
});
});
grunt.registerHelper( "git_current_branch", function( done ) {
grunt.utils.spawn({
cmd: "git",
args: [ "branch", "--no-color" ]
}, function( err, result ) {
var branch;
result.split( "\n" ).forEach(function( branch ) {
var matches = /^\* (.*)/.exec( branch );
if ( matches !== null && matches.length && matches[ 1 ] ) {
done( null, matches[ 1 ] );
}
});
});
});
grunt.registerHelper( "lpad", function( str, len, chr ) {
return ( Array( len + 1 ).join( chr || " " ) + str ).substr( -len );
});
grunt.registerTask( "default", "lint csslint qunit build compare_size" );
grunt.registerTask( "sizer", "concat:ui min:dist/jquery-ui.min.js compare_size" );
grunt.registerTask( "build", "concat min cssmin" );
grunt.registerTask( "release", "clean build copy:dist copy:dist_min copy:dist_min_images copy:dist_css_min md5:dist zip:dist" );
grunt.registerTask( "release_themes", "release download_themes copy_themes copy:themes md5:themes zip:themes" );
grunt.registerTask( "release_cdn", "release_themes copy:cdn copy:cdn_min copy:cdn_i18n copy:cdn_i18n_min copy:cdn_min_images copy:cdn_themes md5:cdn zip:cdn" );
};