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 backbone dependencies into `includeMethods` includeMethods = lodash.union(includeMethods, categoryMethods); } else { // include only the Backbone dependencies includeMethods = categoryMethods; } filterType = 'include'; return true; });
;(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')); /** 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 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', 'isArray'], 'compact': [], 'compose': [], 'contains': [], 'debounce': [], 'defaults': [], 'defer': [], 'delay': [], 'difference': ['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': [], 'isEqual': [], 'isFinite': [], 'isFunction': [], 'isNaN': [], 'isNull': [], 'isNumber': [], 'isObject': [], 'isRegExp': [], 'isString': [], 'isUndefined': [], 'keys': [], 'last': [], 'lastIndexOf': [], 'map': ['identity'], 'max': [], 'memoize': [], 'min': [], 'mixin': ['forEach', 'functions'], 'noConflict': [], 'once': [], 'partial': [], 'pick': [], 'pluck': [], 'range': [], 'reduce': [], 'reduceRight': ['keys'], 'reject': ['identity'], 'rest': [], 'result': [], 'shuffle': [], 'size': ['keys'], 'some': ['identity'], 'sortBy': [], 'sortedIndex': ['bind'], 'tap': [], 'template': ['escape'], 'throttle': [], 'times': [], 'toArray': ['values'], 'union': ['indexOf'], 'uniq': ['identity', 'indexOf'], 'uniqueId': [], 'values': [], 'without': ['indexOf'], 'wrap': [], 'zip': ['max', 'pluck'] }; /** Collections of method names */ var excludeMethods, includeMethods, allMethods = Object.keys(dependencyMap); /** 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]; }, ''); /** Flag used to specify a backbone build */ var isBackbone = process.argv.indexOf('backbone') > -1; /** Flag used to specify a mobile build */ var isMobile = process.argv.indexOf('mobile') > -1; /*--------------------------------------------------------------------------*/ /** * 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 (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 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 `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 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').match(/Function\([\s\S]+$/)[0], 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 `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 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(/(?: *\/\/.*)*\s*if *\(!isArguments[^)]+\)[\s\S]+?};?\s*}\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 + ' *=.*?;'), ';'); 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; }); } /*--------------------------------------------------------------------------*/ // Backbone build if (isBackbone) { // add any additional dependencies backboneDependencies = getDependencies(backboneDependencies); if (filterType == 'exclude') { // remove excluded methods from `backboneDependencies` includeMethods = lodash.without.apply(lodash, [backboneDependencies].concat(excludeMethods)); } else if (filterType) { // merge backbone dependencies into `includeMethods` includeMethods = lodash.union(includeMethods, backboneDependencies); } else { // include only the Backbone dependencies includeMethods = backboneDependencies; } filterType = 'include'; } /*--------------------------------------------------------------------------*/ // 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 backbone dependencies into `includeMethods` includeMethods = lodash.union(includeMethods, categoryMethods); } else { // include only the Backbone dependencies 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 associated functions, variables and code snippets if (isRemoved(source, 'isArguments')) { source = removeIsArgumentsFallback(source); } 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) { // 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 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'); }); source = removeIsArgumentsFallback(source); source = removeVar(source, 'iteratorTemplate'); // remove JScript [[DontEnum]] fix from `isEqual` source = source.replace(/(?:\s*\/\/.*\n)*( +)if *\(result *&& *hasDontEnumBug[\s\S]+?\n\1}/, ''); // remove IE `shift` and `splice` fix source = source.replace(/(?:\s*\/\/.*\n)*( +)if *\(value.length *=== *0[\s\S]+?\n\1}/, ''); } 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 || isBackbone || 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); }); } }());