build.js 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. /**
  2. * Special concat/build task to handle build requirements
  3. * Concats AMD modules, removes their definitions,
  4. * and includes/excludes specified modules
  5. */
  6. module.exports = function( grunt ) {
  7. "use strict";
  8. var fs = require( "fs" ),
  9. requirejs = require( "requirejs" ),
  10. pkg = require( "../package.json" ),
  11. srcFolder = __dirname + "/../src/",
  12. rdefineEnd = /\r?\n\}\s*?\);[^}\w]*$/,
  13. read = function( fileName ) {
  14. return grunt.file.read( srcFolder + fileName );
  15. },
  16. wrapper = read( "wrapper.js" ).split( /\/\/ \@CODE\r?\n\/\/[^\n]+\n/ ),
  17. config = {
  18. baseUrl: "src",
  19. name: pkg.name,
  20. paths: {
  21. jquery : 'https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min'
  22. },
  23. // We have multiple minify steps
  24. optimize: "none",
  25. // Include dependencies loaded with require
  26. findNestedDependencies: true,
  27. // Avoid inserting define() placeholder
  28. skipModuleInsertion: true,
  29. // Avoid breaking semicolons inserted by r.js
  30. skipSemiColonInsertion: true,
  31. wrap: {
  32. start: wrapper[ 0 ].replace( /\/\*jshint .* \*\/\r?\n/, "" ),
  33. end: wrapper[ 1 ]
  34. },
  35. rawText: {},
  36. onBuildWrite: convert
  37. };
  38. /**
  39. * Strip all definitions generated by requirejs
  40. * Convert "var" modules to var declarations
  41. * "var module" means the module only contains a return
  42. * statement that should be converted to a var declaration
  43. * This is indicated by including the file in any "var" folder
  44. * @param {String} name
  45. * @param {String} path
  46. * @param {String} contents The contents to be written (including their AMD wrappers)
  47. */
  48. function convert( name, path, contents ) {
  49. var amdName;
  50. // Convert var modules
  51. if ( /.\/var\//.test( path.replace( process.cwd(), "" ) ) ) {
  52. contents = contents
  53. .replace( /define\([\w\W]*?return undefined\s*;/, " var " + ( /var\/([\w-]+)/.exec( name )[ 1 ] ) + ";" )
  54. .replace( /define\([\w\W]*?return/, " var " + ( /var\/([\w-]+)/.exec( name )[ 1 ] ) + " =" )
  55. .replace( rdefineEnd, "" );
  56. } else if ( /.\/function\//.test( path.replace( process.cwd(), "" ) ) ) {
  57. contents = contents
  58. .replace( /define\([\w\W]*?return function/, " function " + ( /function\/([\w-]+)/.exec( name )[ 1 ] ) + "")
  59. .replace( rdefineEnd, "" );
  60. // Remove empty definitions
  61. contents = contents
  62. .replace( /define\(\[[^\]]*\]\)[\W\r\n]+$/, "" );
  63. } else {
  64. contents = contents
  65. .replace( /\s*return\s+[^\}]+(\}\s*?\);[^\w\}]*)$/, "$1" )
  66. // Multiple exports
  67. .replace( /\s*exports\.\w+\s*=\s*\w+;/g, "" );
  68. // Remove define wrappers, closure ends, and empty declarations
  69. contents = contents
  70. .replace( /define\([^{]*?{/, "" )
  71. .replace( rdefineEnd, "" );
  72. // Remove anything wrapped with
  73. // /* ExcludeStart */ /* ExcludeEnd */
  74. // or a single line directly after a // BuildExclude comment
  75. contents = contents
  76. .replace( /\/\*\s*ExcludeStart\s*\*\/[\w\W]*?\/\*\s*ExcludeEnd\s*\*\//ig, "" )
  77. .replace( /\/\/\s*BuildExclude\r?\n\r?[\w\W]*?\r?\n\r?/ig, "" );
  78. // Remove empty definitions
  79. contents = contents
  80. .replace( /define\(\[[^\]]*\]\)[\W\r\n]+$/, "" );
  81. }
  82. // AMD Name
  83. if ( ( amdName = grunt.option( "amd" ) ) != null && /^exports\/amd$/.test( name ) ) {
  84. // Remove the comma for anonymous defines
  85. contents = contents
  86. .replace( new RegExp('/(\s*)"' + pkg.name + '"(\,\s*)/'),
  87. amdName ? "$1\"" + amdName + "\"$2" : "" );
  88. }
  89. return contents;
  90. }
  91. grunt.registerMultiTask(
  92. "build",
  93. "Concatenate source, remove sub AMD definitions, " +
  94. "(include/exclude modules with +/- flags), embed date/version",
  95. function() {
  96. var flag, index,
  97. done = this.async(),
  98. flags = this.flags,
  99. name = grunt.option( "filename" ),
  100. minimum = this.data.minimum,
  101. removeWith = this.data.removeWith,
  102. excluded = [],
  103. included = [],
  104. version = grunt.config( "pkg.version" ),
  105. /**
  106. * Recursively calls the excluder to remove on all modules in the list
  107. * @param {Array} list
  108. * @param {String} [prepend] Prepend this to the module name.
  109. * Indicates we're walking a directory
  110. */
  111. excludeList = function( list, prepend ) {
  112. if ( list ) {
  113. prepend = prepend ? prepend + "/" : "";
  114. list.forEach( function( module ) {
  115. // Exclude var modules as well
  116. if ( module === "var" ) {
  117. excludeList(
  118. fs.readdirSync( srcFolder + prepend + module ), prepend + module
  119. );
  120. return;
  121. }
  122. if ( prepend ) {
  123. // Skip if this is not a js file and we're walking files in a dir
  124. if ( !( module = /([\w-\/]+)\.js$/.exec( module ) ) ) {
  125. return;
  126. }
  127. // Prepend folder name if passed
  128. // Remove .js extension
  129. module = prepend + module[ 1 ];
  130. }
  131. // Avoid infinite recursion
  132. if ( excluded.indexOf( module ) === -1 ) {
  133. excluder( "-" + module );
  134. }
  135. } );
  136. }
  137. },
  138. /**
  139. * Adds the specified module to the excluded or included list, depending on the flag
  140. * @param {String} flag A module path relative to
  141. * the src directory starting with + or - to indicate
  142. * whether it should included or excluded
  143. */
  144. excluder = function( flag ) {
  145. var m = /^(\+|\-|)([\w\/-]+)$/.exec( flag ),
  146. exclude = m[ 1 ] === "-",
  147. module = m[ 2 ];
  148. if ( exclude ) {
  149. // Can't exclude certain modules
  150. if ( minimum.indexOf( module ) === -1 ) {
  151. // Add to excluded
  152. if ( excluded.indexOf( module ) === -1 ) {
  153. grunt.log.writeln( flag );
  154. excluded.push( module );
  155. // Exclude all files in the folder of the same name
  156. // These are the removable dependencies
  157. // It's fine if the directory is not there
  158. try {
  159. excludeList( fs.readdirSync( srcFolder + module ), module );
  160. } catch ( e ) {
  161. grunt.verbose.writeln( e );
  162. }
  163. }
  164. // Check removeWith list
  165. excludeList( removeWith[ module ] );
  166. } else {
  167. grunt.log.error( "Module \"" + module + "\" is a minimum requirement." );
  168. if ( module === "selector" ) {
  169. grunt.log.error(
  170. "If you meant to replace Sizzle, use -sizzle instead."
  171. );
  172. }
  173. }
  174. } else {
  175. grunt.log.writeln( flag );
  176. included.push( module );
  177. }
  178. };
  179. // Filename can be passed to the command line using
  180. // command line options
  181. name = name ? ( "dist/" + name ) : this.data.dest;
  182. // append commit id to version
  183. if ( process.env.COMMIT ) {
  184. version += " " + process.env.COMMIT;
  185. }
  186. // figure out which files to exclude based on these rules in this order:
  187. // dependency explicit exclude
  188. delete flags[ "*" ];
  189. for ( flag in flags ) {
  190. excluder( flag );
  191. }
  192. grunt.verbose.writeflags( excluded, "Excluded" );
  193. grunt.verbose.writeflags( included, "Included" );
  194. // append excluded modules to version
  195. if ( excluded.length ) {
  196. version += " -" + excluded.join( ",-" );
  197. // set pkg.version to version with excludes, so minified file picks it up
  198. grunt.config.set( "pkg.version", version );
  199. grunt.verbose.writeln( "Version changed to " + version );
  200. // Have to use shallow or core will get excluded since it is a dependency
  201. config.excludeShallow = excluded;
  202. }
  203. config.include = included;
  204. /**
  205. * Handle Final output from the optimizer
  206. * @param {String} compiled
  207. */
  208. config.out = function( compiled ) {
  209. compiled = compiled
  210. // Embed Version
  211. .replace( /@VERSION/g, version )
  212. // Embed Date
  213. // yyyy-mm-ddThh:mmZ
  214. .replace( /@DATE/g, ( new Date() ).toISOString().replace( /:\d+\.\d+Z$/, "Z" ) );
  215. // Write concatenated source to file
  216. grunt.file.write( name, compiled );
  217. };
  218. // Trace dependencies and concatenate files
  219. requirejs.optimize( config, function( response ) {
  220. grunt.verbose.writeln( response );
  221. grunt.log.ok( "File '" + name + "' created." );
  222. done();
  223. }, function( err ) {
  224. done( err );
  225. } );
  226. } );
  227. };