Gruntfile.js 13 KB

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