mirror of
https://github.com/whoisclebs/lodash.git
synced 2026-01-31 15:27:50 +00:00
526 lines
17 KiB
JavaScript
Executable File
526 lines
17 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
;(function() {
|
|
'use strict';
|
|
|
|
/** The Node filesystem and path modules */
|
|
var fs = require('fs'),
|
|
path = require('path');
|
|
|
|
/** Load other modules */
|
|
var lodash = require(path.join(__dirname, 'lodash')),
|
|
minify = require(path.join(__dirname, 'build', 'minify'));
|
|
|
|
/** Flag used to specify a mobile build */
|
|
var isMobile = process.argv.indexOf('mobile') > -1;
|
|
|
|
/** 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');
|
|
|
|
/** 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 function dependencies */
|
|
var dependencyMap = {
|
|
'after': [],
|
|
'bind': [],
|
|
'bindAll': ['bind'],
|
|
'chain': ['mixin'],
|
|
'clone': ['extend', 'isArray'],
|
|
'compact': [],
|
|
'compose': [],
|
|
'contains': ['createIterator'],
|
|
'createIterator': [],
|
|
'debounce': [],
|
|
'defaults': ['createIterator'],
|
|
'defer': [],
|
|
'delay': [],
|
|
'difference': ['indexOf'],
|
|
'escape': [],
|
|
'every': ['createIterator', 'identity'],
|
|
'extend': ['createIterator'],
|
|
'filter': ['createIterator', 'identity'],
|
|
'find': ['createIterator'],
|
|
'first': [],
|
|
'flatten': ['isArray'],
|
|
'forEach': ['createIterator'],
|
|
'forIn': ['createIterator'],
|
|
'forOwn': ['createIterator'],
|
|
'functions': ['createIterator'],
|
|
'groupBy': ['createIterator'],
|
|
'has': [],
|
|
'identity': [],
|
|
'indexOf': ['sortedIndex'],
|
|
'initial': [],
|
|
'intersection': ['every', 'indexOf'],
|
|
'invoke': [],
|
|
'isArguments': [],
|
|
'isArray': [],
|
|
'isBoolean': [],
|
|
'isDate': [],
|
|
'isElement': [],
|
|
'isEmpty': ['createIterator'],
|
|
'isEqual': [],
|
|
'isFinite': [],
|
|
'isFunction': [],
|
|
'isNaN': [],
|
|
'isNull': [],
|
|
'isNumber': [],
|
|
'isObject': [],
|
|
'isRegExp': [],
|
|
'isString': [],
|
|
'isUndefined': [],
|
|
'keys': ['createIterator'],
|
|
'last': [],
|
|
'lastIndexOf': [],
|
|
'map': ['createIterator', 'identity'],
|
|
'max': [],
|
|
'memoize': [],
|
|
'min': [],
|
|
'mixin': ['forEach'],
|
|
'noConflict': [],
|
|
'once': [],
|
|
'partial': [],
|
|
'pick': [],
|
|
'pluck': ['createIterator'],
|
|
'range': [],
|
|
'reduce': ['createIterator'],
|
|
'reduceRight': ['keys'],
|
|
'reject': ['createIterator', 'identity'],
|
|
'rest': [],
|
|
'result': [],
|
|
'shuffle': [],
|
|
'size': ['keys'],
|
|
'some': ['createIterator', 'identity'],
|
|
'sortBy': ['map', 'pluck'],
|
|
'sortedIndex': ['identity'],
|
|
'tap': [],
|
|
'template': ['escape'],
|
|
'throttle': [],
|
|
'times': [],
|
|
'toArray': ['values'],
|
|
'union': ['indexOf'],
|
|
'uniq': ['identity', 'indexOf'],
|
|
'uniqueId': [],
|
|
'values': ['createIterator'],
|
|
'without': ['indexOf'],
|
|
'wrap': [],
|
|
'zip': ['max', 'pluck']
|
|
};
|
|
|
|
/** Names of all methods */
|
|
var allMethods = Object.keys(dependencyMap);
|
|
|
|
/** Names of methods to filter for the build */
|
|
var filterMethods = allMethods;
|
|
|
|
/** Used to specify whether `filterMethods` is used for exclusion or inclusion */
|
|
var filterType = process.argv.reduce(function(result, value) {
|
|
if (result) {
|
|
return result;
|
|
}
|
|
var pair = value.match(/^(category|exclude|include)=(.*)$/);
|
|
if (!pair) {
|
|
return result;
|
|
}
|
|
|
|
result = pair[1];
|
|
filterMethods = pair[2].split(/, */).map(getRealName);
|
|
|
|
if (result == 'category') {
|
|
// resolve method names belonging to each category
|
|
filterMethods = filterMethods.reduce(function(result, category) {
|
|
return result.concat(allMethods.filter(function(funcName) {
|
|
return RegExp('@category ' + category + '\\b', 'i').test(matchFunction(source, funcName));
|
|
}));
|
|
}, []);
|
|
}
|
|
else {
|
|
// remove nonexistent method names
|
|
filterMethods = lodash.intersection(allMethods, filterMethods);
|
|
}
|
|
return result;
|
|
}, '');
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Gets the aliases associated with a given `funcName`.
|
|
*
|
|
* @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 an array of depenants for a function by the given `funcName`.
|
|
*
|
|
* @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 (dependencies.indexOf(funcName) > -1) {
|
|
result.push(otherName);
|
|
}
|
|
return result;
|
|
}, []);
|
|
}
|
|
|
|
/**
|
|
* Gets an array of dependencies for a function of the given `funcName`.
|
|
*
|
|
* @private
|
|
* @param {String} funcName The name of the function to query.
|
|
* @returns {Array} Returns an array of function dependencies.
|
|
*/
|
|
function getDependencies(funcName) {
|
|
var dependencies = dependencyMap[funcName],
|
|
result = [];
|
|
|
|
if (!dependencies) {
|
|
return result;
|
|
}
|
|
// recursively accumulate the dependencies of the `funcName` function, and
|
|
// the dependencies of its dependencies, and so on.
|
|
return dependencies.reduce(function(result, otherName) {
|
|
result.push.apply(result, getDependencies(otherName).concat(otherName));
|
|
return result;
|
|
}, result);
|
|
}
|
|
|
|
/**
|
|
* Gets the real name, not alias, of a given `funcName`.
|
|
*
|
|
* @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 the `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 the `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 the `createIterator` 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'),
|
|
modified = snippet.replace(RegExp('\\b' + refName + '\\b,? *', 'g'), '');
|
|
|
|
return source.replace(snippet, modified);
|
|
}
|
|
|
|
/**
|
|
* Removes the `funcName` function declaration, expression, or assignment and
|
|
* associated code from the `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 = source.match(/lodash\.VERSION *= *[\s\S]+?\/\*-+\*\/\n/)[0];
|
|
|
|
// 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 a given variable from the `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 + ' *=.*?;'), ';');
|
|
|
|
return removeFromCreateIterator(source, varName);
|
|
}
|
|
|
|
/**
|
|
* Removes non-syntax critical whitespace from a string.
|
|
*
|
|
* @private
|
|
* @param {String} source The source to process.
|
|
* @returns {String} Returns the source with whitespace removed.
|
|
*/
|
|
function removeWhitespace(source) {
|
|
return source.replace(/\[object |else if|function | in |return\s+[\w']|throw |typeof |var |\\\\n|\\n|\s+/g, function(match) {
|
|
return match == false || match == '\\n' ? '' : match;
|
|
});
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
// custom build
|
|
(function() {
|
|
// exit early if "category", "exclude", or "include" options aren't specified
|
|
if (!filterType) {
|
|
return;
|
|
}
|
|
if (filterType == 'exclude') {
|
|
// remove the specified functions and their dependants
|
|
filterMethods.forEach(function(funcName) {
|
|
getDependants(funcName).concat(funcName).forEach(function(otherName) {
|
|
source = removeFunction(source, otherName);
|
|
});
|
|
});
|
|
}
|
|
else {
|
|
// add dependencies to `filterMethods`
|
|
filterMethods = lodash.uniq(filterMethods.reduce(function(result, funcName) {
|
|
result.push.apply(result, getDependencies(funcName).concat(funcName));
|
|
return result;
|
|
}, []));
|
|
|
|
// remove methods not included in `filterMethods`
|
|
lodash.each(dependencyMap, function(dependencies, otherName) {
|
|
if (filterMethods.indexOf(otherName) < 0) {
|
|
source = removeFunction(source, otherName);
|
|
}
|
|
});
|
|
}
|
|
|
|
// remove associated functions, variables and code snippets
|
|
if (isRemoved(source, 'isArguments')) {
|
|
// remove `isArguments` if-statement
|
|
source = source.replace(/(?:\s*\/\/.*)*\s*if *\(!isArguments[^)]+\)[\s\S]+?};?\s*}\n/, '');
|
|
}
|
|
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, 'isArray', 'isEmpty', 'isEqual', 'size')) {
|
|
source = removeVar(source, 'arrayClass');
|
|
}
|
|
if (isRemoved(source, 'bind', 'functions', 'groupBy', 'invoke', 'isEqual', 'isFunction', 'result', 'sortBy', 'toArray')) {
|
|
source = removeVar(source, 'funcClass');
|
|
}
|
|
if (isRemoved(source, 'bind')) {
|
|
source = removeVar(source, 'nativeBind');
|
|
}
|
|
if (isRemoved(source, 'isArray')) {
|
|
source = removeVar(source, 'nativeIsArray');
|
|
}
|
|
if (isRemoved(source, 'keys')) {
|
|
source = removeVar(source, 'nativeKeys');
|
|
}
|
|
if (isRemoved(source, 'clone', 'isObject', 'keys')) {
|
|
source = removeVar(source, 'objectTypes');
|
|
source = removeFromCreateIterator(source, 'objectTypes');
|
|
}
|
|
if (isRemoved(source, 'bind', 'isArray', 'keys')) {
|
|
source = removeVar(source, 'reNative');
|
|
}
|
|
if (isRemoved(source, 'isEmpty', 'isEqual', 'isString', 'size')) {
|
|
source = removeVar(source, 'stringClass');
|
|
}
|
|
|
|
// consolidate consecutive horizontal rule comment separators
|
|
source = source.replace(/(?:\s*\/\*-+\*\/\s*){2,}/g, function(separators) {
|
|
return separators.match(/^\s*/)[0] + separators.slice(separators.lastIndexOf('/*'));
|
|
});
|
|
}());
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
if (isMobile) {
|
|
// inline functions defined with `createIterator`
|
|
lodash.functions(lodash).forEach(function(funcName) {
|
|
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 and format the function's code
|
|
var code = (lodash[funcName] + '').replace(/\n(?:.*)/g, function(match) {
|
|
match = match.slice(1);
|
|
return (match == '}' ? '\n ' : '\n ') + match;
|
|
});
|
|
|
|
source = source.replace(reFunc, '$1' + code + ';\n');
|
|
});
|
|
|
|
// remove `iteratorTemplate`
|
|
source = removeVar(source, 'iteratorTemplate');
|
|
|
|
// remove JScript [[DontEnum]] fix from `isEqual`
|
|
source = source.replace(/(?:\s*\/\/.*\n)*( +)if *\(result *&& *hasDontEnumBug[\s\S]+?\n\1}\n/, '\n');
|
|
|
|
// remove IE `shift` and `splice` fix
|
|
source = source.replace(/(?:\s*\/\/.*\n)*( +)if *\(value.length *=== *0[\s\S]+?\n\1}\n/, '\n');
|
|
}
|
|
else {
|
|
// inline `iteratorTemplate` template
|
|
source = source.replace(/(( +)var iteratorTemplate *= *)([\s\S]+?\n\2.+?);\n/, (function() {
|
|
// extract `iteratorTemplate` code
|
|
var code = /^function[^{]+{([\s\S]+?)}$/.exec(lodash._iteratorTemplate)[1];
|
|
|
|
code = removeWhitespace(code)
|
|
// remove unnecessary code
|
|
.replace(/\|\|\{\}|,__t,__j=Array.prototype.join|function print[^}]+}|\+''/g, '')
|
|
.replace(/(\{);|;(\})/g, '$1$2')
|
|
.replace(/\(\(__t=\(([^)]+)\)\)==null\?'':__t\)/g, '$1')
|
|
// ensure escaped characters are interpreted correctly in the string literal
|
|
.replace(/\\/g, '\\\\');
|
|
|
|
// add `code` to `Function()` as a string literal to avoid strict mode
|
|
// errors caused by the required with-statement
|
|
return '$1Function(\'obj\',\n$2 "' + code + '"\n$2);\n';
|
|
}()));
|
|
}
|
|
|
|
// remove pseudo private properties
|
|
source = source.replace(/(?:(?:\s*\/\/.*)*\s*lodash\._[^=]+=.+\n)+/g, '\n');
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
// begin the minification process
|
|
if (filterType || isMobile) {
|
|
fs.writeFileSync(path.join(__dirname, 'lodash.custom.js'), source);
|
|
minify(source, 'lodash.custom.min', function(result) {
|
|
fs.writeFileSync(path.join(__dirname, 'lodash.custom.min.js'), result);
|
|
});
|
|
}
|
|
else {
|
|
minify(source, 'lodash.min', function(result) {
|
|
fs.writeFileSync(path.join(__dirname, 'lodash.min.js'), result);
|
|
});
|
|
}
|
|
}());
|