Files
lodash/build.js
John-David Dalton f72b833724 Ensure _.keys works with arguments objets cross-browser.
Former-commit-id: 1238c9efcfc13a420804c26c5edee1e4aa5a4238
2012-08-06 01:51:47 -07:00

1097 lines
35 KiB
JavaScript
Executable File

#!/usr/bin/env node
;(function() {
'use strict';
/** Load modules */
var fs = require('fs'),
path = require('path'),
vm = require('vm'),
minify = require(path.join(__dirname, 'build', 'minify'));
/** The current working directory */
var cwd = process.cwd();
/** Flag used to specify a Backbone build */
var isBackbone = process.argv.indexOf('backbone') > -1;
/** Flag used to specify a Content Security Policy build */
var isCSP = process.argv.indexOf('csp') > -1 || process.argv.indexOf('CSP') > -1;
/** Flag used to specify a legacy build */
var isLegacy = process.argv.indexOf('legacy') > -1;
/** Flag used to specify a mobile build */
var isMobile = !isLegacy && (isCSP || process.argv.indexOf('mobile') > -1);
/**
* Flag used to specify `_.bindAll`, `_.extend`, and `_.defaults` are
* constructed using the "use strict" directive.
*/
var isStrict = process.argv.indexOf('strict') > -1;
/** Flag used to specify an Underscore build */
var isUnderscore = process.argv.indexOf('underscore') > -1;
/** Flag used to specify if the build should include the "use strict" directive */
var useStrict = isStrict || !(isLegacy || isMobile);
/** Shortcut used to convert array-like objects to arrays */
var slice = [].slice;
/** The lodash.js source */
var source = fs.readFileSync(path.join(__dirname, 'lodash.js'), 'utf8');
/** Load customized Lo-Dash module */
var lodash = (function() {
var sandbox = {};
if (isStrict) {
source = setUseStrictOption(source, true);
}
else if (!useStrict) {
source = removeUseStrictDirective(source);
source = setUseStrictOption(source, false);
}
if (isLegacy) {
source = replaceVar(source, 'noArgsClass', 'true');
['isBindFast', 'isKeysFast', 'nativeBind', 'nativeIsArray', 'nativeKeys'].forEach(function(varName) {
source = replaceVar(source, varName, 'false');
});
}
else if (isMobile) {
source = replaceVar(source, 'isKeysFast', 'false');
}
vm.runInNewContext(source, sandbox);
return sandbox._;
}());
/** Used to associate aliases with their real names */
var aliasToRealMap = {
'all': 'every',
'any': 'some',
'collect': 'map',
'detect': 'find',
'each': 'forEach',
'foldl': 'reduce',
'foldr': 'reduceRight',
'head': 'first',
'include': 'contains',
'inject': 'reduce',
'methods': 'functions',
'select': 'filter',
'tail': 'rest',
'take': 'first',
'unique': 'uniq'
};
/** Used to associate real names with their aliases */
var realToAliasMap = {
'contains': ['include'],
'every': ['all'],
'filter': ['select'],
'find': ['detect'],
'first': ['head', 'take'],
'forEach': ['each'],
'functions': ['methods'],
'map': ['collect'],
'reduce': ['foldl', 'inject'],
'reduceRight': ['foldr'],
'rest': ['tail'],
'some': ['any'],
'uniq': ['unique']
};
/** Used to track Backbone's Lo-Dash dependencies */
var backboneDependencies = [
'bind',
'bindAll',
'clone',
'contains',
'escape',
'every',
'extend',
'filter',
'find',
'first',
'forEach',
'groupBy',
'has',
'indexOf',
'initial',
'invoke',
'isArray',
'isEmpty',
'isEqual',
'isFunction',
'isObject',
'isRegExp',
'keys',
'last',
'lastIndexOf',
'map',
'max',
'min',
'mixin',
'reduce',
'reduceRight',
'reject',
'rest',
'shuffle',
'size',
'some',
'sortBy',
'sortedIndex',
'toArray',
'uniqueId',
'without'
];
/** Used to track function dependencies */
var dependencyMap = {
'after': [],
'bind': [],
'bindAll': ['bind', 'functions'],
'chain': ['mixin'],
'clone': ['extend', 'forIn', 'forOwn', 'isArguments'],
'compact': [],
'compose': [],
'contains': [],
'countBy': [],
'debounce': [],
'defaults': [],
'defer': [],
'delay': [],
'difference': ['indexOf'],
'drop': ['indexOf'],
'escape': [],
'every': ['identity'],
'extend': [],
'filter': ['identity'],
'find': [],
'first': [],
'flatten': ['isArray'],
'forEach': [],
'forIn': [],
'forOwn': [],
'functions': [],
'groupBy': [],
'has': [],
'identity': [],
'indexOf': ['sortedIndex'],
'initial': [],
'intersection': ['every', 'indexOf'],
'invoke': [],
'isArguments': [],
'isArray': [],
'isBoolean': [],
'isDate': [],
'isElement': [],
'isEmpty': ['isArguments'],
'isEqual': ['isArguments'],
'isFinite': [],
'isFunction': [],
'isNaN': [],
'isNull': [],
'isNumber': [],
'isObject': [],
'isRegExp': [],
'isString': [],
'isUndefined': [],
'keys': [],
'last': [],
'lastIndexOf': [],
'map': ['identity'],
'max': [],
'memoize': [],
'merge': ['isArray', 'forIn'],
'min': [],
'mixin': ['forEach', 'functions'],
'noConflict': [],
'once': [],
'partial': [],
'pick': [],
'pluck': [],
'range': [],
'reduce': [],
'reduceRight': ['keys'],
'reject': ['identity'],
'rest': [],
'result': [],
'shuffle': [],
'size': ['isArguments', 'keys'],
'some': ['identity'],
'sortBy': [],
'sortedIndex': ['bind'],
'tap': [],
'template': ['escape'],
'throttle': [],
'times': [],
'toArray': ['values'],
'union': ['indexOf'],
'uniq': ['identity', 'indexOf'],
'uniqueId': [],
'values': [],
'where': ['forIn'],
'without': ['indexOf'],
'wrap': [],
'zip': ['max', 'pluck'],
'zipObject': []
};
/** Used to `iteratorTemplate` */
var iteratorOptions = [
'args',
'array',
'arrayBranch',
'beforeLoop',
'bottom',
'exit',
'firstArg',
'hasDontEnumBug',
'inLoop',
'init',
'isKeysFast',
'object',
'objectBranch',
'noArgsEnum',
'noCharByIndex',
'shadowed',
'top',
'useHas',
'useStrict'
];
/** Collections of method names */
var excludeMethods,
includeMethods,
allMethods = Object.keys(dependencyMap);
var underscoreMethods = lodash.without.apply(lodash, [allMethods].concat([
'countBy',
'drop',
'forIn',
'forOwn',
'merge',
'partial',
'where',
'zipObject'
]));
/** Used to specify whether filtering is for exclusion or inclusion */
var filterType = process.argv.reduce(function(result, value) {
if (result) {
return result;
}
var pair = value.match(/^(exclude|include)=(.*)$/);
if (!pair) {
return result;
}
// remove nonexistent method names
var methodNames = lodash.intersection(allMethods, pair[2].split(/, */).map(getRealName));
if (pair[1] == 'exclude') {
excludeMethods = methodNames;
} else {
includeMethods = methodNames;
}
// return `filterType`
return pair[1];
}, '');
/*--------------------------------------------------------------------------*/
/**
* Logs the help message to the console.
*
* @private
*/
function displayHelp() {
console.log([
'',
' Commands:',
'',
' lodash backbone Build containing all methods required by Backbone',
' lodash csp Build supporting default Content Security Policy restrictions',
' lodash legacy Build tailored for older browsers without ES5 support',
' lodash mobile Build with IE < 9 bug fixes and method compilation removed',
' lodash strict Build with `_.bindAll`, `_.defaults`, and `_.extend` in strict mode',
' lodash underscore Build containing only methods included in Underscore',
' lodash category=... Comma separated categories of methods to include in the build',
' lodash exclude=... Comma separated names of methods to exclude from the build',
' lodash include=... Comma separated names of methods to include in the build',
'',
' All arguments, except `backbone` with `underscore`, `exclude` with `include`,',
' and `legacy` with `csp`/`mobile`, may be combined.',
'',
' Options:',
'',
' -h, --help Display help information',
' -V, --version Output current version of Lo-Dash',
''
].join('\n'));
}
/**
* Gets the aliases associated with a given function name.
*
* @private
* @param {String} funcName The name of the function to get aliases for.
* @returns {Array} Returns an array of aliases.
*/
function getAliases(funcName) {
return realToAliasMap[funcName] || [];
}
/**
* Gets the Lo-Dash method assignments snippet from `source`.
*
* @private
* @param {String} source The source to inspect.
* @returns {String} Returns the method assignments snippet.
*/
function getMethodAssignments(source) {
return (source.match(/lodash\.VERSION *= *[\s\S]+?\/\*-+\*\/\n/) || [''])[0];
}
/**
* Gets an array of depenants for a function by a given name.
*
* @private
* @param {String} funcName The name of the function to query.
* @returns {Array} Returns an array of function dependants.
*/
function getDependants(funcName) {
// iterate over `dependencyMap`, adding the names of functions that
// have `funcName` as a dependency
return lodash.reduce(dependencyMap, function(result, dependencies, otherName) {
if (lodash.contains(dependencies, funcName)) {
result.push(otherName);
}
return result;
}, []);
}
/**
* Gets an array of dependencies for a given function name. If passed an array
* of dependencies it will return an array containing the given dependencies
* plus any additional detected sub-dependencies.
*
* @private
* @param {Array|String} funcName A single function name or array of
* dependencies to query.
* @returns {Array} Returns an array of function dependencies.
*/
function getDependencies(funcName) {
var dependencies = Array.isArray(funcName) ? funcName : dependencyMap[funcName];
if (!dependencies) {
return [];
}
// recursively accumulate the dependencies of the `funcName` function, and
// the dependencies of its dependencies, and so on.
return lodash.uniq(dependencies.reduce(function(result, otherName) {
result.push.apply(result, getDependencies(otherName).concat(otherName));
return result;
}, []));
}
/**
* Gets the formatted source of the given function.
*
* @private
* @param {Function} func The function to process.
* @returns {String} Returns the formatted source.
*/
function getFunctionSource(func) {
var source = func.source || (func + '');
// all leading whitespace
return source.replace(/\n(?:.*)/g, function(match, index) {
match = match.slice(1);
return (
match == '}' && source.indexOf('}', index + 2) == -1 ? '\n ' : '\n '
) + match;
});
}
/**
* Gets the `_.isArguments` fallback snippet from `source`.
*
* @private
* @param {String} source The source to inspect.
* @returns {String} Returns the `isArguments` fallback snippet.
*/
function getIsArgumentsFallback(source) {
return (source.match(/(?:\s*\/\/.*)*\s*if *\(noArgsClass[\s\S]+?};\s*}/) || [''])[0];
}
/**
* Gets the real name, not alias, of a given function name.
*
* @private
* @param {String} funcName The name of the function to resolve.
* @returns {String} Returns the real name.
*/
function getRealName(funcName) {
return aliasToRealMap[funcName] || funcName;
}
/**
* Determines if all functions of the given names have been removed from `source`.
*
* @private
* @param {String} source The source to inspect.
* @param {String} [funcName1, funcName2, ...] The names of functions to check.
* @returns {Boolean} Returns `true` if all functions have been removed, else `false`.
*/
function isRemoved(source) {
return slice.call(arguments, 1).every(function(funcName) {
return !matchFunction(source, funcName);
});
}
/**
* Searches `source` for a `funcName` function declaration, expression, or
* assignment and returns the matched snippet.
*
* @private
* @param {String} source The source to inspect.
* @param {String} funcName The name of the function to match.
* @returns {String} Returns the matched function snippet.
*/
function matchFunction(source, funcName) {
var result = source.match(RegExp(
// match multi-line comment block (could be on a single line)
'\\n +/\\*[^*]*\\*+(?:[^/][^*]*\\*+)*/\\n' +
// begin non-capturing group
'(?:' +
// match a function declaration
'( +)function ' + funcName + '\\b[\\s\\S]+?\\n\\1}|' +
// match a variable declaration with `createIterator`
' +var ' + funcName + ' *=.*?createIterator\\((?:{|[a-zA-Z])[\\s\\S]+?\\);|' +
// match a variable declaration with function expression
'( +)var ' + funcName + ' *=.*?function[\\s\\S]+?\\n\\2};' +
// end non-capturing group
')\\n'
));
return result ? result[0] : '';
}
/**
* Removes the all references to `refName` from `createIterator` in `source`.
*
* @private
* @param {String} source The source to process.
* @param {String} refName The name of the reference to remove.
* @returns {String} Returns the modified source.
*/
function removeFromCreateIterator(source, refName) {
var snippet = matchFunction(source, 'createIterator');
if (snippet) {
// clip the snippet at the `factory` assignment
snippet = snippet.match(/Function\([\s\S]+$/)[0];
var modified = snippet.replace(RegExp('\\b' + refName + '\\b,? *', 'g'), '');
source = source.replace(snippet, modified);
}
return source;
}
/**
* Removes the `funcName` function declaration, expression, or assignment and
* associated code from `source`.
*
* @private
* @param {String} source The source to process.
* @param {String} funcName The name of the function to remove.
* @returns {String} Returns the source with the function removed.
*/
function removeFunction(source, funcName) {
var modified,
snippet = matchFunction(source, funcName);
// exit early if function is not found
if (!snippet) {
return source;
}
// remove function
source = source.replace(matchFunction(source, funcName), '');
// grab the method assignments snippet
snippet = getMethodAssignments(source);
// remove assignment and aliases
modified = getAliases(funcName).concat(funcName).reduce(function(result, otherName) {
return result.replace(RegExp('(?:\\n *//.*\\s*)* *lodash\\.' + otherName + ' *= *.+\\n'), '');
}, snippet);
// replace with the modified snippet
source = source.replace(snippet, modified);
return removeFromCreateIterator(source, funcName);
}
/**
* Removes the `_.isArguments` fallback from `source`.
*
* @private
* @param {String} source The source to process.
* @returns {String} Returns the source with the `isArguments` fallback removed.
*/
function removeIsArgumentsFallback(source) {
return source.replace(getIsArgumentsFallback(source), '');
}
/**
* Removes the `Object.keys` object iteration optimization from `source`.
*
* @private
* @param {String} source The source to process.
* @returns {String} Returns the modified source.
*/
function removeKeysOptimization(source) {
return removeVar(source, 'isKeysFast')
// remove optimized branch in `iteratorTemplate`
.replace(/(?: *\/\/.*\n)*\s*'( *)<% *if *\(isKeysFast[\s\S]+?'\1<% *} *else *\{ *%>.+\n([\s\S]+?) *'\1<% *} *%>.+/, '$2')
// remove `isKeysFast` from `beforeLoop.object` of `mapIteratorOptions`
.replace(/=\s*'\s*\+\s*\(isKeysFast.+/, "= []'")
// remove `isKeysFast` from `inLoop.object` of `mapIteratorOptions`, `invoke`, `pluck`, and `sortBy`
.replace(/'\s*\+\s*\(isKeysFast[^)]+?\)\s*\+\s*'/g, '.push')
// remove data object property assignment in `createIterator`
.replace(/\s*.+?\.isKeysFast *=.+/, '');
}
/**
* Removes all `noArgsClass` references from `source`.
*
* @private
* @param {String} source The source to process.
* @returns {String} Returns the modified source.
*/
function removeNoArgsClass(source) {
return removeVar(source, 'noArgsClass')
// remove `noArgsClass` from `_.clone`, `_.isEqual`, and `_.size`
.replace(/ *\|\| *\(noArgsClass *&&[^)]+?\)\)/g, '')
// remove `noArgsClass` from `_.isEqual`
.replace(/if *\(noArgsClass[^}]+?}\n/, '');
}
/**
* Removes all `noNodeClass` references from `source`.
*
* @private
* @param {String} source The source to process.
* @returns {String} Returns the modified source.
*/
function removeNoNodeClass(source) {
return source
// remove `noNodeClass` assignment
.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *try *\{(?:\s*\/\/.*)*\n *var noNodeClass[\s\S]+?catch[^}]+}\n/, '')
// remove `noNodeClass` from `isPlainObject`
.replace(/\(!noNodeClass *\|\|[\s\S]+?\)\) *&&/, '')
// remove `noNodeClass` from `_.isEqual`
.replace(/ *\|\| *\(noNodeClass *&&[\s\S]+?\)\)\)/, '');
}
/**
* Removes the "use strict" directive from `source`.
*
* @private
* @param {String} source The source to process.
* @returns {String} Returns the modified source.
*/
function removeUseStrictDirective(source) {
return source.replace(/(["'])use strict\1;( *\n)?/, '');
}
/**
* Removes a given variable from `source`.
*
* @private
* @param {String} source The source to process.
* @param {String} varName The name of the variable to remove.
* @returns {String} Returns the source with the variable removed.
*/
function removeVar(source, varName) {
source = source.replace(RegExp(
// begin non-capturing group
'(?:' +
// match multi-line comment block
'(?:\\n +/\\*[^*]*\\*+(?:[^/][^*]*\\*+)*/)?\\n' +
// match a variable declaration that's not part of a declaration list
'( +)var ' + varName + ' *= *(?:.+?;|(?:Function\\(.+?|.*?[^,])\\n[\\s\\S]+?\\n\\1.+?;)\\n|' +
// match a variable in a declaration list
'\\n +' + varName + ' *=.+?,' +
// end non-capturing group
')'
), '');
// remove a varaible at the start of a variable declaration list
source = source.replace(RegExp('(var +)' + varName + ' *=.+?,\\s+'), '$1');
// remove a variable at the end of a variable declaration list
source = source.replace(RegExp(',\\s*' + varName + ' *=.+?;'), ';');
// remove variable reference from `arrayLikeClasses` and `cloneableClasses` assignments
source = source.replace(RegExp('(?:arrayLikeClasses|cloneableClasses)\\[' + varName + '\\] *= *(?:false|true)?', 'g'), '');
return removeFromCreateIterator(source, varName);
}
/**
* Searches `source` for a `varName` variable declaration and replaces its
* assigned value with `varValue`.
*
* @private
* @param {String} source The source to inspect.
* @param {String} varName The name of the variable to replace.
* @returns {String} Returns the source with the variable replaced.
*/
function replaceVar(source, varName, varValue) {
// replace a variable that's not part of a declaration list
source = source.replace(RegExp(
'(( +)var ' + varName + ' *= *)' +
'(?:.+?;|(?:Function\\(.+?|.*?[^,])\\n[\\s\\S]+?\\n\\2.+?;)\\n'
), '$1' + varValue + ';\n');
// replace a varaible at the start or middle of a declaration list
source = source.replace(RegExp('((?:var|\\n) +' + varName + ' *=).+?,'), '$1 ' + varValue + ',');
// replace a variable at the end of a variable declaration list
source = source.replace(RegExp('(,\\s*' + varName + ' *=).+?;'), '$1 ' + varValue + ';');
return source;
}
/**
* Hard-codes the `useStrict` template option value for `iteratorTemplate`.
*
* @private
* @param {String} source The source to process.
* @param {Boolean} value The value to set.
* @returns {String} Returns the modified source.
*/
function setUseStrictOption(source, value) {
return source
// replace `useStrict` branch in `value` with hard-coded option
.replace(/(?: *\/\/.*\n)*(\s*)' *<% *if *\(useStrict\).+/, value ? "$1'\\'use strict\\';\\n' +" : '')
// remove `useStrict` from iterator options
.replace(/ *'useStrict': *false,\n/g, '')
// remove `useStrict` data object property assignment in `createIterator`
.replace(/\s*.+?\.useStrict *=.+/, '');
}
/**
* Writes `source` to a file with the given `filename` to the current
* working directory.
*
* @private
* @param {String} source The source to write.
* @param {String} filename The name of the file.
*/
function writeFile(source, filename) {
// correct overly aggressive Closure Compiler minification
source = source.replace('prototype={valueOf:1}', 'prototype={valueOf:1,y:1}');
// re-remove "use strict" added by the minifier
if (!useStrict) {
source = removeUseStrictDirective(source);
}
fs.writeFileSync(path.join(cwd, filename), source);
}
/*--------------------------------------------------------------------------*/
// display help message
if (lodash.find(process.argv, function(arg) {
return /^(?:-h|--help)$/.test(arg);
})) {
displayHelp();
process.exit();
}
// display `lodash.VERSION`
if (lodash.find(process.argv, function(arg) {
return /^(?:-V|--version)$/.test(arg);
})) {
console.log(lodash.VERSION);
process.exit();
}
/*--------------------------------------------------------------------------*/
// add methods required by Backbone or Underscore
[
{ 'flag': isBackbone, 'methodNames': backboneDependencies },
{ 'flag': isUnderscore, 'methodNames': underscoreMethods }
]
.some(function(data) {
var flag = data.flag,
methodNames = data.methodNames;
if (!flag) {
return false;
}
// add any additional sub-dependencies
methodNames = getDependencies(methodNames);
if (filterType == 'exclude') {
// remove excluded methods from `methodNames`
includeMethods = lodash.without.apply(lodash, [methodNames].concat(excludeMethods));
}
else if (filterType) {
// merge `methodNames` into `includeMethods`
includeMethods = lodash.union(includeMethods, methodNames);
}
else {
// include only the `methodNames`
includeMethods = methodNames;
}
filterType = 'include';
return true;
});
/*--------------------------------------------------------------------------*/
// add category methods
process.argv.some(function(value) {
var categories = value.match(/^category=(.*)$/);
if (!categories) {
return false;
}
// resolve method names belonging to each category
var categoryMethods = categories.reduce(function(result, category) {
return result.concat(allMethods.filter(function(funcName) {
return RegExp('@category ' + category + '\\b', 'i').test(matchFunction(source, funcName));
}));
}, []);
if (filterType == 'exclude') {
// remove excluded methods from `categoryMethods`
includeMethods = lodash.without.apply(lodash, [categoryMethods].concat(excludeMethods));
}
else if (filterType) {
// merge `categoryMethods` into `includeMethods`
includeMethods = lodash.union(includeMethods, categoryMethods);
}
else {
// include only the `categoryMethods`
includeMethods = categoryMethods;
}
filterType = 'include';
return true;
});
/*--------------------------------------------------------------------------*/
// custom build
(function() {
// exit early if "exclude" or "include" options aren't specified
if (!filterType) {
return;
}
if (filterType == 'exclude') {
// remove methods that are named in `excludeMethods` and their dependants
excludeMethods.forEach(function(funcName) {
getDependants(funcName).concat(funcName).forEach(function(otherName) {
source = removeFunction(source, otherName);
});
});
}
else {
// add dependencies to `includeMethods`
includeMethods = getDependencies(includeMethods);
// remove methods that aren't named in `includeMethods`
lodash.each(allMethods, function(otherName) {
if (!lodash.contains(includeMethods, otherName)) {
source = removeFunction(source, otherName);
}
});
}
// remove `isArguments` fallback before `isArguments` is transformed by
// other parts of the build process
if (isRemoved(source, 'isArguments')) {
source = removeIsArgumentsFallback(source);
}
}());
/*--------------------------------------------------------------------------*/
// simplify template snippets
source = source.replace(
RegExp(
"'else if \\(thisArg\\) \\{\\\\n' \\+\\s*" +
"' callback = iteratorBind\\(callback, thisArg\\)\\\\n' \\+\\s*" +
"'}'"
, 'g'),
"'else if (thisArg) callback = iteratorBind(callback, thisArg)'"
);
/*--------------------------------------------------------------------------*/
// DRY out isType functions
(function() {
var iteratorName = lodash.find(['forEach', 'forOwn'], function(funcName) {
return !isRemoved(source, funcName);
});
// skip this optimization if there are no iteration methods to use
if (!iteratorName) {
return;
}
var funcNames = [],
objectSnippets = [];
// build replacement code
lodash.forOwn({
'Date': 'dateClass',
'Function': 'funcClass',
'Number': 'numberClass',
'RegExp': 'regexpClass',
'String': 'stringClass'
}, function(value, key) {
var funcName = 'is' + key,
funcCode = matchFunction(source, funcName);
if (funcCode) {
funcNames.push(funcName);
objectSnippets.push("'" + key + "': " + value);
}
});
// skip this optimization if there are less than 2 isType functions
if (funcNames.length < 2) {
return;
}
// remove existing isType functions
funcNames.forEach(function(funcName) {
source = removeFunction(source, funcName);
});
// insert new DRY code after the method assignments
var snippet = getMethodAssignments(source);
source = source.replace(snippet, snippet + '\n' +
' // add `_.' + funcNames.join('`, `_.') + '`\n' +
' ' + iteratorName + '({\n ' + objectSnippets.join(',\n ') + '\n }, function(className, key) {\n' +
" lodash['is' + key] = function(value) {\n" +
' return toString.call(value) == className;\n' +
' };\n' +
' });\n'
);
}());
/*--------------------------------------------------------------------------*/
if (isLegacy) {
['isBindFast', 'nativeBind', 'nativeIsArray', 'nativeKeys'].forEach(function(varName) {
source = removeVar(source, varName);
});
['bind', 'isArray'].forEach(function(funcName) {
var snippet = matchFunction(source, funcName),
modified = snippet;
// remove native `Function#bind` branch in `_.bind`
if (funcName == 'bind') {
modified = modified.replace(/(?:\s*\/\/.*)*\s*else if *\(isBindFast[^}]+}/, '');
}
// remove native `Array.isArray` branch in `_.isArray`
else {
modified = modified.replace(/nativeIsArray * \|\|/, '');
}
source = source.replace(snippet, modified);
});
// replace `_.keys` with `shimKeys`
if (!isRemoved(source, 'keys')) {
source = source.replace(
matchFunction(source, 'keys').replace(/[\s\S]+?var keys *=/, ''),
matchFunction(source, 'shimKeys').replace(/[\s\S]+?var shimKeys *=/, '')
);
source = removeFunction(source, 'shimKeys');
}
// replace `_.isArguments` with fallback
if (!isRemoved(source, 'isArguments')) {
source = source.replace(
matchFunction(source, 'isArguments').replace(/[\s\S]+?function isArguments/, ''),
getIsArgumentsFallback(source).match(/isArguments *= *function([\s\S]+?) *};/)[1] + ' }\n'
);
source = removeIsArgumentsFallback(source);
}
source = removeVar(source, 'reNative');
source = removeFromCreateIterator(source, 'nativeKeys');
source = removeKeysOptimization(source);
}
if (isMobile) {
// inline all functions defined with `createIterator`
lodash.functions(lodash).forEach(function(funcName) {
// match `funcName` with pseudo private `_` prefixes removed to allow matching `shimKeys`
var reFunc = RegExp('(\\bvar ' + funcName.replace(/^_/, '') + ' *= *)createIterator\\(((?:{|[a-zA-Z])[\\s\\S]+?)\\);\\n');
// skip if not defined with `createIterator`
if (!reFunc.test(source)) {
return;
}
// extract, format, and inject the compiled function's source code
source = source.replace(reFunc, '$1' + getFunctionSource(lodash[funcName]) + ';\n');
});
// replace `callee` in `_.merge` with `merge`
source = source.replace(matchFunction(source, 'merge'), function(match) {
return match.replace(/\bcallee\b/g, 'merge');
});
// remove `hasDontEnumBug`, `iteratesOwnLast`, `noArgsEnum` assignment
source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var hasDontEnumBug\b[\s\S]+?}\(1\)\);\n/, '');
// remove `iteratesOwnLast` from `isPlainObject`
source = source.replace(/(?:\s*\/\/.*)*\n( +)if *\(iteratesOwnLast[\s\S]+?\n\1}/, '');
// remove JScript [[DontEnum]] fix from `_.isEqual`
source = source.replace(/(?:\s*\/\/.*)*\n( +)if *\(hasDontEnumBug[\s\S]+?\n\1}/, '');
// remove IE `shift` and `splice` fix from mutator Array functions mixin
source = source.replace(/(?:\s*\/\/.*)*\n( +)if *\(value.length *=== *0[\s\S]+?\n\1}/, '');
// remove `noArraySliceOnStrings` from `_.toArray`
source = source.replace(/noArraySliceOnStrings *\?[^:]+: *([^)]+)/g, '$1');
// remove `noCharByIndex` from `_.reduceRight`
source = source.replace(/noCharByIndex *&&[^:]+: *([^;]+)/g, '$1');
source = removeVar(source, 'extendIteratorOptions');
source = removeVar(source, 'iteratorTemplate');
source = removeVar(source, 'noArraySliceOnStrings');
source = removeVar(source, 'noCharByIndex');
source = removeIsArgumentsFallback(source);
source = removeKeysOptimization(source);
source = removeNoArgsClass(source);
source = removeNoNodeClass(source);
}
else {
// inline `iteratorTemplate` template
source = source.replace(/(( +)var iteratorTemplate *= *)[\s\S]+?\n\2.+?;\n/, (function() {
var snippet = getFunctionSource(lodash._iteratorTemplate);
// prepend data object references to property names to avoid having to
// use a with-statement
iteratorOptions.forEach(function(property) {
snippet = snippet.replace(RegExp('([^\\w.])\\b' + property + '\\b', 'g'), '$1obj.' + property);
});
// remove unnecessary code
snippet = snippet
.replace(/var __t.+/, "var __p = '';")
.replace(/function print[^}]+}/, '')
.replace(/'(?:\\n|\s)+'/g, "''")
.replace(/__p *\+= *' *';/g, '')
.replace(/(__p *\+= *)' *' *\+/g, '$1')
.replace(/(\{) *;|; *(\})/g, '$1$2')
.replace(/\(\(__t *= *\( *([^)]+) *\)\) *== *null *\? *'' *: *__t\)/g, '$1');
// remove the with-statement
snippet = snippet.replace(/ *with *\(.+?\) *{/, '\n').replace(/}([^}]*}[^}]*$)/, '$1');
// minor cleanup
snippet = snippet
.replace(/obj *\|\| *\(obj *= *\{}\);/, '')
.replace(/var __p = '';\s*__p \+=/, 'var __p =');
// remove comments, including sourceURLs
snippet = snippet.replace(/\s*\/\/.*(?:\n|$)/g, '');
return '$1' + snippet + ';\n';
}()));
}
/*--------------------------------------------------------------------------*/
// remove associated functions, variables, and code snippets that the minifier may miss
if (isRemoved(source, 'bind')) {
source = removeVar(source, 'nativeBind');
source = removeVar(source, 'isBindFast');
}
if (isRemoved(source, 'isArray')) {
source = removeVar(source, 'nativeIsArray');
}
if (isRemoved(source, 'keys')) {
source = removeFunction(source, 'shimKeys');
}
if (isRemoved(source, 'mixin')) {
// remove `LoDash` constructor
source = removeFunction(source, 'LoDash');
// remove `LoDash` calls
source = source.replace(/(?:new +LoDash(?!\()|(?:new +)?LoDash\([^)]*\));?/g, '');
// remove `LoDash.prototype` additions
source = source.replace(/(?:\s*\/\/.*)*\s*LoDash.prototype *=[\s\S]+?\/\*-+\*\//, '');
}
if (isRemoved(source, 'template')) {
// remove `templateSettings` assignment
source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *lodash\.templateSettings[\s\S]+?};\n/, '');
}
if (isRemoved(source, 'toArray')) {
source = removeVar(source, 'noArraySliceOnStrings');
}
if (isRemoved(source, 'clone', 'merge')) {
source = removeFunction(source, 'isPlainObject');
}
if (isRemoved(source, 'clone', 'isArguments', 'isEmpty', 'isEqual', 'size')) {
source = removeNoArgsClass(source);
}
if (isRemoved(source, 'isEqual', 'isPlainObject')) {
source = removeNoNodeClass(source);
}
if ((source.match(/\bcreateIterator\b/g) || []).length < 2) {
source = removeFunction(source, 'createIterator');
source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var noArgsEnum;|.+?noArgsEnum *=.+/g, '');
}
if (isRemoved(source, 'createIterator', 'bind', 'isArray', 'keys')) {
source = removeVar(source, 'reNative');
}
if (isRemoved(source, 'createIterator', 'clone', 'merge')) {
source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var iteratesOwnLast;|.+?iteratesOwnLast *=.+/g, '');
}
if (isRemoved(source, 'createIterator', 'isEqual')) {
source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var hasDontEnumBug;|.+?hasDontEnumBug *=.+/g, '');
}
if (isRemoved(source, 'createIterator', 'keys')) {
source = removeVar(source, 'nativeKeys');
}
if (!source.match(/var (?:hasDontEnumBug|iteratesOwnLast|noArgsEnum)\b/g)) {
// remove `hasDontEnumBug`, `iteratesOwnLast`, `noArgsEnum` assignment
source = source.replace(/ *\(function\(\) *{\s*var props\b[\s\S]+?}\(1\)\);/, '');
}
// remove pseudo private properties
source = source.replace(/(?:(?:\s*\/\/.*)*\s*lodash\._[^=]+=.+\n)+/g, '\n');
// consolidate consecutive horizontal rule comment separators
source = source.replace(/(?:\s*\/\*-+\*\/\s*){2,}/g, function(separators) {
return separators.match(/^\s*/)[0] + separators.slice(separators.lastIndexOf('/*'));
});
// cleanup code
source = source.replace(/^ *;\n/gm, '');
// begin the minification process
if (filterType || isBackbone || isLegacy || isMobile || isStrict || isUnderscore) {
writeFile(source, 'lodash.custom.js');
minify(source, 'lodash.custom.min', function(result) {
writeFile(result, 'lodash.custom.min.js');
});
}
else {
minify(source, 'lodash.min', function(result) {
writeFile(result, 'lodash.min.js');
});
}
}());