Build: combine intro and outro
[jquery.git] / build / tasks / build.js
blob2ebb4c47e7bd97013dc87edac6f11ae044a90494
1 /**
2  * Special concat/build task to handle various jQuery build requirements
3  * Concats AMD modules, removes their definitions,
4  * and includes/excludes specified modules
5  */
7 module.exports = function( grunt ) {
9         "use strict";
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 );
17                 },
18                 globals = read( "exports/global.js" ),
19                 wrapper = read( "wrapper.js" ).split( /\/\/ \@CODE\n\/\/[^\n]+/ ),
20                 config = {
21                         baseUrl: "src",
22                         name: "jquery",
24                         // We have multiple minify steps
25                         optimize: "none",
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,
35                         wrap: {
36                                 start: wrapper[ 0 ].replace( /\/\*jshint .* \*\/\n/, "" ),
37                                 end: globals + wrapper[ 1 ]
38                         },
39                         rawText: {},
40                         onBuildWrite: convert
41                 };
43         /**
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)
52          */
53         function convert( name, path, contents ) {
54                 var amdName;
56                 // Convert var modules
57                 if ( /.\/var\//.test( path.replace( process.cwd(), "" ) ) ) {
58                         contents = contents
59                                 .replace( /define\([\w\W]*?return/, "var " + ( /var\/([\w-]+)/.exec( name )[ 1 ] ) + " =" )
60                                 .replace( rdefineEnd, "" );
62                 // Sizzle treatment
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;" );
69                 } else {
71                         contents = contents
72                                 .replace( /\s*return\s+[^\}]+(\}\s*?\);[^\w\}]*)$/, "$1" )
74                                 // Multiple exports
75                                 .replace( /\s*exports\.\w+\s*=\s*\w+;/g, "" );
77                         // Remove define wrappers, closure ends, and empty declarations
78                         contents = contents
79                                 .replace( /define\([^{]*?{/, "" )
80                                 .replace( rdefineEnd, "" );
82                         // Remove anything wrapped with
83                         // /* ExcludeStart */ /* ExcludeEnd */
84                         // or a single line directly after a // BuildExclude comment
85                         contents = contents
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
90                         contents = contents
91                                 .replace( /define\(\[[^\]]*\]\)[\W\n]+$/, "" );
92                 }
94                 // AMD Name
95                 if ( ( amdName = grunt.option( "amd" ) ) != null && /^exports\/amd$/.test( name ) ) {
96                         if ( amdName ) {
97                                 grunt.log.writeln( "Naming jQuery with AMD name: " + amdName );
98                         } else {
99                                 grunt.log.writeln( "AMD name now anonymous" );
100                         }
102                         // Remove the comma for anonymous defines
103                         contents = contents
104                                 .replace( /(\s*)"jquery"(\,\s*)/, amdName ? "$1\"" + amdName + "\"$2" : "" );
106                 }
107                 return contents;
108         }
110         grunt.registerMultiTask(
111                 "build",
112                 "Concatenate source, remove sub AMD definitions, " +
113                         "(include/exclude modules with +/- flags), embed date/version",
114         function() {
115                 var flag, index,
116                         done = this.async(),
117                         flags = this.flags,
118                         optIn = flags[ "*" ],
119                         name = grunt.option( "filename" ),
120                         minimum = this.data.minimum,
121                         removeWith = this.data.removeWith,
122                         excluded = [],
123                         included = [],
124                         version = grunt.config( "pkg.version" ),
125                         /**
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
130                          */
131                         excludeList = function( list, prepend ) {
132                                 if ( list ) {
133                                         prepend = prepend ? prepend + "/" : "";
134                                         list.forEach( function( module ) {
136                                                 // Exclude var modules as well
137                                                 if ( module === "var" ) {
138                                                         excludeList(
139                                                                 fs.readdirSync( srcFolder + prepend + module ), prepend + module
140                                                         );
141                                                         return;
142                                                 }
143                                                 if ( prepend ) {
145                                                         // Skip if this is not a js file and we're walking files in a dir
146                                                         if ( !( module = /([\w-\/]+)\.js$/.exec( module ) ) ) {
147                                                                 return;
148                                                         }
150                                                         // Prepend folder name if passed
151                                                         // Remove .js extension
152                                                         module = prepend + module[ 1 ];
153                                                 }
155                                                 // Avoid infinite recursion
156                                                 if ( excluded.indexOf( module ) === -1 ) {
157                                                         excluder( "-" + module );
158                                                 }
159                                         } );
160                                 }
161                         },
162                         /**
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
167                          */
168                         excluder = function( flag ) {
169                                 var m = /^(\+|\-|)([\w\/-]+)$/.exec( flag ),
170                                         exclude = m[ 1 ] === "-",
171                                         module = m[ 2 ];
173                                 if ( exclude ) {
175                                         // Can't exclude certain modules
176                                         if ( minimum.indexOf( module ) === -1 ) {
178                                                 // Add to excluded
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
186                                                         try {
187                                                                 excludeList( fs.readdirSync( srcFolder + module ), module );
188                                                         } catch ( e ) {
189                                                                 grunt.verbose.writeln( e );
190                                                         }
191                                                 }
193                                                 // Check removeWith list
194                                                 excludeList( removeWith[ module ] );
195                                         } else {
196                                                 grunt.log.error( "Module \"" + module + "\" is a minimum requirement." );
197                                                 if ( module === "selector" ) {
198                                                         grunt.log.error(
199                                                                 "If you meant to replace Sizzle, use -sizzle instead."
200                                                         );
201                                                 }
202                                         }
203                                 } else {
204                                         grunt.log.writeln( flag );
205                                         included.push( module );
206                                 }
207                         };
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;
217                 }
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
225                 // examples:
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)
233                 delete flags[ "*" ];
234                 for ( flag in flags ) {
235                         excluder( flag );
236                 }
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 );
243                 }
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 );
250                 }
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;
265                 }
266                 config.include = included;
268                 /**
269                  * Handle Final output from the optimizer
270                  * @param {String} compiled
271                  */
272                 config.out = function( compiled ) {
273                         compiled = compiled
275                                 // Embed Version
276                                 .replace( /@VERSION/g, version )
278                                 // Embed Date
279                                 // yyyy-mm-ddThh:mmZ
280                                 .replace( /@DATE/g, ( new Date() ).toISOString().replace( /:\d+\.\d+Z$/, "Z" ) );
282                         // Write concatenated source to file
283                         grunt.file.write( name, compiled );
284                 };
286                 // Turn off opt-in if necessary
287                 if ( !optIn ) {
289                         // Overwrite the default inclusions with the explicit ones provided
290                         config.rawText.jquery = "define([" +
291                                 ( included.length ? included.join( "," ) : "" ) +
292                         "]);";
293                 }
295                 // Trace dependencies and concatenate files
296                 requirejs.optimize( config, function( response ) {
297                         grunt.verbose.writeln( response );
298                         grunt.log.ok( "File '" + name + "' created." );
299                         done();
300                 }, function( err ) {
301                         done( err );
302                 } );
303         } );
305         // Special "alias" task to make custom build creation less grawlix-y
306         // Translation example
307         //
308         //   grunt custom:+ajax,-dimensions,-effects,-offset
309         //
310         // Becomes:
311         //
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" ] );
320         } );