4 * https://gist.github.com/2489540
9 /*global config:true, task:true, process:true*/
11 var child_process = require("child_process");
13 module.exports = function( grunt ) {
17 // https://gist.github.com/2876125
18 function readOptionalJSON( filepath ) {
21 data = grunt.file.readJSON( filepath );
22 grunt.verbose.write( "Reading " + filepath + "..." ).ok();
27 var task = grunt.task;
28 var file = grunt.file;
29 var utils = grunt.utils;
31 var verbose = grunt.verbose;
32 var fail = grunt.fail;
33 var option = grunt.option;
34 var config = grunt.config;
35 var template = grunt.template;
42 pkg: "<json:package.json>",
43 dst: readOptionalJSON("dist/.destination.json"),
45 banner: "/*! jQuery v<%= pkg.version %> jquery.com | jquery.org/license */"
52 "src/sizzle-jquery.js",
53 "src/sizzle/sizzle.js"
69 "src/manipulation.js",
71 { flag: "css", src: "src/css.js" },
73 { flag: "ajax", src: "src/ajax.js" },
74 { flag: "ajax/jsonp", src: "src/ajax/jsonp.js", needs: [ "ajax", "ajax/script" ] },
75 { flag: "ajax/script", src: "src/ajax/script.js", needs: ["ajax"] },
76 { flag: "ajax/xhr", src: "src/ajax/xhr.js", needs: ["ajax"] },
77 { flag: "effects", src: "src/effects.js", needs: ["css"] },
78 { flag: "offset", src: "src/offset.js", needs: ["css"] },
79 { flag: "dimensions", src: "src/dimensions.js", needs: ["css"] },
80 { flag: "deprecated", src: "src/deprecated.js" },
87 "dist/jquery.min.js": [ "<banner>", "dist/jquery.js" ]
91 dist: "dist/jquery.js",
93 tests: "test/unit/**/*.js"
97 function jshintrc( path ) {
98 return readOptionalJSON( (path || "") + ".jshintrc" ) || {};
103 dist: jshintrc( "src/" ),
104 tests: jshintrc( "test/" )
109 files: "test/index.html"
113 "<config:lint.grunt>", "<config:lint.tests>",
126 grunt.registerTask( "default", "update_submodules selector build:*:* lint min dist:* compare_size" );
128 // Short list as a high frequency watch task
129 grunt.registerTask( "dev", "selector build:*:* lint" );
131 // Load grunt tasks from NPM packages
132 grunt.loadNpmTasks( "grunt-compare-size" );
133 grunt.loadNpmTasks( "grunt-git-authors" );
134 grunt.loadNpmTasks( "grunt-update-submodules" );
136 grunt.registerTask( "testswarm", function( commit, configFile ) {
137 var testswarm = require( "testswarm" ),
139 config = grunt.file.readJSON( configFile ).jquery,
140 tests = "ajax attributes callbacks core css data deferred dimensions effects event manipulation offset queue serialize support traversing Sizzle".split(" ");
142 tests.forEach(function( test ) {
143 testUrls.push( config.testUrl + commit + "/test/index.html?module=" + test );
147 url: config.swarmUrl,
149 timeout: 1000 * 60 * 30,
152 authUsername: config.authUsername,
153 authToken: config.authToken,
154 jobName: 'jQuery commit #<a href="https://github.com/jquery/jquery/commit/' + commit + '">' + commit.substr( 0, 10 ) + '</a>',
155 runMax: config.runMax,
157 "runUrls[]": testUrls,
158 "browserSets[]": ["popular"]
162 // Build src/selector.js
163 grunt.registerMultiTask( "selector", "Build src/selector.js", function() {
165 var name = this.file.dest,
166 files = this.file.src,
168 api: file.read( files[0] ),
169 src: file.read( files[1] )
175 sizzle-jquery.js -> sizzle between "EXPOSE" blocks,
176 replace define & window.Sizzle assignment
180 if ( typeof define === "function" && define.amd ) {
181 define(function() { return Sizzle; });
183 window.Sizzle = Sizzle;
189 Sizzle.attr = jQuery.attr;
190 jQuery.find = Sizzle;
191 jQuery.expr = Sizzle.selectors;
192 jQuery.expr[":"] = jQuery.expr.pseudos;
193 jQuery.unique = Sizzle.uniqueSort;
194 jQuery.text = Sizzle.getText;
195 jQuery.isXMLDoc = Sizzle.isXML;
196 jQuery.contains = Sizzle.contains;
200 // Break into 3 pieces
201 parts = sizzle.src.split("// EXPOSE");
202 // Replace the if/else block with api
203 parts[1] = sizzle.api;
205 compiled = parts.join("");
208 verbose.write("Injected sizzle-jquery.js into sizzle.js");
210 // Write concatenated source to file
211 file.write( name, compiled );
213 // Fail task if errors were logged.
214 if ( this.errorCount ) {
218 // Otherwise, print a success message.
219 log.writeln( "File '" + name + "' created." );
223 // Special "alias" task to make custom build creation less grawlix-y
224 grunt.registerTask( "custom", function() {
225 var done = this.async(),
226 args = [].slice.call(arguments),
227 modules = args.length ? args[0].replace(/,/g, ":") : "";
230 // Translation example
232 // grunt custom:+ajax,-dimensions,-effects,-offset
236 // grunt build:*:*:+ajax:-dimensions:-effects:-offset
238 grunt.log.writeln( "Creating custom build...\n" );
242 args: [ "build:*:*:" + modules, "min" ]
243 }, function( err, result ) {
245 grunt.verbose.error();
250 grunt.log.writeln( result.replace("Done, without errors.", "") );
256 // Special concat/build task to handle various jQuery build requirements
258 grunt.registerMultiTask(
260 "Concatenate source (include/exclude modules with +/- flags), embed date/version",
262 // Concat specified files.
265 modules = this.flags,
266 explicit = Object.keys(modules).length > 1,
267 optIn = !modules["*"],
268 name = this.file.dest,
270 version = config( "pkg.version" ),
271 excluder = function( flag, needsFlag ) {
272 // explicit > implicit, so set this first and let it be overridden by explicit
273 if ( optIn && !modules[ flag ] && !modules[ "+" + flag ] ) {
274 excluded[ flag ] = false;
277 if ( excluded[ needsFlag ] || modules[ "-" + flag ] ) {
278 // explicit exclusion from flag or dependency
279 excluded[ flag ] = true;
280 } else if ( modules[ "+" + flag ] && ( excluded[ needsFlag ] === false ) ) {
281 // explicit inclusion from flag or dependency overriding a weak inclusion
282 delete excluded[ needsFlag ];
286 // append commit id to version
287 if ( process.env.COMMIT ) {
288 version += " " + process.env.COMMIT;
291 // figure out which files to exclude based on these rules in this order:
292 // explicit > implicit (explicit also means a dependency/dependent that was explicit)
295 // *: none (implicit exclude)
296 // *:* all (implicit include)
297 // *:*:-effects all except effects (explicit > implicit)
298 // *:*:-css all except css and its deps (explicit)
299 // *:*:-css:+effects all except css and its deps (explicit exclude from dep. trumps explicit include)
300 // *:+effects none except effects and its deps (explicit include from dep. trumps implicit exclude)
301 this.file.src.forEach(function( filepath ) {
302 var flag = filepath.flag;
308 // check for dependencies
309 if ( filepath.needs ) {
310 filepath.needs.forEach(function( needsFlag ) {
311 excluder( flag, needsFlag );
317 // append excluded modules to version
318 if ( Object.keys( excluded ).length ) {
319 version += " -" + Object.keys( excluded ).join( ",-" );
320 // set pkg.version to version with excludes, so minified file picks it up
321 grunt.config.set( "pkg.version", version );
325 // conditionally concatenate source
326 this.file.src.forEach(function( filepath ) {
327 var flag = filepath.flag,
333 if ( excluded[ flag ] !== undefined ) {
334 message = ( "Excluding " + flag ).red;
338 message = ( "Including " + flag ).green;
340 // If this module was actually specified by the
341 // builder, then st the flag to include it in the
343 if ( modules[ "+" + flag ] ) {
348 // Only display the inclusion/exclusion list when handling
351 // Additionally, only display modules that have been specified
353 if ( explicit && specified ) {
354 grunt.log.writetableln([ 27, 30 ], [
356 ( "(" + filepath.src + ")").grey
360 filepath = filepath.src;
364 compiled += file.read( filepath );
370 compiled = compiled.replace( /@VERSION/g, version )
371 .replace( "@DATE", function () {
372 var date = new Date();
382 // Write concatenated source to file
383 file.write( name, compiled );
385 // Fail task if errors were logged.
386 if ( this.errorCount ) {
390 // Otherwise, print a success message.
391 log.writeln( "File '" + name + "' created." );
394 // Allow custom dist file locations
395 grunt.registerTask( "dist", function() {
396 var flags, paths, stored;
398 // Check for stored destination paths
399 // ( set in dist/.destination.json )
400 stored = Object.keys( config("dst") );
402 // Allow command line input as well
403 flags = Object.keys( this.flags );
405 // Combine all output target paths
406 paths = [].concat( stored, flags ).filter(function( path ) {
410 // Ensure the dist files are pure ASCII
411 var fs = require("fs"),
413 distpaths.forEach(function( filename ) {
414 var buf = fs.readFileSync( filename, "utf8" ),
416 if ( buf.length !== Buffer.byteLength( buf, "utf8" ) ) {
417 log.writeln( filename + ": Non-ASCII characters detected:" );
418 for ( i = 0; i < buf.length; i++ ) {
419 c = buf.charCodeAt( i );
421 log.writeln( "- position " + i + ": " + c );
422 log.writeln( "-- " + buf.substring( i - 20, i + 20 ) );
432 // Proceed only if there are actual
434 if ( paths.length ) {
436 // 'distpaths' is declared at the top of the
437 // module.exports function scope. It is an array
438 // of default files that jQuery creates
439 distpaths.forEach(function( filename ) {
440 paths.forEach(function( path ) {
443 if ( !/\/$/.test( path ) ) {
447 created = path + filename.replace( "dist/", "" );
449 if ( !/^\//.test( path ) ) {
450 log.error( "File '" + created + "' was NOT created." );
454 file.write( created, file.read(filename) );
456 log.writeln( "File '" + created + "' created." );