jquery-ui/grunt.js

462 lines
15 KiB
JavaScript
Raw Normal View History

/*global config:true, task:true*/
function stripBanner(files) {
return files.map(function(file) {
return '<strip_all_banners:' + file + '>';
});
}
function stripDirectory(file) {
return file.replace(/.+\/(.+)$/, '$1');
}
function createBanner(files) {
// strip folders
var fileNames = files && files.map(stripDirectory);
return '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' +
'<%= template.today("isoDate") %>\n' +
'<%= pkg.homepage ? "* " + pkg.homepage + "\n" : "" %>' +
'* Includes: ' + (files ? fileNames.join(', ') : '<%= stripDirectory(task.current.data.src[1]) %>') + '\n' +
'* Copyright (c) <%= template.today("yyyy") %> <%= pkg.author.name %>;' +
' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */';
}
// allow access from banner template
global.stripDirectory = stripDirectory;
task.registerHelper('strip_all_banners', function(filepath) {
return file.read(filepath).replace(/^\s*\/\*[\s\S]*?\*\/\s*/g, '');
});
var 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'.split(', ');
var uiFiles = coreFiles.map(function(file) {
return 'ui/' + file;
}).concat(file.expand('ui/*.js').filter(function(file) {
return coreFiles.indexOf(file.substring(3)) === -1;
}));
var 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']
};
function minFile(file) {
minify['dist/' + file.replace(/\.js$/, '.min.js').replace(/ui\//, 'minified/')] = ['<banner>', file];
}
uiFiles.forEach(minFile);
var allI18nFiles = file.expand('ui/i18n/*.js');
allI18nFiles.forEach(minFile);
var cssFiles = 'core accordion autocomplete button datepicker dialog menu progressbar resizable selectable slider spinner tabs tooltip theme'.split(' ').map(function(component) {
return 'themes/base/jquery.ui.' + component + '.css';
});
var minifyCSS = {
'dist/jquery-ui.min.css': 'dist/jquery-ui.css'
};
cssFiles.forEach(function(file) {
minifyCSS['dist/' + file.replace(/\.css$/, '.min.css').replace(/themes\/base\//, 'themes/base/minified/')] = ['<banner>', file];
});
config.init({
pkg: '<json:package.json>',
files: {
distFolder: 'dist/<%= pkg.name %>-<%= pkg.version %>',
cdnDistFolder: 'dist/<%= pkg.name %>-<%= pkg.version %>-cdn',
themesDistFolder: 'dist/<%= pkg.name %>-themes-<%= pkg.version %>',
zip: 'dist/<%= pkg.name %>-<%= pkg.version %>.zip',
cdnZip: 'dist/<%= pkg.name %>-<%= pkg.version %>-cdn.zip',
themesZip: 'dist/<%= pkg.name %>-themes-<%= pkg.version %>.zip'
},
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,
css_min: minifyCSS,
copy: {
dist: {
src: [
'AUTHORS.txt',
'GPL-LICENSE.txt',
'jquery-1.7.1.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: '<%= files.distFolder %>'
},
dist_min: {
src: 'dist/minified/**/*',
strip: /^dist/,
dest: '<%= files.distFolder %>/ui'
},
dist_css_min: {
src: 'dist/themes/base/minified/*.css',
strip: /^dist/,
dest: '<%= files.distFolder %>'
},
dist_min_images: {
src: 'themes/base/images/*',
strip: /^themes\/base\//,
dest: '<%= files.distFolder %>/themes/base/minified'
},
cdn: {
src: [
'AUTHORS.txt',
'GPL-LICENSE.txt',
'MIT-LICENSE.txt',
'ui/*.js',
'themes/base/images/*.png',
'themes/base/jquery.ui.*.css',
'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: '<%= files.cdnDistFolder %>'
},
cdn_i18n: {
src: 'ui/i18n/jquery.ui.datepicker-*.js',
strip: 'ui/',
dest: '<%= files.cdnDistFolder %>'
},
cdn_i18n_min: {
src: 'dist/minified/i18n/jquery.ui.datepicker-*.js',
strip: 'dist/minified',
dest: '<%= files.cdnDistFolder %>'
},
cdn_min: {
src: 'dist/minified/*.js',
strip: /^dist\/minified/,
dest: '<%= files.cdnDistFolder %>/ui'
},
cdn_css_min: {
src: 'dist/themes/base/minified/*.css',
strip: /^dist/,
dest: '<%= files.cdnDistFolder %>'
},
cdn_min_images: {
src: 'themes/base/images/*',
strip: /^themes\/base\//,
dest: '<%= files.cdnDistFolder %>/themes/base/minified'
},
themes: {
src: [
'AUTHORS.txt',
'GPL-LICENSE.txt',
'MIT-LICENSE.txt',
'package.json'
],
dest: '<%= files.themesDistFolder %>'
}
},
zip: {
dist: {
src: '<%= files.distFolder %>/**/*',
dest: '<%= files.zip %>'
},
cdn: {
src: '<%= files.cdnDistFolder %>/**/*',
dest: '<%= files.cdnZip %>'
},
themes: {
src: '<%= files.themesDistFolder %>/**/*',
dest: '<%= files.themesZip %>'
}
},
md5: {
dist: {
dir: '<%= files.distFolder %>',
dest: '<%= files.distFolder %>/MANIFEST'
},
cdn: {
dir: '<%= files.cdnDistFolder %>',
dest: '<%= files.cdnDistFolder %>/MANIFEST'
},
themes: {
dir: '<%= files.themesDistFolder %>',
dest: '<%= files.themesDistFolder %>/MANIFEST'
}
},
qunit: {
files: file.expand('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: {
// TODO extend this to tests
files: ['ui/*']
},
jshint: {
options: {
curly: true,
eqeqeq: true,
immed: true,
latedef: true,
newcap: true,
noarg: true,
sub: true,
undef: true,
eqnull: true,
browser: true
},
globals: {
jQuery: true
}
}
});
// grunt doesn't know about this files object, so need to process that manually once
// before any other variable is resolved, otherwise it would just include the templates
var files = config().files;
for (var key in files) {
files[key] = template.process(files[key], config());
}
config('files', files);
task.registerBasicTask('copy', 'Copy files to destination folder and replace @VERSION with pkg.version', function(data) {
function replaceVersion(source) {
return source.replace("@VERSION", config("pkg").version);
}
var files = file.expand(data.src);
var target = data.dest + '/';
files.forEach(function(fileName) {
var targetFile = data.strip ? fileName.replace(data.strip, '') : fileName;
file.copy(fileName, target + targetFile, replaceVersion);
});
log.writeln('Copyied ' + files.length + ' files.');
for (var fileName in data.renames) {
file.copy(fileName, target + template.process(data.renames[fileName], config()));
}
if (data.renames) {
log.writeln('Renamed ' + data.renames.length + ' files.');
}
});
task.registerBasicTask('zip', 'Create a zip file for release', function(data) {
var files = file.expand(data.src);
log.writeln("Creating zip file " + data.dest);
var fs = require('fs');
var AdmZip = require('adm-zip');
var zip = new AdmZip();
files.forEach(function(file) {
log.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(data.dest);
log.writeln("Wrote " + files.length + " files to " + data.dest);
});
task.registerBasicTask( 'css_min', 'Minify CSS files with Sqwish.', function( data ) {
var files = file.expand( data.src );
// Get banner, if specified. It would be nice if UglifyJS supported ignoring
// all comments matching a certain pattern, like /*!...*/, but it doesn't.
var banner = task.directive(files[0], function() { return null; });
if (banner === null) {
banner = '';
} else {
files.shift();
}
var max = task.helper( 'concat', files );
// Concat banner + minified source.
var min = banner + require('sqwish').minify( max, false );
file.write( data.dest, min );
if ( task.hadErrors() ) {
return false;
}
log.writeln( 'File "' + data.dest + '" created.' );
task.helper( 'min_max_info', min, max );
});
task.registerBasicTask('md5', 'Create list of md5 hashes for CDN uploads', function(data) {
// remove dest file before creating it, to make sure itself is not included
if (require('path').existsSync(data.dest)) {
require('fs').unlinkSync(data.dest);
}
var crypto = require('crypto');
var dir = template.process(data.dir, config()) + '/';
var hashes = [];
file.expand(dir + '**/*').forEach(function(fileName) {
var hash = crypto.createHash('md5');
hash.update(file.read(fileName));
hashes.push(fileName.replace(dir, '') + ' ' + hash.digest('hex'));
});
file.write(data.dest, hashes.join('\n') + '\n');
});
task.registerTask('download_themes', function() {
// var AdmZip = require('adm-zip');
var fs = require('fs');
var request = require('request');
var done = this.async();
var themes = file.read('build/themes').split(',');
var requests = 0;
file.mkdir('dist/tmp');
themes.forEach(function(theme, index) {
requests += 1;
file.mkdir('dist/tmp/' + index);
var zipFileName = 'dist/tmp/' + index + '.zip';
var out = fs.createWriteStream(zipFileName);
out.on('close', function() {
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...
task.helper("child_process", {
cmd: "unzip",
args: ["-d", "dist/tmp/" + index, zipFileName]
}, function(err, result) {
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);
});
});
task.registerTask('copy_themes', function() {
// each package includes the base theme, ignore that
var filter = /themes\/base/;
var files = file.expand('dist/tmp/*/development-bundle/themes/**/*').filter(function(file) {
return !filter.test(file);
});
var target = config('files.themesDistFolder') + '/';
files.forEach(function(fileName) {
var targetFile = fileName.replace(/dist\/tmp\/\d+\/development-bundle\//, '').replace("jquery-ui-.custom", "jquery-ui.css");
file.copy(fileName, target + targetFile);
});
// copy minified base theme from regular release
var distFolder = config('files.distFolder');
files = file.expand(distFolder + '/themes/base/**/*');
files.forEach(function(fileName) {
file.copy(fileName, target + fileName.replace(distFolder, ''));
});
});
// 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
task.registerBasicTask("compare_size", "Compare size of this branch to master", function(data) {
var files = file.expand(data.src),
done = this.async(),
sizecache = __dirname + "/dist/.sizecache.json",
sources = {
min: file.read(files[1]),
max: file.read(files[0])
},
oldsizes = {},
sizes = {};
try {
oldsizes = JSON.parse(file.read(sizecache));
} catch(e) {
oldsizes = {};
}
// Obtain the current branch and continue...
task.helper("git_current_branch", function(err, branch) {
var key, diff;
// Derived and adapted from Corey Frang's original `sizer`
log.writeln( "sizes - compared to master" );
sizes[files[0]] = sources.max.length;
sizes[files[1]] = sources.min.length;
sizes[files[1] + '.gz'] = task.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",
task.helper("lpad", sizes[ key ], 8 ),
task.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
file.write( sizecache, JSON.stringify(sizes) );
}
done();
});
});
task.registerHelper("git_current_branch", function(done) {
task.helper("child_process", {
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 ] );
}
});
});
});
task.registerHelper("lpad", function(str, len, chr) {
return ( Array( len + 1 ).join( chr || " " ) + str ).substr( -len );
});
task.registerTask('default', 'lint qunit build compare_size');
task.registerTask('sizer', 'concat:ui min:dist/jquery-ui.min.js compare_size');
task.registerTask('build', 'concat min css_min');
task.registerTask('release', 'build copy:dist copy:dist_min copy:dist_min_images copy:dist_css_min md5:dist zip:dist');
task.registerTask('release_themes', 'release download_themes copy_themes copy:themes md5:themes zip:themes');
// TODO include other themes in cdn release
task.registerTask('release_cdn', 'build copy:cdn copy:cdn_min copy:cdn_i18n copy:cdn_i18n_min copy:cdn_css_min copy:cdn_min_images md5:cdn zip:cdn');