Latest JSHint gets cranky with the _ vars in catch() {}
[jquery.git] / grunt.js
blob2ddb3001ce48ca93e005e9e7e8de201e43e35805
1 /**
2  * Resources
3  *
4  * https://gist.github.com/2489540
5  *
6  */
8 /*jshint node: true */
9 /*global config:true, task:true, process:true*/
11 var child_process = require("child_process");
13 module.exports = function( grunt ) {
15         // readOptionalJSON
16         // by Ben Alman
17         // https://gist.github.com/2876125
18         function readOptionalJSON( filepath ) {
19                 var data = {};
20                 try {
21                         data = grunt.file.readJSON( filepath );
22                         grunt.verbose.write( "Reading " + filepath + "..." ).ok();
23                 } catch(e) {}
24                 return data;
25         }
27         var task = grunt.task;
28         var file = grunt.file;
29         var utils = grunt.utils;
30         var log = grunt.log;
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;
36         var distpaths = [
37                 "dist/jquery.js",
38                 "dist/jquery.min.js"
39         ];
41         grunt.initConfig({
42                 pkg: "<json:package.json>",
43                 dst: readOptionalJSON("dist/.destination.json"),
44                 meta: {
45                         banner: "/*! jQuery v<%= pkg.version %> jquery.com | jquery.org/license */"
46                 },
47                 compare_size: {
48                         files: distpaths
49                 },
50                 selector: {
51                         "src/selector.js": [
52                                 "src/sizzle-jquery.js",
53                                 "src/sizzle/sizzle.js"
54                         ]
55                 },
56                 build: {
57                         "dist/jquery.js": [
58                                 "src/intro.js",
59                                 "src/core.js",
60                                 "src/callbacks.js",
61                                 "src/deferred.js",
62                                 "src/support.js",
63                                 "src/data.js",
64                                 "src/queue.js",
65                                 "src/attributes.js",
66                                 "src/event.js",
67                                 "src/selector.js",
68                                 "src/traversing.js",
69                                 "src/manipulation.js",
71                                 { flag: "css", src: "src/css.js" },
72                                 "src/serialize.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" },
82                                 "src/exports.js",
83                                 "src/outro.js"
84                         ]
85                 },
86                 min: {
87                         "dist/jquery.min.js": [ "<banner>", "dist/jquery.js" ]
88                 },
90                 lint: {
91                         dist: "dist/jquery.js",
92                         grunt: "grunt.js",
93                         tests: "test/unit/**/*.js"
94                 },
96                 jshint: (function() {
97                         function jshintrc( path ) {
98                                 return readOptionalJSON( (path || "") + ".jshintrc" ) || {};
99                         }
101                         return {
102                                 options: jshintrc(),
103                                 dist: jshintrc( "src/" ),
104                                 tests: jshintrc( "test/" )
105                         };
106                 })(),
108                 qunit: {
109                         files: "test/index.html"
110                 },
111                 watch: {
112                         files: [
113                                 "<config:lint.grunt>", "<config:lint.tests>",
114                                 "src/**/*.js"
115                         ],
116                         tasks: "dev"
117                 },
118                 uglify: {
119                         codegen: {
120                                 ascii_only: true
121                         }
122                 }
123         });
125         // Default grunt.
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" ),
138                         testUrls = [],
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 );
144                 });
146                 testswarm({
147                         url: config.swarmUrl,
148                         pollInterval: 10000,
149                         timeout: 1000 * 60 * 30,
150                         done: this.async()
151                 }, {
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,
156                         "runNames[]": tests,
157                         "runUrls[]": testUrls,
158                         "browserSets[]": ["popular"]
159                 });
160         });
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,
167                                 sizzle = {
168                                         api: file.read( files[0] ),
169                                         src: file.read( files[1] )
170                                 },
171                                 compiled, parts;
173                 /**
175                         sizzle-jquery.js -> sizzle between "EXPOSE" blocks,
176                         replace define & window.Sizzle assignment
179                         // EXPOSE
180                         if ( typeof define === "function" && define.amd ) {
181                                 define(function() { return Sizzle; });
182                         } else {
183                                 window.Sizzle = Sizzle;
184                         }
185                         // EXPOSE
187                         Becomes...
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;
198                  */
200                 // Break into 3 pieces
201                 parts = sizzle.src.split("// EXPOSE");
202                 // Replace the if/else block with api
203                 parts[1] = sizzle.api;
204                 // Rejoin the pieces
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 ) {
215                         return false;
216                 }
218                 // Otherwise, print a success message.
219                 log.writeln( "File '" + name + "' created." );
220         });
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
231                 //
232                 //   grunt custom:+ajax,-dimensions,-effects,-offset
233                 //
234                 // Becomes:
235                 //
236                 //   grunt build:*:*:+ajax:-dimensions:-effects:-offset
238                 grunt.log.writeln( "Creating custom build...\n" );
240                 grunt.utils.spawn({
241                         cmd: "grunt",
242                         args: [ "build:*:*:" + modules, "min" ]
243                 }, function( err, result ) {
244                         if ( err ) {
245                                 grunt.verbose.error();
246                                 done( err );
247                                 return;
248                         }
250                         grunt.log.writeln( result.replace("Done, without errors.", "") );
252                         done();
253                 });
254         });
256         // Special concat/build task to handle various jQuery build requirements
257         //
258         grunt.registerMultiTask(
259                 "build",
260                 "Concatenate source (include/exclude modules with +/- flags), embed date/version",
261                 function() {
262                         // Concat specified files.
263                         var i,
264                                 compiled = "",
265                                 modules = this.flags,
266                                 explicit = Object.keys(modules).length > 1,
267                                 optIn = !modules["*"],
268                                 name = this.file.dest,
269                                 excluded = {},
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;
275                                         }
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 ];
283                                         }
284                                 };
286                         // append commit id to version
287                         if ( process.env.COMMIT ) {
288                                 version += " " + process.env.COMMIT;
289                         }
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)
293                         //  exclude > include
294                         // examples:
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;
304                                 if ( flag ) {
306                                         excluder(flag);
308                                         // check for dependencies
309                                         if ( filepath.needs ) {
310                                                 filepath.needs.forEach(function( needsFlag ) {
311                                                         excluder( flag, needsFlag );
312                                                 });
313                                         }
314                                 }
315                         });
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 );
322                         }
325                         // conditionally concatenate source
326                         this.file.src.forEach(function( filepath ) {
327                                 var flag = filepath.flag,
328                                                 specified = false,
329                                                 omit = false,
330                                                 message = "";
332                                 if ( flag ) {
333                                         if ( excluded[ flag ] !== undefined ) {
334                                                 message = ( "Excluding " + flag ).red;
335                                                 specified = true;
336                                                 omit = true;
337                                         } else {
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
342                                                 // output list
343                                                 if ( modules[ "+" + flag ] ) {
344                                                         specified = true;
345                                                 }
346                                         }
348                                         // Only display the inclusion/exclusion list when handling
349                                         // an explicit list.
350                                         //
351                                         // Additionally, only display modules that have been specified
352                                         // by the user
353                                         if ( explicit && specified ) {
354                                                 grunt.log.writetableln([ 27, 30 ], [
355                                                         message,
356                                                         ( "(" + filepath.src + ")").grey
357                                                 ]);
358                                         }
360                                         filepath = filepath.src;
361                                 }
363                                 if ( !omit ) {
364                                         compiled += file.read( filepath );
365                                 }
366                         });
368                         // Embed Version
369                         // Embed Date
370                         compiled = compiled.replace( /@VERSION/g, version )
371                                 .replace( "@DATE", function () {
372                                         var date = new Date();
374                                         // YYYY-MM-DD
375                                         return [
376                                                 date.getFullYear(),
377                                                 date.getMonth() + 1,
378                                                 date.getDate()
379                                         ].join( "-" );
380                                 });
382                         // Write concatenated source to file
383                         file.write( name, compiled );
385                         // Fail task if errors were logged.
386                         if ( this.errorCount ) {
387                                 return false;
388                         }
390                         // Otherwise, print a success message.
391                         log.writeln( "File '" + name + "' created." );
392                 });
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 ) {
407                         return path !== "*";
408                 });
410                 // Ensure the dist files are pure ASCII
411                 var fs = require("fs"),
412                         nonascii = false;
413                 distpaths.forEach(function( filename ) {
414                         var buf = fs.readFileSync( filename, "utf8" ),
415                         i, c;
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 );
420                                         if ( c > 127 ) {
421                                                 log.writeln( "- position " + i + ": " + c );
422                                                 log.writeln( "-- " + buf.substring( i - 20, i + 20 ) );
423                                                 nonascii = true;
424                                         }
425                                 }
426                         }
427                 });
428                 if ( nonascii ) {
429                         return false;
430                 }
432                 // Proceed only if there are actual
433                 // paths to write to
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 ) {
441                                         var created;
443                                         if ( !/\/$/.test( path ) ) {
444                                                 path += "/";
445                                         }
447                                         created = path + filename.replace( "dist/", "" );
449                                         if ( !/^\//.test( path ) ) {
450                                                 log.error( "File '" + created + "' was NOT created." );
451                                                 return;
452                                         }
454                                         file.write( created, file.read(filename) );
456                                         log.writeln( "File '" + created + "' created." );
457                                 });
458                         });
459                 }
460         });