Do not watch the dist files to make infinite change loops less common.
[jquery.git] / grunt.js
blobe05f06724150c2735a46fb144998fc0176a40caa
1 /**
2  * Resources
3  *
4  * https://gist.github.com/2489540
5  *
6  */
8 /*jshint node: true */
9 /*global config:true, task:true, process:true*/
10 module.exports = function( grunt ) {
12         // readOptionalJSON
13         // by Ben Alman
14         // https://gist.github.com/2876125
15         function readOptionalJSON( filepath ) {
16                 var data = {};
17                 try {
18                         data = grunt.file.readJSON( filepath );
19                         grunt.verbose.write( "Reading " + filepath + "..." ).ok();
20                 } catch(e) {}
21                 return data;
22         }
24         var task = grunt.task;
25         var file = grunt.file;
26         var utils = grunt.utils;
27         var log = grunt.log;
28         var verbose = grunt.verbose;
29         var fail = grunt.fail;
30         var option = grunt.option;
31         var config = grunt.config;
32         var template = grunt.template;
33         var distpaths = [
34                 "dist/jquery.js",
35                 "dist/jquery.min.js"
36         ];
38         grunt.initConfig({
39                 pkg: "<json:package.json>",
40                 dst: readOptionalJSON("dist/.destination.json"),
41                 meta: {
42                         banner: "/*! jQuery v@<%= pkg.version %> jquery.com | jquery.org/license */"
43                 },
44                 compare_size: {
45                         files: distpaths
46                 },
47                 selector: {
48                         "src/selector.js": [
49                                 "src/sizzle-jquery.js",
50                                 "src/sizzle/sizzle.js"
51                         ]
52                 },
53                 build: {
54                         "dist/jquery.js": [
55                                 "src/intro.js",
56                                 "src/core.js",
57                                 "src/callbacks.js",
58                                 "src/deferred.js",
59                                 "src/support.js",
60                                 "src/data.js",
61                                 "src/queue.js",
62                                 "src/attributes.js",
63                                 "src/event.js",
64                                 "src/selector.js",
65                                 "src/traversing.js",
66                                 "src/manipulation.js",
68                                 { flag: "deprecated", src: "src/deprecated.js" },
69                                 { flag: "css", src: "src/css.js" },
70                                 "src/serialize.js",
71                                 { flag: "ajax", src: "src/ajax.js" },
72                                 { flag: "ajax/jsonp", src: "src/ajax/jsonp.js", needs: [ "ajax", "ajax/script" ]  },
73                                 { flag: "ajax/script", src: "src/ajax/script.js", needs: ["ajax"]  },
74                                 { flag: "ajax/xhr", src: "src/ajax/xhr.js", needs: ["ajax"]  },
75                                 { flag: "effects", src: "src/effects.js", needs: ["css"] },
76                                 { flag: "offset", src: "src/offset.js", needs: ["css"] },
77                                 { flag: "dimensions", src: "src/dimensions.js", needs: ["css"] },
79                                 "src/exports.js",
80                                 "src/outro.js"
81                         ]
82                 },
83                 min: {
84                         "dist/jquery.min.js": [ "<banner>", "dist/jquery.js" ]
85                 },
87                 lint: {
88                         dist: "dist/jquery.js",
89                         grunt: "grunt.js",
90                         tests: "test/unit/**/*.js"
91                 },
93                 jshint: (function() {
94                         function jshintrc( path ) {
95                                 return readOptionalJSON( (path || "") + ".jshintrc" ) || {};
96                         }
98                         return {
99                                 options: jshintrc(),
100                                 dist: jshintrc( "src/" ),
101                                 tests: jshintrc( "test/" )
102                         };
103                 })(),
105                 qunit: {
106                         files: "test/index.html"
107                 },
108                 watch: {
109                         files: [
110                                 "<config:lint.grunt>", "<config:lint.tests>",
111                                 "src/**/*.js"
112                         ],
113                         tasks: "dev"
114                 },
115                 uglify: {}
116         });
118         // Default grunt.
119         grunt.registerTask( "default", "submodules selector build:*:* lint min dist:* compare_size" );
121         // Short list as a high frequency watch task
122         grunt.registerTask( "dev", "selector build:*:* lint" );
124         // Load grunt tasks from NPM packages
125         grunt.loadNpmTasks( "grunt-compare-size" );
126         grunt.loadNpmTasks( "grunt-git-authors" );
128         grunt.registerTask( "testswarm", function( commit, configFile ) {
129                 var testswarm = require( "testswarm" ),
130                         testUrls = [],
131                         config = grunt.file.readJSON( configFile ).jquery,
132                         tests = "ajax attributes callbacks core css data deferred dimensions effects event manipulation offset queue serialize support traversing Sizzle".split(" ");
134                 tests.forEach(function( test ) {
135                         testUrls.push( config.testUrl + commit + "/test/index.html?module=" + test );
136                 });
138                 testswarm({
139                         url: config.swarmUrl,
140                         pollInterval: 10000,
141                         timeout: 1000 * 60 * 30,
142                         done: this.async()
143                 }, {
144                         authUsername: config.authUsername,
145                         authToken: config.authToken,
146                         jobName: 'jQuery commit #<a href="https://github.com/jquery/jquery/commit/' + commit + '">' + commit.substr( 0, 10 ) + '</a>',
147                         runMax: config.runMax,
148                         "runNames[]": tests,
149                         "runUrls[]": testUrls,
150                         "browserSets[]": ["popular"]
151                 });
152         });
154         // Build src/selector.js
155         grunt.registerMultiTask( "selector", "Build src/selector.js", function() {
157                 var name = this.file.dest,
158                                 files = this.file.src,
159                                 sizzle = {
160                                         api: file.read( files[0] ),
161                                         src: file.read( files[1] )
162                                 },
163                                 compiled, parts;
165                 /**
167                         sizzle-jquery.js -> sizzle between "EXPOSE" blocks,
168                         replace define & window.Sizzle assignment
171                         // EXPOSE
172                         if ( typeof define === "function" && define.amd ) {
173                                 define(function() { return Sizzle; });
174                         } else {
175                                 window.Sizzle = Sizzle;
176                         }
177                         // EXPOSE
179                         Becomes...
181                         Sizzle.attr = jQuery.attr;
182                         jQuery.find = Sizzle;
183                         jQuery.expr = Sizzle.selectors;
184                         jQuery.expr[":"] = jQuery.expr.pseudos;
185                         jQuery.unique = Sizzle.uniqueSort;
186                         jQuery.text = Sizzle.getText;
187                         jQuery.isXMLDoc = Sizzle.isXML;
188                         jQuery.contains = Sizzle.contains;
190                  */
192                 // Break into 3 pieces
193                 parts = sizzle.src.split("// EXPOSE");
194                 // Replace the if/else block with api
195                 parts[1] = sizzle.api;
196                 // Rejoin the pieces
197                 compiled = parts.join("");
200                 verbose.write("Injected sizzle-jquery.js into sizzle.js");
202                 // Write concatenated source to file
203                 file.write( name, compiled );
205                 // Fail task if errors were logged.
206                 if ( this.errorCount ) {
207                         return false;
208                 }
210                 // Otherwise, print a success message.
211                 log.writeln( "File '" + name + "' created." );
212         });
215         // Special "alias" task to make custom build creation less grawlix-y
216         grunt.registerTask( "custom", function() {
217                 var done = this.async(),
218                                 args = [].slice.call(arguments),
219                                 modules = args.length ? args[0].replace(/,/g, ":") : "";
222                 // Translation example
223                 //
224                 //   grunt custom:+ajax,-dimensions,-effects,-offset
225                 //
226                 // Becomes:
227                 //
228                 //   grunt build:*:*:+ajax:-dimensions:-effects:-offset
230                 grunt.log.writeln( "Creating custom build...\n" );
232                 grunt.utils.spawn({
233                         cmd: "grunt",
234                         args: [ "build:*:*:" + modules, "min" ]
235                 }, function( err, result ) {
236                         if ( err ) {
237                                 grunt.verbose.error();
238                                 done( err );
239                                 return;
240                         }
242                         grunt.log.writeln( result.replace("Done, without errors.", "") );
244                         done();
245                 });
246         });
248         // Special concat/build task to handle various jQuery build requirements
249         //
250         grunt.registerMultiTask(
251                 "build",
252                 "Concatenate source (include/exclude modules with +/- flags), embed date/version",
253                 function() {
254                         // Concat specified files.
255                         var i,
256                                 compiled = "",
257                                 modules = this.flags,
258                                 explicit = Object.keys(modules).length > 1,
259                                 optIn = !modules["*"],
260                                 name = this.file.dest,
261                                 excluded = {},
262                                 version = config( "pkg.version" ),
263                                 excluder = function( flag, needsFlag ) {
264                                         // explicit > implicit, so set this first and let it be overridden by explicit
265                                         if ( optIn && !modules[ flag ] && !modules[ "+" + flag ] ) {
266                                                 excluded[ flag ] = false;
267                                         }
269                                         if ( excluded[ needsFlag ] || modules[ "-" + flag ] ) {
270                                                 // explicit exclusion from flag or dependency
271                                                 excluded[ flag ] = true;
272                                         } else if ( modules[ "+" + flag ] && ( excluded[ needsFlag ] === false ) ) {
273                                                 // explicit inclusion from flag or dependency overriding a weak inclusion
274                                                 delete excluded[ needsFlag ];
275                                         }
276                                 };
278                         // append commit id to version
279                         if ( process.env.COMMIT ) {
280                                 version += " " + process.env.COMMIT;
281                         }
283                         // figure out which files to exclude based on these rules in this order:
284                         //  explicit > implicit (explicit also means a dependency/dependent that was explicit)
285                         //  exclude > include
286                         // examples:
287                         //  *:                 none (implicit exclude)
288                         //  *:*                all (implicit include)
289                         //  *:*:-effects       all except effects (explicit > implicit)
290                         //  *:*:-css           all except css and its deps (explicit)
291                         //  *:*:-css:+effects  all except css and its deps (explicit exclude from dep. trumps explicit include)
292                         //  *:+effects         none except effects and its deps (explicit include from dep. trumps implicit exclude)
293                         this.file.src.forEach(function( filepath ) {
294                                 var flag = filepath.flag;
296                                 if ( flag ) {
298                                         excluder(flag);
300                                         // check for dependencies
301                                         if ( filepath.needs ) {
302                                                 filepath.needs.forEach(function( needsFlag ) {
303                                                         excluder( flag, needsFlag );
304                                                 });
305                                         }
306                                 }
307                         });
309                         // append excluded modules to version
310                         if ( Object.keys( excluded ).length ) {
311                                 version += " -" + Object.keys( excluded ).join( ",-" );
312                                 // set pkg.version to version with excludes, so minified file picks it up
313                                 grunt.config.set( "pkg.version", version );
314                         }
317                         // conditionally concatenate source
318                         this.file.src.forEach(function( filepath ) {
319                                 var flag = filepath.flag,
320                                                 specified = false,
321                                                 omit = false,
322                                                 message = "";
324                                 if ( flag ) {
325                                         if ( excluded[ flag ] !== undefined ) {
326                                                 message = ( "Excluding " + flag ).red;
327                                                 specified = true;
328                                                 omit = true;
329                                         } else {
330                                                 message = ( "Including " + flag ).green;
332                                                 // If this module was actually specified by the
333                                                 // builder, then st the flag to include it in the
334                                                 // output list
335                                                 if ( modules[ "+" + flag ] ) {
336                                                         specified = true;
337                                                 }
338                                         }
340                                         // Only display the inclusion/exclusion list when handling
341                                         // an explicit list.
342                                         //
343                                         // Additionally, only display modules that have been specified
344                                         // by the user
345                                         if ( explicit && specified ) {
346                                                 grunt.log.writetableln([ 27, 30 ], [
347                                                         message,
348                                                         ( "(" + filepath.src + ")").grey
349                                                 ]);
350                                         }
352                                         filepath = filepath.src;
353                                 }
355                                 if ( !omit ) {
356                                         compiled += file.read( filepath );
357                                 }
358                         });
360                         // Embed Date
361                         // Embed Version
362                         compiled = compiled.replace( "@DATE", new Date() )
363                                 .replace( /@VERSION/g, version );
365                         // Write concatenated source to file
366                         file.write( name, compiled );
368                         // Fail task if errors were logged.
369                         if ( this.errorCount ) {
370                                 return false;
371                         }
373                         // Otherwise, print a success message.
374                         log.writeln( "File '" + name + "' created." );
375                 });
377         grunt.registerTask( "submodules", function() {
378                 var done = this.async();
380                 grunt.verbose.write( "Updating submodules..." );
382                 // TODO: migrate remaining `make` to grunt tasks
383                 //
384                 grunt.utils.spawn({
385                         cmd: "make"
386                 }, function( err, result ) {
387                         if ( err ) {
388                                 grunt.verbose.error();
389                                 done( err );
390                                 return;
391                         }
393                         grunt.log.writeln( result );
395                         done();
396                 });
397         });
399         // Allow custom dist file locations
400         grunt.registerTask( "dist", function() {
401                 var flags, paths, stored;
403                 // Check for stored destination paths
404                 // ( set in dist/.destination.json )
405                 stored = Object.keys( config("dst") );
407                 // Allow command line input as well
408                 flags = Object.keys( this.flags );
410                 // Combine all output target paths
411                 paths = [].concat( stored, flags ).filter(function( path ) {
412                         return path !== "*";
413                 });
416                 // Proceed only if there are actual
417                 // paths to write to
418                 if ( paths.length ) {
420                         // 'distpaths' is declared at the top of the
421                         // module.exports function scope. It is an array
422                         // of default files that jQuery creates
423                         distpaths.forEach(function( filename ) {
424                                 paths.forEach(function( path ) {
425                                         var created;
427                                         if ( !/\/$/.test( path ) ) {
428                                                 path += "/";
429                                         }
431                                         created = path + filename.replace( "dist/", "" );
433                                         if ( !/^\//.test( path ) ) {
434                                                 log.error( "File '" + created + "' was NOT created." );
435                                                 return;
436                                         }
438                                         file.write( created, file.read(filename) );
440                                         log.writeln( "File '" + created + "' created." );
441                                 });
442                         });
443                 }
444         });