| /** |
| * Special concat/build task to handle various jQuery build requirements |
| * Concats AMD modules, removes their definitions, |
| * and includes/excludes specified modules |
| */ |
| |
| "use strict"; |
| |
| module.exports = function( grunt ) { |
| var fs = require( "fs" ), |
| requirejs = require( "requirejs" ), |
| slimBuildFlags = require( "./lib/slim-build-flags" ), |
| Insight = require( "insight" ), |
| pkg = require( "../../package.json" ), |
| srcFolder = __dirname + "/../../src/", |
| rdefineEnd = /\}\s*?\);[^}\w]*$/, |
| read = function( fileName ) { |
| return grunt.file.read( srcFolder + fileName ); |
| }, |
| |
| // Catch `// @CODE` and subsequent comment lines event if they don't start |
| // in the first column. |
| wrapper = read( "wrapper.js" ).split( /[\x20\t]*\/\/ @CODE\n(?:[\x20\t]*\/\/[^\n]+\n)*/ ), |
| |
| config = { |
| baseUrl: "src", |
| name: "jquery", |
| |
| // Allow strict mode |
| useStrict: true, |
| |
| // We have multiple minify steps |
| optimize: "none", |
| |
| // Include dependencies loaded with require |
| findNestedDependencies: true, |
| |
| // Avoid inserting define() placeholder |
| skipModuleInsertion: true, |
| |
| // Avoid breaking semicolons inserted by r.js |
| skipSemiColonInsertion: true, |
| wrap: { |
| start: wrapper[ 0 ].replace( /\/\*\s*eslint(?: |-).*\s*\*\/\n/, "" ), |
| end: wrapper[ 1 ] |
| }, |
| rawText: {}, |
| onBuildWrite: convert |
| }; |
| |
| /** |
| * Strip all definitions generated by requirejs |
| * Convert "var" modules to var declarations |
| * "var module" means the module only contains a return |
| * statement that should be converted to a var declaration |
| * This is indicated by including the file in any "var" folder |
| * @param {String} name |
| * @param {String} path |
| * @param {String} contents The contents to be written (including their AMD wrappers) |
| */ |
| function convert( name, path, contents ) { |
| var amdName; |
| |
| // Convert var modules |
| if ( /.\/var\//.test( path.replace( process.cwd(), "" ) ) ) { |
| contents = contents |
| .replace( |
| /define\(\s*(["'])[\w\W]*?\1[\w\W]*?return/, |
| "var " + |
| ( /var\/([\w-]+)/.exec( name )[ 1 ] ) + |
| " =" |
| ) |
| .replace( rdefineEnd, "" ); |
| |
| // Sizzle treatment |
| } else if ( /\/sizzle$/.test( name ) ) { |
| contents = "var Sizzle =\n" + contents |
| |
| // Remove EXPOSE lines from Sizzle |
| .replace( /\/\/\s*EXPOSE[\w\W]*\/\/\s*EXPOSE/, "return Sizzle;" ); |
| |
| } else { |
| |
| contents = contents |
| .replace( /\s*return\s+[^\}]+(\}\s*?\);[^\w\}]*)$/, "$1" ) |
| |
| // Multiple exports |
| .replace( /\s*exports\.\w+\s*=\s*\w+;/g, "" ); |
| |
| // Remove define wrappers, closure ends, and empty declarations |
| contents = contents |
| .replace( /define\([^{]*?{\s*(?:("|')use strict\1(?:;|))?/, "" ) |
| .replace( rdefineEnd, "" ); |
| |
| // Remove anything wrapped with |
| // /* ExcludeStart */ /* ExcludeEnd */ |
| // or a single line directly after a // BuildExclude comment |
| contents = contents |
| .replace( /\/\*\s*ExcludeStart\s*\*\/[\w\W]*?\/\*\s*ExcludeEnd\s*\*\//ig, "" ) |
| .replace( /\/\/\s*BuildExclude\n\r?[\w\W]*?\n\r?/ig, "" ); |
| |
| // Remove empty definitions |
| contents = contents |
| .replace( /define\(\[[^\]]*\]\)[\W\n]+$/, "" ); |
| } |
| |
| // AMD Name |
| if ( ( amdName = grunt.option( "amd" ) ) != null && /^exports\/amd$/.test( name ) ) { |
| if ( amdName ) { |
| grunt.log.writeln( "Naming jQuery with AMD name: " + amdName ); |
| } else { |
| grunt.log.writeln( "AMD name now anonymous" ); |
| } |
| |
| // Remove the comma for anonymous defines |
| contents = contents |
| .replace( /(\s*)"jquery"(\,\s*)/, amdName ? "$1\"" + amdName + "\"$2" : "" ); |
| |
| } |
| return contents; |
| } |
| |
| grunt.registerMultiTask( |
| "build", |
| "Concatenate source, remove sub AMD definitions, " + |
| "(include/exclude modules with +/- flags), embed date/version", |
| function() { |
| var flag, index, |
| done = this.async(), |
| flags = this.flags, |
| optIn = flags[ "*" ], |
| name = grunt.option( "filename" ), |
| minimum = this.data.minimum, |
| removeWith = this.data.removeWith, |
| excluded = [], |
| included = [], |
| version = grunt.config( "pkg.version" ), |
| |
| /** |
| * Recursively calls the excluder to remove on all modules in the list |
| * @param {Array} list |
| * @param {String} [prepend] Prepend this to the module name. |
| * Indicates we're walking a directory |
| */ |
| excludeList = function( list, prepend ) { |
| if ( list ) { |
| prepend = prepend ? prepend + "/" : ""; |
| list.forEach( function( module ) { |
| |
| // Exclude var modules as well |
| if ( module === "var" ) { |
| excludeList( |
| fs.readdirSync( srcFolder + prepend + module ), prepend + module |
| ); |
| return; |
| } |
| if ( prepend ) { |
| |
| // Skip if this is not a js file and we're walking files in a dir |
| if ( !( module = /([\w-\/]+)\.js$/.exec( module ) ) ) { |
| return; |
| } |
| |
| // Prepend folder name if passed |
| // Remove .js extension |
| module = prepend + module[ 1 ]; |
| } |
| |
| // Avoid infinite recursion |
| if ( excluded.indexOf( module ) === -1 ) { |
| excluder( "-" + module ); |
| } |
| } ); |
| } |
| }, |
| |
| /** |
| * Adds the specified module to the excluded or included list, depending on the flag |
| * @param {String} flag A module path relative to |
| * the src directory starting with + or - to indicate |
| * whether it should included or excluded |
| */ |
| excluder = function( flag ) { |
| var additional, |
| m = /^(\+|\-|)([\w\/-]+)$/.exec( flag ), |
| exclude = m[ 1 ] === "-", |
| module = m[ 2 ]; |
| |
| if ( exclude ) { |
| |
| // Can't exclude certain modules |
| if ( minimum.indexOf( module ) === -1 ) { |
| |
| // Add to excluded |
| if ( excluded.indexOf( module ) === -1 ) { |
| grunt.log.writeln( flag ); |
| excluded.push( module ); |
| |
| // Exclude all files in the folder of the same name |
| // These are the removable dependencies |
| // It's fine if the directory is not there |
| try { |
| excludeList( fs.readdirSync( srcFolder + module ), module ); |
| } catch ( e ) { |
| grunt.verbose.writeln( e ); |
| } |
| } |
| |
| additional = removeWith[ module ]; |
| |
| // Check removeWith list |
| if ( additional ) { |
| excludeList( additional.remove || additional ); |
| if ( additional.include ) { |
| included = included.concat( additional.include ); |
| grunt.log.writeln( "+" + additional.include ); |
| } |
| } |
| } else { |
| grunt.log.error( "Module \"" + module + "\" is a minimum requirement." ); |
| if ( module === "selector" ) { |
| grunt.log.error( |
| "If you meant to replace Sizzle, use -sizzle instead." |
| ); |
| } |
| } |
| } else { |
| grunt.log.writeln( flag ); |
| included.push( module ); |
| } |
| }; |
| |
| // Filename can be passed to the command line using |
| // command line options |
| // e.g. grunt build --filename=jquery-custom.js |
| name = name ? ( "dist/" + name ) : this.data.dest; |
| |
| // append commit id to version |
| if ( process.env.COMMIT ) { |
| version += " " + process.env.COMMIT; |
| } |
| |
| // figure out which files to exclude based on these rules in this order: |
| // dependency explicit exclude |
| // > explicit exclude |
| // > explicit include |
| // > dependency implicit exclude |
| // > implicit exclude |
| // examples: |
| // * none (implicit exclude) |
| // *:* all (implicit include) |
| // *:*:-css all except css and dependents (explicit > implicit) |
| // *:*:-css:+effects same (excludes effects because explicit include is |
| // trumped by explicit exclude of dependency) |
| // *:+effects none except effects and its dependencies |
| // (explicit include trumps implicit exclude of dependency) |
| delete flags[ "*" ]; |
| for ( flag in flags ) { |
| excluder( flag ); |
| } |
| |
| // Handle Sizzle exclusion |
| // Replace with selector-native |
| if ( ( index = excluded.indexOf( "sizzle" ) ) > -1 ) { |
| config.rawText.selector = "define(['./selector-native']);"; |
| excluded.splice( index, 1 ); |
| } |
| |
| // Replace exports/global with a noop noConflict |
| if ( ( index = excluded.indexOf( "exports/global" ) ) > -1 ) { |
| config.rawText[ "exports/global" ] = "define(['../core']," + |
| "function( jQuery ) {\njQuery.noConflict = function() {};\n});"; |
| excluded.splice( index, 1 ); |
| } |
| |
| grunt.verbose.writeflags( excluded, "Excluded" ); |
| grunt.verbose.writeflags( included, "Included" ); |
| |
| // append excluded modules to version |
| if ( excluded.length ) { |
| version += " -" + excluded.join( ",-" ); |
| |
| // set pkg.version to version with excludes, so minified file picks it up |
| grunt.config.set( "pkg.version", version ); |
| grunt.verbose.writeln( "Version changed to " + version ); |
| |
| // Have to use shallow or core will get excluded since it is a dependency |
| config.excludeShallow = excluded; |
| } |
| config.include = included; |
| |
| /** |
| * Handle Final output from the optimizer |
| * @param {String} compiled |
| */ |
| config.out = function( compiled ) { |
| compiled = compiled |
| |
| // Embed Version |
| .replace( /@VERSION/g, version ) |
| |
| // Embed Date |
| // yyyy-mm-ddThh:mmZ |
| .replace( /@DATE/g, ( new Date() ).toISOString().replace( /:\d+\.\d+Z$/, "Z" ) ); |
| |
| // Write concatenated source to file |
| grunt.file.write( name, compiled ); |
| }; |
| |
| // Turn off opt-in if necessary |
| if ( !optIn ) { |
| |
| // Overwrite the default inclusions with the explicit ones provided |
| config.rawText.jquery = "define([" + |
| ( included.length ? included.join( "," ) : "" ) + |
| "]);"; |
| } |
| |
| // Trace dependencies and concatenate files |
| requirejs.optimize( config, function( response ) { |
| grunt.verbose.writeln( response ); |
| grunt.log.ok( "File '" + name + "' created." ); |
| done(); |
| }, function( err ) { |
| done( err ); |
| } ); |
| } ); |
| |
| // Special "alias" task to make custom build creation less grawlix-y |
| // Translation example |
| // |
| // grunt custom:+ajax,-dimensions,-effects,-offset |
| // |
| // Becomes: |
| // |
| // grunt build:*:*:+ajax:-dimensions:-effects:-offset |
| // |
| // There's also a special "slim" alias that resolves to the jQuery Slim build |
| // configuration: |
| // |
| // grunt custom:slim |
| grunt.registerTask( "custom", function() { |
| var args = this.args, |
| modules = args.length ? |
| args[ 0 ] |
| .split( "," ) |
| |
| // Replace "slim" with respective exclusions meant for |
| // the official slim build |
| .reduce( ( acc, elem ) => acc.concat( |
| elem === "slim" ? |
| slimBuildFlags : |
| [ elem ] |
| ), [] ) |
| |
| .join( ":" ) : |
| "", |
| done = this.async(), |
| insight = new Insight( { |
| trackingCode: "UA-1076265-4", |
| pkg: pkg |
| } ); |
| |
| function exec( trackingAllowed ) { |
| var tracks = args.length ? args[ 0 ].split( "," ) : []; |
| var defaultPath = [ "build", "custom" ]; |
| |
| tracks = tracks.map( function( track ) { |
| return track.replace( /\//g, "+" ); |
| } ); |
| |
| if ( trackingAllowed ) { |
| |
| // Track individuals |
| tracks.forEach( function( module ) { |
| var path = defaultPath.concat( [ "individual" ], module ); |
| |
| insight.track.apply( insight, path ); |
| } ); |
| |
| // Track full command |
| insight.track.apply( insight, defaultPath.concat( [ "full" ], tracks ) ); |
| } |
| |
| grunt.task.run( [ "build:*:*" + ( modules ? ":" + modules : "" ), "uglify", "dist" ] ); |
| done(); |
| } |
| |
| grunt.log.writeln( "Creating custom build...\n" ); |
| |
| // Ask for permission the first time |
| if ( insight.optOut === undefined ) { |
| insight.askPermission( null, function( _error, result ) { |
| exec( result ); |
| } ); |
| } else { |
| exec( !insight.optOut ); |
| } |
| } ); |
| }; |