4 * https://gist.github.com/2489540
8 module.exports = function( grunt ) {
14 // https://gist.github.com/2876125
15 function readOptionalJSON( filepath ) {
18 data = grunt.file.readJSON( filepath );
19 grunt.verbose.write( "Reading " + filepath + "..." ).ok();
24 var file = grunt.file,
26 verbose = grunt.verbose,
27 config = grunt.config,
34 pkg: "<json:package.json>",
35 dst: readOptionalJSON("dist/.destination.json"),
37 banner: "/*! jQuery v<%= pkg.version %> jquery.com | jquery.org/license */"
44 "src/sizzle-jquery.js",
45 "src/sizzle/sizzle.js"
61 "src/manipulation.js",
63 { flag: "css", src: "src/css.js" },
65 { flag: "ajax", src: "src/ajax.js" },
66 { flag: "ajax/script", src: "src/ajax/script.js", needs: ["ajax"] },
67 { flag: "ajax/jsonp", src: "src/ajax/jsonp.js", needs: [ "ajax", "ajax/script" ] },
68 { flag: "ajax/xhr", src: "src/ajax/xhr.js", needs: ["ajax"] },
69 { flag: "effects", src: "src/effects.js", needs: ["css"] },
70 { flag: "offset", src: "src/offset.js", needs: ["css"] },
71 { flag: "dimensions", src: "src/dimensions.js", needs: ["css"] },
72 { flag: "deprecated", src: "src/deprecated.js" },
79 "dist/jquery.min.js": [ "<banner>", "dist/jquery.js" ]
83 dist: "dist/jquery.js",
85 tests: "test/unit/**/*.js"
89 function jshintrc( path ) {
90 return readOptionalJSON( (path || "") + ".jshintrc" ) || {};
95 dist: jshintrc( "src/" ),
96 tests: jshintrc( "test/" )
101 files: "test/index.html"
105 "<config:lint.grunt>", "<config:lint.tests>",
118 grunt.registerTask( "default", "update_submodules selector build:*:* lint min dist:* compare_size" );
120 // Short list as a high frequency watch task
121 grunt.registerTask( "dev", "selector build:*:* lint" );
123 // Load grunt tasks from NPM packages
124 grunt.loadNpmTasks( "grunt-compare-size" );
125 grunt.loadNpmTasks( "grunt-git-authors" );
126 grunt.loadNpmTasks( "grunt-update-submodules" );
128 grunt.registerTask( "testswarm", function( commit, configFile ) {
130 testswarm = require( "testswarm" ),
132 pull = /PR-(\d+)/.exec( commit ),
133 config = grunt.file.readJSON( configFile ).jquery,
134 tests = "ajax attributes callbacks core css data deferred dimensions effects event manipulation offset queue serialize support traversing Sizzle".split(" ");
137 jobName = "jQuery pull <a href='https://github.com/jquery/jquery/pull/" +
138 pull[ 1 ] + "'>#" + pull[ 1 ] + "</a>";
140 jobName = "jQuery commit #<a href='https://github.com/jquery/jquery/commit/" +
141 commit + "'>" + commit.substr( 0, 10 ) + "</a>";
144 tests.forEach(function( test ) {
145 testUrls.push( config.testUrl + commit + "/test/index.html?module=" + test );
149 url: config.swarmUrl,
151 timeout: 1000 * 60 * 30,
154 authUsername: config.authUsername,
155 authToken: config.authToken,
157 runMax: config.runMax,
159 "runUrls[]": testUrls,
160 "browserSets[]": config.browserSets
164 // Build src/selector.js
165 grunt.registerMultiTask( "selector", "Build src/selector.js", function() {
167 var name = this.file.dest,
168 files = this.file.src,
170 api: file.read( files[0] ),
171 src: file.read( files[1] )
177 sizzle-jquery.js -> sizzle between "EXPOSE" blocks,
178 replace define & window.Sizzle assignment
182 if ( typeof define === "function" && define.amd ) {
183 define(function() { return Sizzle; });
185 window.Sizzle = Sizzle;
191 Sizzle.attr = jQuery.attr;
192 jQuery.find = Sizzle;
193 jQuery.expr = Sizzle.selectors;
194 jQuery.expr[":"] = jQuery.expr.pseudos;
195 jQuery.unique = Sizzle.uniqueSort;
196 jQuery.text = Sizzle.getText;
197 jQuery.isXMLDoc = Sizzle.isXML;
198 jQuery.contains = Sizzle.contains;
202 // Break into 3 pieces
203 parts = sizzle.src.split("// EXPOSE");
204 // Replace the if/else block with api
205 parts[1] = sizzle.api;
207 compiled = parts.join("");
210 verbose.write("Injected sizzle-jquery.js into sizzle.js");
212 // Write concatenated source to file
213 file.write( name, compiled );
215 // Fail task if errors were logged.
216 if ( this.errorCount ) {
220 // Otherwise, print a success message.
221 log.writeln( "File '" + name + "' created." );
225 // Special "alias" task to make custom build creation less grawlix-y
226 grunt.registerTask( "custom", function() {
227 var done = this.async(),
228 args = [].slice.call(arguments),
229 modules = args.length ? args[0].replace(/,/g, ":") : "";
232 // Translation example
234 // grunt custom:+ajax,-dimensions,-effects,-offset
238 // grunt build:*:*:+ajax:-dimensions:-effects:-offset
240 grunt.log.writeln( "Creating custom build...\n" );
243 cmd: process.platform === "win32" ? "grunt.cmd" : "grunt",
244 args: [ "build:*:*:" + modules, "min" ]
245 }, function( err, result ) {
247 grunt.verbose.error();
252 grunt.log.writeln( result.replace("Done, without errors.", "") );
258 // Special concat/build task to handle various jQuery build requirements
260 grunt.registerMultiTask(
262 "Concatenate source (include/exclude modules with +/- flags), embed date/version",
264 // Concat specified files.
266 modules = this.flags,
267 optIn = !modules["*"],
268 explicit = optIn || Object.keys(modules).length > 1,
269 name = this.file.dest,
272 version = config( "pkg.version" ),
273 excluder = function( flag, needsFlag ) {
274 // optIn defaults implicit behavior to weak exclusion
275 if ( optIn && !modules[ flag ] && !modules[ "+" + flag ] ) {
276 excluded[ flag ] = false;
279 // explicit or inherited strong exclusion
280 if ( excluded[ needsFlag ] || modules[ "-" + flag ] ) {
281 excluded[ flag ] = true;
283 // explicit inclusion overrides weak exclusion
284 } else if ( excluded[ needsFlag ] === false &&
285 ( modules[ flag ] || modules[ "+" + flag ] ) ) {
287 delete excluded[ needsFlag ];
289 // ...all the way down
290 if ( deps[ needsFlag ] ) {
291 deps[ needsFlag ].forEach(function( subDep ) {
292 modules[ needsFlag ] = true;
293 excluder( needsFlag, subDep );
299 // append commit id to version
300 if ( process.env.COMMIT ) {
301 version += " " + process.env.COMMIT;
304 // figure out which files to exclude based on these rules in this order:
305 // dependency explicit exclude
306 // > explicit exclude
307 // > explicit include
308 // > dependency implicit exclude
309 // > implicit exclude
311 // * none (implicit exclude)
312 // *:* all (implicit include)
313 // *:*:-css all except css and dependents (explicit > implicit)
314 // *:*:-css:+effects same (excludes effects because explicit include is trumped by explicit exclude of dependency)
315 // *:+effects none except effects and its dependencies (explicit include trumps implicit exclude of dependency)
316 this.file.src.forEach(function( filepath ) {
317 var flag = filepath.flag;
323 // check for dependencies
324 if ( filepath.needs ) {
325 deps[ flag ] = filepath.needs;
326 filepath.needs.forEach(function( needsFlag ) {
327 excluder( flag, needsFlag );
333 // append excluded modules to version
334 if ( Object.keys( excluded ).length ) {
335 version += " -" + Object.keys( excluded ).join( ",-" );
336 // set pkg.version to version with excludes, so minified file picks it up
337 grunt.config.set( "pkg.version", version );
341 // conditionally concatenate source
342 this.file.src.forEach(function( filepath ) {
343 var flag = filepath.flag,
349 if ( excluded[ flag ] !== undefined ) {
350 message = ( "Excluding " + flag ).red;
354 message = ( "Including " + flag ).green;
356 // If this module was actually specified by the
357 // builder, then st the flag to include it in the
359 if ( modules[ "+" + flag ] ) {
364 // Only display the inclusion/exclusion list when handling
367 // Additionally, only display modules that have been specified
369 if ( explicit && specified ) {
370 grunt.log.writetableln([ 27, 30 ], [
372 ( "(" + filepath.src + ")").grey
376 filepath = filepath.src;
380 compiled += file.read( filepath );
386 compiled = compiled.replace( /@VERSION/g, version )
387 .replace( "@DATE", function () {
388 var date = new Date();
398 // Write concatenated source to file
399 file.write( name, compiled );
401 // Fail task if errors were logged.
402 if ( this.errorCount ) {
406 // Otherwise, print a success message.
407 log.writeln( "File '" + name + "' created." );
410 // Allow custom dist file locations
411 grunt.registerTask( "dist", function() {
412 var flags, paths, stored;
414 // Check for stored destination paths
415 // ( set in dist/.destination.json )
416 stored = Object.keys( config("dst") );
418 // Allow command line input as well
419 flags = Object.keys( this.flags );
421 // Combine all output target paths
422 paths = [].concat( stored, flags ).filter(function( path ) {
426 // Ensure the dist files are pure ASCII
427 var fs = require("fs"),
429 distpaths.forEach(function( filename ) {
430 var buf = fs.readFileSync( filename, "utf8" ),
432 if ( buf.length !== Buffer.byteLength( buf, "utf8" ) ) {
433 log.writeln( filename + ": Non-ASCII characters detected:" );
434 for ( i = 0; i < buf.length; i++ ) {
435 c = buf.charCodeAt( i );
437 log.writeln( "- position " + i + ": " + c );
438 log.writeln( "-- " + buf.substring( i - 20, i + 20 ) );
448 // Proceed only if there are actual
450 if ( paths.length ) {
452 // 'distpaths' is declared at the top of the
453 // module.exports function scope. It is an array
454 // of default files that jQuery creates
455 distpaths.forEach(function( filename ) {
456 paths.forEach(function( path ) {
459 if ( !/\/$/.test( path ) ) {
463 created = path + filename.replace( "dist/", "" );
465 file.write( created, file.read(filename) );
467 log.writeln( "File '" + created + "' created." );