Files
lodash/build/pre-compile.js
John-David Dalton 80d0b5d4ed Add _.where. [closes #22].
Former-commit-id: dec7a9d0df4158a395ec84fb9e774ed20205d421
2012-07-29 13:48:21 -07:00

414 lines
12 KiB
JavaScript

#!/usr/bin/env node
;(function() {
'use strict';
/** The Node filesystem module */
var fs = require('fs');
/** Used to minify variables embedded in compiled strings */
var compiledVars = [
'accumulator',
'args',
'arrayClass',
'arrayLikeClasses',
'ArrayProto',
'bind',
'callback',
'collection',
'compareAscending',
'concat',
'ctor',
'forIn',
'funcClass',
'funcs',
'hasOwnProperty',
'identity',
'index',
'indexOf',
'isArguments',
'isFunc',
'iteratee',
'iterateeIndex',
'iteratorBind',
'length',
'methodName',
'nativeKeys',
'noaccum',
'object',
'objectTypes',
'ownIndex',
'ownProps',
'pass',
'prop',
'properties',
'property',
'propertyIsEnumerable',
'propIndex',
'props',
'propsLength',
'result',
'skipProto',
'slice',
'stringClass',
'target',
'thisArg',
'toString',
'value',
'valueProp'
];
/** Used to minify `compileIterator` option properties */
var iteratorOptions = [
'args',
'array',
'arrayBranch',
'beforeLoop',
'bottom',
'exit',
'firstArg',
'hasDontEnumBug',
'inLoop',
'init',
'isKeysFast',
'object',
'objectBranch',
'noCharByIndex',
'shadowed',
'top',
'useHas',
'useStrict'
];
/** Used to minify variables and string values to a single character */
var minNames = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
/** Used to protect the specified properties from getting minified */
var propWhitelist = [
'_',
'_chain',
'_wrapped',
'after',
'all',
'amd',
'any',
'attachEvent',
'bind',
'bindAll',
'chain',
'clearTimeout',
'clone',
'collect',
'compact',
'compose',
'contains',
'countBy',
'criteria',
'debounce',
'defaults',
'defer',
'delay',
'detect',
'difference',
'drop',
'each',
'environment',
'escape',
'escape',
'evaluate',
'every',
'extend',
'filter',
'find',
'first',
'flatten',
'foldl',
'foldr',
'forEach',
'forIn',
'forOwn',
'functions',
'groupBy',
'has',
'head',
'identity',
'include',
'indexOf',
'initial',
'inject',
'interpolate',
'intersection',
'invoke',
'isArguments',
'isArray',
'isBoolean',
'isDate',
'isElement',
'isEmpty',
'isEqual',
'isEqual',
'isFinite',
'isFinite',
'isFunction',
'isNaN',
'isNull',
'isNumber',
'isObject',
'isRegExp',
'isString',
'isUndefined',
'keys',
'last',
'lastIndexOf',
'map',
'max',
'memoize',
'methods',
'min',
'mixin',
'noConflict',
'once',
'opera',
'partial',
'pick',
'pluck',
'range',
'reduce',
'reduceRight',
'reject',
'rest',
'result',
'select',
'setTimeout',
'shuffle',
'size',
'some',
'sortBy',
'sortedIndex',
'source',
'tail',
'take',
'tap',
'template',
'templateSettings',
'throttle',
'times',
'toArray',
'union',
'uniq',
'unique',
'uniqueId',
'value',
'values',
'variable',
'VERSION',
'where',
'without',
'wrap',
'zip',
'zipObject'
];
/*--------------------------------------------------------------------------*/
/**
* Pre-process a given Lo-Dash `source`, preparing it for minification.
*
* @param {String} source The source to process.
* @returns {String} Returns the processed source.
*/
function preprocess(source) {
// remove copyright to add later in post-compile.js
source = source.replace(/\/\*![\s\S]+?\*\//, '');
// remove unrecognized JSDoc tags so Closure Compiler won't complain
source = source.replace(/@(?:alias|category)\b.*/g, '');
// manually convert `arrayLikeClasses` property assignments because
// Closure Compiler errors trying to minify them
source = source.replace(/(arrayLikeClasses =)[\s\S]+?= *true/g,
"$1{'[object Arguments]': true, '[object Array]': true, '[object Boolean]': false, " +
"'[object Date]': false, '[object Function]': false, '[object Number]': false, " +
"'[object Object]': false, '[object RegExp]': false, '[object String]': true }"
);
// manually convert `cloneableClasses` property assignments because
// Closure Compiler errors trying to minify them
source = source.replace(/(cloneableClasses =)[\s\S]+?= *true/g,
"$1{'[object Arguments]': false, '[object Array]': true, '[object Boolean]': true, " +
"'[object Date]': true, '[object Function]': false, '[object Number]': true, " +
"'[object Object]': true, '[object RegExp]': true, '[object String]': true }"
);
// add brackets to whitelisted properties so Closure Compiler won't mung them
// http://code.google.com/closure/compiler/docs/api-tutorial3.html#export
source = source.replace(RegExp('\\.(' + propWhitelist.join('|') + ')\\b', 'g'), "['$1']");
// remove brackets from `_.escape()` in `tokenizeEscape`
source = source.replace(/_\['escape']\("/, '_.escape("');
// remove brackets from `_.escape()` in `_.template`
source = source.replace(/__e *= *_\['escape']/, '__e=_.escape');
// remove brackets from `collection.indexOf` in `_.contains`
source = source.replace("collection['indexOf'](target)", 'collection.indexOf(target)');
// remove brackets from `result[length].value` in `_.sortBy`
source = source.replace("result[length]['value']", 'result[length].value');
// remove whitespace from string literals
source = source.replace(/'(?:(?=(\\?))\1.)*?'/g, function(string) {
// avoids removing the '\n' of the `stringEscapes` object
return string.replace(/\[object |else if|function | in |return\s+[\w']|throw |typeof |use strict|var |@ |'\\n'|\\\\n|\\n|\s+/g, function(match) {
return match == false || match == '\\n' ? '' : match;
});
});
// remove whitespace from `_.template` related regexes
source = source.replace(/(?:reDelimiterCode\w+|reEmptyString\w+|reInsertVariable) *=.+/g, function(match) {
return match.replace(/ |\\n/g, '');
});
// remove newline from double-quoted strings in `_.template`
source = source
.replace('"\';\\n__with ("', '"\';__with("')
.replace('"\\n}__\\n__p += \'"', '"}____p+=\'"')
.replace('"__p = \'"', '"__p=\'"')
.replace('"\';\\n"', '"\';"')
.replace("') {\\n'", "'){'")
// remove `useSourceURL` variable
source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *try *\{(?:\s*\/\/.*\n)* *var useSourceURL[\s\S]+?catch[^}]+}\n/, '');
// remove debug sourceURL use in `_.template`
source = source.replace(/(?:\s*\/\/.*\n)* *if *\(useSourceURL[^}]+}/, '');
// minify internal properties used by `_.sortBy`
(function() {
var properties = ['criteria', 'value'],
snippets = source.match(/( +)(?:function compareAscending|var sortBy)\b[\s\S]+?\n\1}/g);
if (!snippets) {
return;
}
snippets.forEach(function(snippet) {
var modified = snippet,
isSortBy = /var sortBy\b/.test(modified),
isInlined = !/\bcreateIterator\b/.test(modified);
// minify properties
properties.forEach(function(property, index) {
var reBracketProp = RegExp("\\['" + property + '\\b', 'g'),
reDotProp = RegExp('\\.' + property + '\\b', 'g'),
rePropColon = RegExp('\\b' + property + ' *:', 'g');
// add quotes around properties in the inlined `_.sortBy` of the mobile
// build so Closure Compiler won't mung them
if (isSortBy && isInlined) {
modified = modified
.replace(reDotProp, "['" + minNames[index] + "']")
.replace(rePropColon, "'" + minNames[index] + "':");
}
else {
modified = modified
.replace(reBracketProp, "['" + minNames[index])
.replace(reDotProp, '.' + minNames[index])
.replace(rePropColon, minNames[index] + ':');
}
});
// replace with modified snippet
source = source.replace(snippet, modified);
});
}());
// minify all compilable snippets
var snippets = source.match(
RegExp([
// match the `iteratorTemplate`
'( +)var iteratorTemplate\\b[\\s\\S]+?\\n\\1}',
// match methods created by `createIterator` calls
'createIterator\\((?:{|[a-zA-Z]+)[\\s\\S]+?\\);\\n',
// match variables storing `createIterator` options
'( +)var [a-zA-Z]+IteratorOptions\\b[\\s\\S]+?\\n\\2}',
// match the the `createIterator` function
'( +)function createIterator\\b[\\s\\S]+?\\n\\3}'
].join('|'), 'g')
);
// exit early if no compilable snippets
if (!snippets) {
return source;
}
snippets.forEach(function(snippet, index) {
var isCreateIterator = /function createIterator\b/.test(snippet),
isIteratorTemplate = /var iteratorTemplate\b/.test(snippet),
modified = snippet;
// add brackets to whitelisted properties so Closure Compiler won't mung them
modified = modified.replace(RegExp('\\.(' + iteratorOptions.join('|') + ')\\b', 'g'), "['$1']");
if (isCreateIterator) {
// replace with modified snippet early and clip snippet to the `factory`
// call so other arguments aren't minified
source = source.replace(snippet, modified);
snippet = modified = modified.replace(/factory\([\s\S]+$/, '');
}
// minify snippet variables / arguments
compiledVars.forEach(function(variable, index) {
// ensure properties in compiled strings aren't minified
modified = modified.replace(RegExp('([^.]\\b)' + variable + '\\b(?!\' *[\\]:])', 'g'), '$1' + minNames[index]);
// correct `typeof x == 'object'`
if (variable == 'object') {
modified = modified.replace(RegExp("(typeof [^']+')" + minNames[index] + "'", 'g'), "$1object'");
}
});
// minify `createIterator` option property names
iteratorOptions.forEach(function(property, index) {
if (isIteratorTemplate) {
// minify property names as interpolated template variables
modified = modified.replace(RegExp('\\b' + property + '\\b', 'g'), minNames[index]);
}
else {
if (property == 'array' || property == 'object') {
// minify "array" and "object" sub property names
modified = modified.replace(RegExp("'" + property + "'( *[\\]:])", 'g'), "'" + minNames[index] + "'$1");
}
else {
// minify property name strings
modified = modified.replace(RegExp("'" + property + "'", 'g'), "'" + minNames[index] + "'");
// minify property names in regexes and accessors
if (isCreateIterator) {
modified = modified.replace(RegExp('([\\.|/])' + property + '\\b' , 'g'), '$1' + minNames[index]);
}
}
}
});
// replace with modified snippet
source = source.replace(snippet, modified);
});
return source;
}
/*--------------------------------------------------------------------------*/
// expose `preprocess`
if (module != require.main) {
module.exports = preprocess;
}
else {
// read the Lo-Dash source file from the first argument if the script
// was invoked directly (e.g. `node pre-compile.js source.js`) and write to
// the same file
(function() {
var source = fs.readFileSync(process.argv[2], 'utf8');
fs.writeFileSync(process.argv[2], preprocess(source), 'utf8');
}());
}
}());