grunt.js 13 KB

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