2 * Special concat/build task to handle various jQuery build requirements
3 * Concats AMD modules, removes their definitions,
4 * and includes/excludes specified modules
7 module.exports = function( grunt ) {
11 var fs = require( "fs" ),
12 requirejs = require( "requirejs" ),
13 srcFolder = __dirname + "/../../src/",
14 rdefineEnd = /\}\s*?\);[^}\w]*$/,
15 read = function( fileName ) {
16 return grunt.file.read( srcFolder + fileName );
18 globals = read( "exports/global.js" ),
19 wrapper = read( "wrapper.js" ).split( /\/\/ \@CODE\n\/\/[^\n]+/ ),
24 // We have multiple minify steps
27 // Include dependencies loaded with require
28 findNestedDependencies: true,
30 // Avoid inserting define() placeholder
31 skipModuleInsertion: true,
33 // Avoid breaking semicolons inserted by r.js
34 skipSemiColonInsertion: true,
36 start: wrapper[ 0 ].replace( /\/\*jshint .* \*\/\n/, "" ),
37 end: globals + wrapper[ 1 ]
44 * Strip all definitions generated by requirejs
45 * Convert "var" modules to var declarations
46 * "var module" means the module only contains a return
47 * statement that should be converted to a var declaration
48 * This is indicated by including the file in any "var" folder
49 * @param {String} name
50 * @param {String} path
51 * @param {String} contents The contents to be written (including their AMD wrappers)
53 function convert( name, path, contents ) {
56 // Convert var modules
57 if ( /.\/var\//.test( path.replace( process.cwd(), "" ) ) ) {
59 .replace( /define\([\w\W]*?return/, "var " + ( /var\/([\w-]+)/.exec( name )[ 1 ] ) + " =" )
60 .replace( rdefineEnd, "" );
63 } else if ( /\/sizzle$/.test( name ) ) {
64 contents = "var Sizzle =\n" + contents
66 // Remove EXPOSE lines from Sizzle
67 .replace( /\/\/\s*EXPOSE[\w\W]*\/\/\s*EXPOSE/, "return Sizzle;" );
72 .replace( /\s*return\s+[^\}]+(\}\s*?\);[^\w\}]*)$/, "$1" )
75 .replace( /\s*exports\.\w+\s*=\s*\w+;/g, "" );
77 // Remove define wrappers, closure ends, and empty declarations
79 .replace( /define\([^{]*?{/, "" )
80 .replace( rdefineEnd, "" );
82 // Remove anything wrapped with
83 // /* ExcludeStart */ /* ExcludeEnd */
84 // or a single line directly after a // BuildExclude comment
86 .replace( /\/\*\s*ExcludeStart\s*\*\/[\w\W]*?\/\*\s*ExcludeEnd\s*\*\//ig, "" )
87 .replace( /\/\/\s*BuildExclude\n\r?[\w\W]*?\n\r?/ig, "" );
89 // Remove empty definitions
91 .replace( /define\(\[[^\]]*\]\)[\W\n]+$/, "" );
95 if ( ( amdName = grunt.option( "amd" ) ) != null && /^exports\/amd$/.test( name ) ) {
97 grunt.log.writeln( "Naming jQuery with AMD name: " + amdName );
99 grunt.log.writeln( "AMD name now anonymous" );
102 // Remove the comma for anonymous defines
104 .replace( /(\s*)"jquery"(\,\s*)/, amdName ? "$1\"" + amdName + "\"$2" : "" );
110 grunt.registerMultiTask(
112 "Concatenate source, remove sub AMD definitions, " +
113 "(include/exclude modules with +/- flags), embed date/version",
118 optIn = flags[ "*" ],
119 name = grunt.option( "filename" ),
120 minimum = this.data.minimum,
121 removeWith = this.data.removeWith,
124 version = grunt.config( "pkg.version" ),
126 * Recursively calls the excluder to remove on all modules in the list
127 * @param {Array} list
128 * @param {String} [prepend] Prepend this to the module name.
129 * Indicates we're walking a directory
131 excludeList = function( list, prepend ) {
133 prepend = prepend ? prepend + "/" : "";
134 list.forEach( function( module ) {
136 // Exclude var modules as well
137 if ( module === "var" ) {
139 fs.readdirSync( srcFolder + prepend + module ), prepend + module
145 // Skip if this is not a js file and we're walking files in a dir
146 if ( !( module = /([\w-\/]+)\.js$/.exec( module ) ) ) {
150 // Prepend folder name if passed
151 // Remove .js extension
152 module = prepend + module[ 1 ];
155 // Avoid infinite recursion
156 if ( excluded.indexOf( module ) === -1 ) {
157 excluder( "-" + module );
163 * Adds the specified module to the excluded or included list, depending on the flag
164 * @param {String} flag A module path relative to
165 * the src directory starting with + or - to indicate
166 * whether it should included or excluded
168 excluder = function( flag ) {
169 var m = /^(\+|\-|)([\w\/-]+)$/.exec( flag ),
170 exclude = m[ 1 ] === "-",
175 // Can't exclude certain modules
176 if ( minimum.indexOf( module ) === -1 ) {
179 if ( excluded.indexOf( module ) === -1 ) {
180 grunt.log.writeln( flag );
181 excluded.push( module );
183 // Exclude all files in the folder of the same name
184 // These are the removable dependencies
185 // It's fine if the directory is not there
187 excludeList( fs.readdirSync( srcFolder + module ), module );
189 grunt.verbose.writeln( e );
193 // Check removeWith list
194 excludeList( removeWith[ module ] );
196 grunt.log.error( "Module \"" + module + "\" is a minimum requirement." );
197 if ( module === "selector" ) {
199 "If you meant to replace Sizzle, use -sizzle instead."
204 grunt.log.writeln( flag );
205 included.push( module );
209 // Filename can be passed to the command line using
210 // command line options
211 // e.g. grunt build --filename=jquery-custom.js
212 name = name ? ( "dist/" + name ) : this.data.dest;
214 // append commit id to version
215 if ( process.env.COMMIT ) {
216 version += " " + process.env.COMMIT;
219 // figure out which files to exclude based on these rules in this order:
220 // dependency explicit exclude
221 // > explicit exclude
222 // > explicit include
223 // > dependency implicit exclude
224 // > implicit exclude
226 // * none (implicit exclude)
227 // *:* all (implicit include)
228 // *:*:-css all except css and dependents (explicit > implicit)
229 // *:*:-css:+effects same (excludes effects because explicit include is
230 // trumped by explicit exclude of dependency)
231 // *:+effects none except effects and its dependencies
232 // (explicit include trumps implicit exclude of dependency)
234 for ( flag in flags ) {
238 // Handle Sizzle exclusion
239 // Replace with selector-native
240 if ( ( index = excluded.indexOf( "sizzle" ) ) > -1 ) {
241 config.rawText.selector = "define(['./selector-native']);";
242 excluded.splice( index, 1 );
245 // Replace exports/global with a noop noConflict
246 if ( ( index = excluded.indexOf( "exports/global" ) ) > -1 ) {
247 config.rawText[ "exports/global" ] = "define(['../core']," +
248 "function( jQuery ) {\njQuery.noConflict = function() {};\n});";
249 excluded.splice( index, 1 );
252 grunt.verbose.writeflags( excluded, "Excluded" );
253 grunt.verbose.writeflags( included, "Included" );
255 // append excluded modules to version
256 if ( excluded.length ) {
257 version += " -" + excluded.join( ",-" );
259 // set pkg.version to version with excludes, so minified file picks it up
260 grunt.config.set( "pkg.version", version );
261 grunt.verbose.writeln( "Version changed to " + version );
263 // Have to use shallow or core will get excluded since it is a dependency
264 config.excludeShallow = excluded;
266 config.include = included;
269 * Handle Final output from the optimizer
270 * @param {String} compiled
272 config.out = function( compiled ) {
276 .replace( /@VERSION/g, version )
280 .replace( /@DATE/g, ( new Date() ).toISOString().replace( /:\d+\.\d+Z$/, "Z" ) );
282 // Write concatenated source to file
283 grunt.file.write( name, compiled );
286 // Turn off opt-in if necessary
289 // Overwrite the default inclusions with the explicit ones provided
290 config.rawText.jquery = "define([" +
291 ( included.length ? included.join( "," ) : "" ) +
295 // Trace dependencies and concatenate files
296 requirejs.optimize( config, function( response ) {
297 grunt.verbose.writeln( response );
298 grunt.log.ok( "File '" + name + "' created." );
305 // Special "alias" task to make custom build creation less grawlix-y
306 // Translation example
308 // grunt custom:+ajax,-dimensions,-effects,-offset
312 // grunt build:*:*:+ajax:-dimensions:-effects:-offset
313 grunt.registerTask( "custom", function() {
314 var args = this.args,
315 modules = args.length ? args[ 0 ].replace( /,/g, ":" ) : "";
317 grunt.log.writeln( "Creating custom build...\n" );
319 grunt.task.run( [ "build:*:*" + ( modules ? ":" + modules : "" ), "uglify", "dist" ] );