Simplify data-* attr lookup with camelKey
[jquery.git] / Gruntfile.js
blob7ff43035e2594d6e9182441679a09c75ce60f4d4
1 module.exports = function( grunt ) {
3         "use strict";
5         var distpaths = [
6                         "dist/jquery.js",
7                         "dist/jquery.min.map",
8                         "dist/jquery.min.js"
9                 ],
10                 readOptionalJSON = function( filepath ) {
11                         var data = {};
12                         try {
13                                 data = grunt.file.readJSON( filepath );
14                         } catch(e) {}
15                         return data;
16                 };
18         grunt.initConfig({
19                 pkg: grunt.file.readJSON("package.json"),
20                 dst: readOptionalJSON("dist/.destination.json"),
21                 compare_size: {
22                         files: distpaths
23                 },
24                 selector: {
25                         destFile: "src/selector-sizzle.js",
26                         apiFile: "src/sizzle-jquery.js",
27                         srcFile: "src/sizzle/sizzle.js"
28                 },
29                 build: {
30                         all:{
31                                 dest: "dist/jquery.js",
32                                 src: [
33                                         "src/intro.js",
34                                         "src/core.js",
35                                         "src/callbacks.js",
36                                         "src/deferred.js",
37                                         "src/support.js",
38                                         "src/data.js",
39                                         "src/queue.js",
40                                         "src/attributes.js",
41                                         "src/event.js",
42                                         { flag: "sizzle", src: "src/selector-sizzle.js", alt: "src/selector-native.js" },
43                                         "src/traversing.js",
44                                         "src/manipulation.js",
45                                         { flag: "css", src: "src/css.js" },
46                                         "src/serialize.js",
47                                         { flag: "event-alias", src: "src/event-alias.js" },
48                                         { flag: "ajax", src: "src/ajax.js" },
49                                         { flag: "ajax/script", src: "src/ajax/script.js", needs: ["ajax"]  },
50                                         { flag: "ajax/jsonp", src: "src/ajax/jsonp.js", needs: [ "ajax", "ajax/script" ]  },
51                                         { flag: "ajax/xhr", src: "src/ajax/xhr.js", needs: ["ajax"]  },
52                                         { flag: "effects", src: "src/effects.js", needs: ["css"] },
53                                         { flag: "offset", src: "src/offset.js", needs: ["css"] },
54                                         { flag: "dimensions", src: "src/dimensions.js", needs: ["css"] },
55                                         { flag: "deprecated", src: "src/deprecated.js" },
57                                         "src/exports.js",
58                                         "src/outro.js"
59                                 ]
60                         }
61                 },
63                 jshint: {
64                         dist: {
65                                 src: [ "dist/jquery.js" ],
66                                 options: {
67                                         jshintrc: "src/.jshintrc"
68                                 }
69                         },
70                         grunt: {
71                                 src: [ "Gruntfile.js" ],
72                                 options: {
73                                         jshintrc: ".jshintrc"
74                                 }
75                         },
76                         tests: {
77                                 // TODO: Once .jshintignore is supported, use that instead.
78                                 // issue located here: https://github.com/gruntjs/grunt-contrib-jshint/issues/1
79                                 src: [ "test/data/{test,testinit,testrunner}.js", "test/unit/**/*.js" ],
80                                 options: {
81                                         jshintrc: "test/.jshintrc"
82                                 }
83                         }
84                 },
86                 testswarm: {
87                         tests: "ajax attributes callbacks core css data deferred dimensions effects event manipulation offset queue selector serialize support traversing Sizzle".split(" ")
88                 },
90                 watch: {
91                         files: [ "<%= jshint.grunt.src %>", "<%= jshint.tests.src %>", "src/**/*.js" ],
92                         tasks: "dev"
93                 },
95                 uglify: {
96                         all: {
97                                 files: {
98                                         "dist/jquery.min.js": [ "dist/jquery.js" ]
99                                 },
100                                 options: {
101                                         banner: "/*! jQuery v<%= pkg.version %> | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license */",
102                                         sourceMap: "dist/jquery.min.map",
103                                         beautify: {
104                                                 ascii_only: true
105                                         }
106                                 }
107                         }
108                 }
109         });
111         grunt.registerTask( "testswarm", function( commit, configFile ) {
112                 var jobName,
113                         testswarm = require( "testswarm" ),
114                         testUrls = [],
115                         pull = /PR-(\d+)/.exec( commit ),
116                         config = grunt.file.readJSON( configFile ).jquery,
117                         tests = grunt.config([ this.name, "tests" ]);
119                 if ( pull ) {
120                         jobName = "jQuery pull <a href='https://github.com/jquery/jquery/pull/" +
121                                 pull[ 1 ] + "'>#" + pull[ 1 ] + "</a>";
122                 } else {
123                         jobName = "jQuery commit #<a href='https://github.com/jquery/jquery/commit/" +
124                                 commit + "'>" + commit.substr( 0, 10 ) + "</a>";
125                 }
127                 tests.forEach(function( test ) {
128                         testUrls.push( config.testUrl + commit + "/test/index.html?module=" + test );
129                 });
131                 testswarm({
132                         url: config.swarmUrl,
133                         pollInterval: 10000,
134                         timeout: 1000 * 60 * 30,
135                         done: this.async()
136                 }, {
137                         authUsername: config.authUsername,
138                         authToken: config.authToken,
139                         jobName: jobName,
140                         runMax: config.runMax,
141                         "runNames[]": tests,
142                         "runUrls[]": testUrls,
143                         "browserSets[]": "popular-no-old-ie"
144                 });
145         });
147         grunt.registerTask( "selector", "Build Sizzle-based selector module", function() {
149                 var cfg = grunt.config("selector"),
150                         name = cfg.destFile,
151                         sizzle = {
152                                 api: grunt.file.read( cfg.apiFile ),
153                                 src: grunt.file.read( cfg.srcFile )
154                         },
155                         compiled, parts;
157                 /**
159                         sizzle-jquery.js -> sizzle between "EXPOSE" blocks,
160                         replace define & window.Sizzle assignment
163                         // EXPOSE
164                         if ( typeof define === "function" && define.amd ) {
165                                 define(function() { return Sizzle; });
166                         } else {
167                                 window.Sizzle = Sizzle;
168                         }
169                         // EXPOSE
171                         Becomes...
173                         Sizzle.attr = jQuery.attr;
174                         jQuery.find = Sizzle;
175                         jQuery.expr = Sizzle.selectors;
176                         jQuery.expr[":"] = jQuery.expr.pseudos;
177                         jQuery.unique = Sizzle.uniqueSort;
178                         jQuery.text = Sizzle.getText;
179                         jQuery.isXMLDoc = Sizzle.isXML;
180                         jQuery.contains = Sizzle.contains;
182                  */
184                 // Break into 3 pieces
185                 parts = sizzle.src.split("// EXPOSE");
186                 // Replace the if/else block with api
187                 parts[1] = sizzle.api;
188                 // Rejoin the pieces
189                 compiled = parts.join("");
191                 grunt.verbose.writeln("Injected " + cfg.apiFile + " into " + cfg.srcFile);
193                 // Write concatenated source to file, and ensure newline-only termination
194                 grunt.file.write( name, compiled.replace( /\x0d\x0a/g, "\x0a" ) );
196                 // Fail task if errors were logged.
197                 if ( this.errorCount ) {
198                         return false;
199                 }
201                 // Otherwise, print a success message.
202                 grunt.log.writeln( "File '" + name + "' created." );
203         });
206         // Special "alias" task to make custom build creation less grawlix-y
207         grunt.registerTask( "custom", function() {
208                 var done = this.async(),
209                                 args = [].slice.call(arguments),
210                                 modules = args.length ? args[0].replace(/,/g, ":") : "";
213                 // Translation example
214                 //
215                 //   grunt custom:+ajax,-dimensions,-effects,-offset
216                 //
217                 // Becomes:
218                 //
219                 //   grunt build:*:*:+ajax:-dimensions:-effects:-offset
221                 grunt.log.writeln( "Creating custom build...\n" );
223                 grunt.util.spawn({
224                         cmd: process.platform === "win32" ? "grunt.cmd" : "grunt",
225                         args: [ "build:*:*:" + modules, "uglify", "dist" ]
226                 }, function( err, result ) {
227                         if ( err ) {
228                                 grunt.verbose.error();
229                                 done( err );
230                                 return;
231                         }
233                         grunt.log.writeln( result.stdout.replace("Done, without errors.", "") );
235                         done();
236                 });
237         });
239         // Special concat/build task to handle various jQuery build requirements
240         //
241         grunt.registerMultiTask(
242                 "build",
243                 "Concatenate source (include/exclude modules with +/- flags), embed date/version",
244                 function() {
246                         // Concat specified files.
247                         var compiled = "",
248                                 modules = this.flags,
249                                 optIn = !modules["*"],
250                                 explicit = optIn || Object.keys(modules).length > 1,
251                                 name = this.data.dest,
252                                 src = this.data.src,
253                                 deps = {},
254                                 excluded = {},
255                                 version = grunt.config( "pkg.version" ),
256                                 excluder = function( flag, needsFlag ) {
257                                         // optIn defaults implicit behavior to weak exclusion
258                                         if ( optIn && !modules[ flag ] && !modules[ "+" + flag ] ) {
259                                                 excluded[ flag ] = false;
260                                         }
262                                         // explicit or inherited strong exclusion
263                                         if ( excluded[ needsFlag ] || modules[ "-" + flag ] ) {
264                                                 excluded[ flag ] = true;
266                                         // explicit inclusion overrides weak exclusion
267                                         } else if ( excluded[ needsFlag ] === false &&
268                                                 ( modules[ flag ] || modules[ "+" + flag ] ) ) {
270                                                 delete excluded[ needsFlag ];
272                                                 // ...all the way down
273                                                 if ( deps[ needsFlag ] ) {
274                                                         deps[ needsFlag ].forEach(function( subDep ) {
275                                                                 modules[ needsFlag ] = true;
276                                                                 excluder( needsFlag, subDep );
277                                                         });
278                                                 }
279                                         }
280                                 };
282                         // append commit id to version
283                         if ( process.env.COMMIT ) {
284                                 version += " " + process.env.COMMIT;
285                         }
287                         // figure out which files to exclude based on these rules in this order:
288                         //  dependency explicit exclude
289                         //  > explicit exclude
290                         //  > explicit include
291                         //  > dependency implicit exclude
292                         //  > implicit exclude
293                         // examples:
294                         //  *                  none (implicit exclude)
295                         //  *:*                all (implicit include)
296                         //  *:*:-css           all except css and dependents (explicit > implicit)
297                         //  *:*:-css:+effects  same (excludes effects because explicit include is trumped by explicit exclude of dependency)
298                         //  *:+effects         none except effects and its dependencies (explicit include trumps implicit exclude of dependency)
299                         src.forEach(function( filepath ) {
300                                 var flag = filepath.flag;
302                                 if ( flag ) {
304                                         excluder(flag);
306                                         // check for dependencies
307                                         if ( filepath.needs ) {
308                                                 deps[ flag ] = filepath.needs;
309                                                 filepath.needs.forEach(function( needsFlag ) {
310                                                         excluder( flag, needsFlag );
311                                                 });
312                                         }
313                                 }
314                         });
316                         // append excluded modules to version
317                         if ( Object.keys( excluded ).length ) {
318                                 version += " -" + Object.keys( excluded ).join( ",-" );
319                                 // set pkg.version to version with excludes, so minified file picks it up
320                                 grunt.config.set( "pkg.version", version );
321                         }
324                         // conditionally concatenate source
325                         src.forEach(function( filepath ) {
326                                 var flag = filepath.flag,
327                                                 specified = false,
328                                                 omit = false,
329                                                 messages = [];
331                                 if ( flag ) {
332                                         if ( excluded[ flag ] !== undefined ) {
333                                                 messages.push([
334                                                         ( "Excluding " + flag ).red,
335                                                         ( "(" + filepath.src + ")" ).grey
336                                                 ]);
337                                                 specified = true;
338                                                 omit = !filepath.alt;
339                                                 if ( !omit ) {
340                                                         flag += " alternate";
341                                                         filepath.src = filepath.alt;
342                                                 }
343                                         }
344                                         if ( excluded[ flag ] === undefined ) {
345                                                 messages.push([
346                                                         ( "Including " + flag ).green,
347                                                         ( "(" + filepath.src + ")" ).grey
348                                                 ]);
350                                                 // If this module was actually specified by the
351                                                 // builder, then set the flag to include it in the
352                                                 // output list
353                                                 if ( modules[ "+" + flag ] ) {
354                                                         specified = true;
355                                                 }
356                                         }
358                                         filepath = filepath.src;
360                                         // Only display the inclusion/exclusion list when handling
361                                         // an explicit list.
362                                         //
363                                         // Additionally, only display modules that have been specified
364                                         // by the user
365                                         if ( explicit && specified ) {
366                                                 messages.forEach(function( message ) {
367                                                         grunt.log.writetableln( [ 27, 30 ], message );
368                                                 });
369                                         }
370                                 }
372                                 if ( !omit ) {
373                                         compiled += grunt.file.read( filepath );
374                                 }
375                         });
377                         // Embed Version
378                         // Embed Date
379                         compiled = compiled.replace( /@VERSION/g, version )
380                                 .replace( "@DATE", function () {
381                                         var date = new Date();
383                                         // YYYY-MM-DD
384                                         return [
385                                                 date.getFullYear(),
386                                                 date.getMonth() + 1,
387                                                 date.getDate()
388                                         ].join( "-" );
389                                 });
391                         // Write concatenated source to file
392                         grunt.file.write( name, compiled );
394                         // Fail task if errors were logged.
395                         if ( this.errorCount ) {
396                                 return false;
397                         }
399                         // Otherwise, print a success message.
400                         grunt.log.writeln( "File '" + name + "' created." );
401                 });
403         // Process files for distribution
404         grunt.registerTask( "dist", function() {
405                 var flags, paths, stored;
407                 // Check for stored destination paths
408                 // ( set in dist/.destination.json )
409                 stored = Object.keys( grunt.config("dst") );
411                 // Allow command line input as well
412                 flags = Object.keys( this.flags );
414                 // Combine all output target paths
415                 paths = [].concat( stored, flags ).filter(function( path ) {
416                         return path !== "*";
417                 });
419                 // Ensure the dist files are pure ASCII
420                 var fs = require("fs"),
421                         nonascii = false;
423                 distpaths.forEach(function( filename ) {
424                         var i, c, map,
425                                 text = fs.readFileSync( filename, "utf8" );
427                         // Ensure files use only \n for line endings, not \r\n
428                         if ( /\x0d\x0a/.test( text ) ) {
429                                 grunt.log.writeln( filename + ": Incorrect line endings (\\r\\n)" );
430                                 nonascii = true;
431                         }
433                         // Ensure only ASCII chars so script tags don't need a charset attribute
434                         if ( text.length !== Buffer.byteLength( text, "utf8" ) ) {
435                                 grunt.log.writeln( filename + ": Non-ASCII characters detected:" );
436                                 for ( i = 0; i < text.length; i++ ) {
437                                         c = text.charCodeAt( i );
438                                         if ( c > 127 ) {
439                                                 grunt.log.writeln( "- position " + i + ": " + c );
440                                                 grunt.log.writeln( "-- " + text.substring( i - 20, i + 20 ) );
441                                                 break;
442                                         }
443                                 }
444                                 nonascii = true;
445                         }
447                         // Modify map/min so that it points to files in the same folder;
448                         // see https://github.com/mishoo/UglifyJS2/issues/47
449                         if ( /\.map$/.test( filename ) ) {
450                                 text = text.replace( /"dist\//g, "\"" );
451                                 fs.writeFileSync( filename, text, "utf-8" );
452                         } else if ( /\.min\.js$/.test( filename ) ) {
453                                 // Wrap sourceMap directive in multiline comments (#13274)
454                                 text = text.replace( /\n?(\/\/@\s*sourceMappingURL=)(.*)/,
455                                         function( _, directive, path ) {
456                                                 map = "\n" + directive + path.replace( /^dist\//, "" );
457                                                 return "";
458                                         });
459                                 if ( map ) {
460                                         text = text.replace( /(^\/\*[\w\W]*?)\s*\*\/|$/,
461                                                 function( _, comment ) {
462                                                         return ( comment || "\n/*" ) + map + "\n*/";
463                                                 });
464                                 }
465                                 fs.writeFileSync( filename, text, "utf-8" );
466                         }
468                         // Optionally copy dist files to other locations
469                         paths.forEach(function( path ) {
470                                 var created;
472                                 if ( !/\/$/.test( path ) ) {
473                                         path += "/";
474                                 }
476                                 created = path + filename.replace( "dist/", "" );
477                                 grunt.file.write( created, text );
478                                 grunt.log.writeln( "File '" + created + "' created." );
479                         });
480                 });
482                 return !nonascii;
483         });
485         // Load grunt tasks from NPM packages
486         grunt.loadNpmTasks("grunt-compare-size");
487         grunt.loadNpmTasks("grunt-git-authors");
488         grunt.loadNpmTasks("grunt-update-submodules");
489         grunt.loadNpmTasks("grunt-contrib-watch");
490         grunt.loadNpmTasks("grunt-contrib-jshint");
491         grunt.loadNpmTasks("grunt-contrib-uglify");
493         // Default grunt
494         grunt.registerTask( "default", [ "update_submodules", "selector", "build:*:*", "jshint", "uglify", "dist:*" ] );
496         // Short list as a high frequency watch task
497         grunt.registerTask( "dev", [ "selector", "build:*:*", "jshint" ] );