jquery/build/tasks/build.js
Michał Gołębiowski-Owczarek 47835965bd Selector: Inline Sizzle into the selector module
This commit removes Sizzle from jQuery, inlining its code & removing obsolete
workarounds where applicable.

The selector-native module has been removed. Further work on the selector
module may decrease the size enough that it will no longer be necessary. If
it turns out it's still useful, we'll reinstate it but the code will look
different anyway as we'll want to share as much code as possible with
the existing selector module.

The Sizzle AUTHORS.txt file has been merged with the jQuery one - people are
sorted by their first contributions to either of the two repositories.

The commit reduces the gzipped jQuery size by 1460 bytes compared to master.

Closes gh-4395
2019-07-29 21:19:21 +02:00

364 lines
10 KiB
JavaScript

/**
* Special concat/build task to handle various jQuery build requirements
* Concats AMD modules, removes their definitions,
* and includes/excludes specified modules
*/
module.exports = function( grunt ) {
"use strict";
var fs = require( "fs" ),
requirejs = require( "requirejs" ),
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, "" );
} 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." );
}
} 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 );
}
// 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
grunt.registerTask( "custom", function() {
var args = this.args,
modules = args.length ? args[ 0 ].replace( /,/g, ":" ) : "",
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 );
}
} );
};