Rewrite build.js to be used as a module.

Former-commit-id: bf6425925e511a327b5297f9b17620a97ff53b67
This commit is contained in:
John-David Dalton
2012-09-06 20:36:24 -07:00
parent a2a3bb291f
commit a742b5f3e2

383
build.js
View File

@@ -2,111 +2,19 @@
;(function() {
'use strict';
/** The debug version of `source` */
var debugSource;
/** Load modules */
var fs = require('fs'),
path = require('path'),
vm = require('vm'),
minify = require(path.join(__dirname, 'build', 'minify'));
/** The arguments passed to build.js */
var argv = process.argv;
minify = require(path.join(__dirname, 'build', 'minify')),
_ = require(path.join(__dirname, 'lodash'));
/** The current working directory */
var cwd = process.cwd();
/** Flag used to specify a Backbone build */
var isBackbone = argv.indexOf('backbone') > -1;
/** Flag used to specify a Content Security Policy build */
var isCSP = argv.indexOf('csp') > -1 || argv.indexOf('CSP') > -1;
/** Flag used to specify a legacy build */
var isLegacy = argv.indexOf('legacy') > -1;
/** Flag used to specify an Underscore build */
var isUnderscore = argv.indexOf('underscore') > -1;
/** Flag used to specify a mobile build */
var isMobile = !isLegacy && (isCSP || isUnderscore || argv.indexOf('mobile') > -1);
/**
* Flag used to specify `_.bindAll`, `_.extend`, and `_.defaults` are
* constructed using the "use strict" directive.
*/
var isStrict = argv.indexOf('strict') > -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 {
source = removeUseStrictDirective(source);
if (!useStrict) {
source = setUseStrictOption(source, false);
}
}
if (isLegacy) {
source = replaceVar(source, 'noArgsClass', 'true');
['isBindFast', 'isKeysFast', 'isStrictFast', 'nativeBind', 'nativeIsArray', 'nativeKeys'].forEach(function(varName) {
source = replaceVar(source, varName, 'false');
});
}
else if (isUnderscore) {
// remove `deep` clone functionality
source = source.replace(/( +)function clone[\s\S]+?\n\1}/, [
' function clone(value) {',
' if (value == null) {',
' return value;',
' }',
' var isObj = objectTypes[typeof value];',
' if (isObj && value.clone && isFunction(value.clone)) {',
' return value.clone(deep);',
' }',
' if (isObj) {',
' var className = toString.call(value);',
' if (!cloneableClasses[className] || (noArgsClass && isArguments(value))) {',
' return value;',
' }',
' var isArr = className == arrayClass;',
' }',
' return isObj',
' ? (isArr ? slice.call(value) : extend({}, value))',
' : value;',
' }'
].join('\n'));
}
if (isMobile) {
source = replaceVar(source, 'isKeysFast', 'false');
// remove Opera 10.53-10.60 JIT fixes
source = source.replace(/length *> *-1 *&& *length/g, 'length');
// remove `prototype` [[Enumerable]] fix from `_.keys`
source = source.replace(/(?:\s*\/\/.*)*\n( +)if *\(.+?propertyIsEnumerable[\s\S]+?\n\1}/, '');
// remove `prototype` [[Enumerable]] fix from `iteratorTemplate`
source = source
.replace(/(?: *\/\/.*\n)*\s*' *(?:<% *)?if *\(!hasDontEnumBug *(?:&&|\))[\s\S]+?<% *} *(?:%>|').+/g, '')
.replace(/!hasDontEnumBug *\|\|/g, '');
}
vm.runInNewContext(source, sandbox);
return sandbox._;
}());
/** Used to associate aliases with their real names */
var aliasToRealMap = {
'all': 'every',
@@ -286,23 +194,6 @@
'zip': ['max', 'pluck']
};
/** Used to report invalid arguments */
var invalidArgs = lodash.reject(argv.slice(2), function(value) {
if (/^(?:category|exclude|include)=(?:.*)$/.test(value)) {
return true;
}
return [
'backbone',
'csp',
'legacy',
'mobile',
'strict',
'underscore',
'-h', '--help',
'-V', '--version'
].indexOf(value) > -1;
});
/** Used to inline `iteratorTemplate` */
var iteratorOptions = [
'args',
@@ -329,9 +220,9 @@
/** Collections of method names */
var excludeMethods = [],
includeMethods = [],
allMethods = Object.keys(dependencyMap);
allMethods = _.keys(dependencyMap);
var underscoreMethods = lodash.without.apply(lodash, [allMethods].concat([
var underscoreMethods = _.without.apply(_, [allMethods].concat([
'countBy',
'forIn',
'forOwn',
@@ -346,27 +237,6 @@
'where'
]));
/** Used to specify whether filtering is for exclusion or inclusion */
var filterType = 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];
}, '');
/*--------------------------------------------------------------------------*/
/**
@@ -432,8 +302,8 @@
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)) {
return _.reduce(dependencyMap, function(result, dependencies, otherName) {
if (_.contains(dependencies, funcName)) {
result.push(otherName);
}
return result;
@@ -457,7 +327,7 @@
}
// 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) {
return _.uniq(dependencies.reduce(function(result, otherName) {
result.push.apply(result, getDependencies(otherName).concat(otherName));
return result;
}, []));
@@ -672,17 +542,6 @@
.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`.
*
@@ -764,26 +623,156 @@
}
/**
* Writes `source` to a file with the given `filename` to the current
* working directory.
* Post-process the minified source.
*
* @private
* @param {String} source The source to write.
* @param {String} filename The name of the file.
* @param {String} source The minified source to process.
* @returns {String} The modified source.
*/
function writeFile(source, filename) {
function postMinify(source) {
// correct overly aggressive Closure Compiler minification
source = source.replace(/prototype\s*=\s*{\s*valueOf\s*:\s*1\s*}/, 'prototype={valueOf:1,y:1}');
// re-remove "use strict" added by the minifier
if (!isStrict) {
source = removeUseStrictDirective(source);
}
fs.writeFileSync(path.join(cwd, filename), source);
return source.replace(/prototype\s*=\s*{\s*valueOf\s*:\s*1\s*}/, 'prototype={valueOf:1,y:1}');
}
/*--------------------------------------------------------------------------*/
/**
* Creates a debug and minified build, executing the `callback` for each.
* The `callback` is invoked with 2 arguments; (filepath, source)
*
* @param {Array} options The options array.
* @param {Function} callback The function called per build.
*/
function build(options, callback) {
options || (options = []);
// the debug version of `source`
var debugSource;
// flag used to specify a Backbone build
var isBackbone = options.indexOf('backbone') > -1;
// flag used to specify a Content Security Policy build
var isCSP = options.indexOf('csp') > -1 || options.indexOf('CSP') > -1;
// flag used to specify a legacy build
var isLegacy = options.indexOf('legacy') > -1;
// flag used to specify an Underscore build
var isUnderscore = options.indexOf('underscore') > -1;
// flag used to specify a mobile build
var isMobile = !isLegacy && (isCSP || isUnderscore || options.indexOf('mobile') > -1);
// flag used to specify `_.bindAll`, `_.extend`, and `_.defaults` are
// constructed using the "use strict" directive
var isStrict = options.indexOf('strict') > -1;
// flag used to specify if the build should include the "use strict" directive
var useStrict = isStrict || !(isLegacy || isMobile);
// 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 {
// remove "use strict" directive
source = source.replace(/(["'])use strict\1;( *\n)?/, '');
if (!useStrict) {
source = setUseStrictOption(source, false);
}
}
if (isLegacy) {
source = replaceVar(source, 'noArgsClass', 'true');
['isBindFast', 'isKeysFast', 'isStrictFast', 'nativeBind', 'nativeIsArray', 'nativeKeys'].forEach(function(varName) {
source = replaceVar(source, varName, 'false');
});
}
else if (isUnderscore) {
// remove `deep` clone functionality
source = source.replace(/( +)function clone[\s\S]+?\n\1}/, [
' function clone(value) {',
' if (value == null) {',
' return value;',
' }',
' var isObj = objectTypes[typeof value];',
' if (isObj && value.clone && isFunction(value.clone)) {',
' return value.clone(deep);',
' }',
' if (isObj) {',
' var className = toString.call(value);',
' if (!cloneableClasses[className] || (noArgsClass && isArguments(value))) {',
' return value;',
' }',
' var isArr = className == arrayClass;',
' }',
' return isObj',
' ? (isArr ? slice.call(value) : extend({}, value))',
' : value;',
' }'
].join('\n'));
}
if (isMobile) {
source = replaceVar(source, 'isKeysFast', 'false');
// remove Opera 10.53-10.60 JIT fixes
source = source.replace(/length *> *-1 *&& *length/g, 'length');
// remove `prototype` [[Enumerable]] fix from `_.keys`
source = source.replace(/(?:\s*\/\/.*)*\n( +)if *\(.+?propertyIsEnumerable[\s\S]+?\n\1}/, '');
// remove `prototype` [[Enumerable]] fix from `iteratorTemplate`
source = source
.replace(/(?: *\/\/.*\n)*\s*' *(?:<% *)?if *\(!hasDontEnumBug *(?:&&|\))[\s\S]+?<% *} *(?:%>|').+/g, '')
.replace(/!hasDontEnumBug *\|\|/g, '');
}
vm.runInNewContext(source, sandbox);
return sandbox._;
}());
// used to specify whether filtering is for exclusion or inclusion
var filterType = options.reduce(function(result, value) {
if (result) {
return result;
}
var pair = value.match(/^(exclude|include)=(.*)$/);
if (!pair) {
return result;
}
// remove nonexistent method names
var methodNames = _.intersection(allMethods, pair[2].split(/, */).map(getRealName));
if (pair[1] == 'exclude') {
excludeMethods = methodNames;
} else {
includeMethods = methodNames;
}
// return `filterType`
return pair[1];
}, '');
// used to report invalid arguments
var invalidArgs = _.reject(options, function(value) {
if (/^(?:category|exclude|include)=(?:.*)$/.test(value)) {
return true;
}
return [
'backbone',
'csp',
'legacy',
'mobile',
'strict',
'underscore',
'-h', '--help',
'-V', '--version'
].indexOf(value) > -1;
});
// report invalid arguments
if (invalidArgs.length) {
console.log(
@@ -792,26 +781,26 @@
' passed: ' + invalidArgs.join(', ')
);
displayHelp();
process.exit();
return;
}
// display help message
if (lodash.find(argv, function(arg) {
if (_.find(options, function(arg) {
return /^(?:-h|--help)$/.test(arg);
})) {
displayHelp();
process.exit();
return;
}
// display `lodash.VERSION`
if (lodash.find(argv, function(arg) {
if (_.find(options, function(arg) {
return /^(?:-V|--version)$/.test(arg);
})) {
console.log(lodash.VERSION);
process.exit();
console.log(_.VERSION);
return;
}
/*--------------------------------------------------------------------------*/
/*------------------------------------------------------------------------*/
// don't expose `_.forIn` or `_.forOwn` if `isUnderscore` is `true` unless
// requested by `include`
@@ -841,11 +830,11 @@
if (filterType == 'exclude') {
// remove excluded methods from `methodNames`
includeMethods = lodash.without.apply(lodash, [methodNames].concat(excludeMethods));
includeMethods = _.without.apply(_, [methodNames].concat(excludeMethods));
}
else if (filterType) {
// merge `methodNames` into `includeMethods`
includeMethods = lodash.union(includeMethods, methodNames);
includeMethods = _.union(includeMethods, methodNames);
}
else {
// include only the `methodNames`
@@ -855,10 +844,10 @@
return true;
});
/*--------------------------------------------------------------------------*/
/*------------------------------------------------------------------------*/
// add category methods
argv.some(function(value) {
options.some(function(value) {
var categories = value.match(/^category=(.*)$/);
if (!categories) {
return false;
@@ -872,11 +861,11 @@
if (filterType == 'exclude') {
// remove excluded methods from `categoryMethods`
includeMethods = lodash.without.apply(lodash, [categoryMethods].concat(excludeMethods));
includeMethods = _.without.apply(_, [categoryMethods].concat(excludeMethods));
}
else if (filterType) {
// merge `categoryMethods` into `includeMethods`
includeMethods = lodash.union(includeMethods, categoryMethods);
includeMethods = _.union(includeMethods, categoryMethods);
}
else {
// include only the `categoryMethods`
@@ -886,7 +875,7 @@
return true;
});
/*--------------------------------------------------------------------------*/
/*------------------------------------------------------------------------*/
// custom build
(function() {
@@ -907,8 +896,8 @@
includeMethods = getDependencies(includeMethods);
// remove methods that aren't named in `includeMethods`
lodash.each(allMethods, function(otherName) {
if (!lodash.contains(includeMethods, otherName)) {
_.each(allMethods, function(otherName) {
if (!_.contains(includeMethods, otherName)) {
source = removeFunction(source, otherName);
}
});
@@ -921,7 +910,7 @@
}
}());
/*--------------------------------------------------------------------------*/
/*------------------------------------------------------------------------*/
// simplify template snippets by removing unnecessary brackets
source = source.replace(
@@ -932,11 +921,11 @@
RegExp("{(\\\\n' *\\+\\s*.*?\\+\\n\\s*' *)}(?:\\\\n)?' *\\+", 'g'), "$1;\\n'+"
);
/*--------------------------------------------------------------------------*/
/*------------------------------------------------------------------------*/
// DRY out isType functions
(function() {
var iteratorName = lodash.find(['forEach', 'forOwn'], function(funcName) {
var iteratorName = _.find(['forEach', 'forOwn'], function(funcName) {
return !isRemoved(source, funcName);
});
@@ -948,7 +937,7 @@
objectSnippets = [];
// build replacement code
lodash.forOwn({
_.forOwn({
'Arguments': 'argsClass',
'Date': 'dateClass',
'Number': 'numberClass',
@@ -989,7 +978,7 @@
);
}());
/*--------------------------------------------------------------------------*/
/*------------------------------------------------------------------------*/
if (isLegacy) {
['isBindFast', 'nativeBind', 'nativeIsArray', 'nativeKeys'].forEach(function(varName) {
@@ -1037,7 +1026,7 @@
if (isMobile) {
// inline all functions defined with `createIterator`
lodash.functions(lodash).forEach(function(funcName) {
_.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');
@@ -1120,7 +1109,7 @@
}()));
}
/*--------------------------------------------------------------------------*/
/*------------------------------------------------------------------------*/
// modify/remove references to removed methods/variables
if (isRemoved(source, 'isArguments')) {
@@ -1209,15 +1198,33 @@
// begin the minification process
if (filterType || isBackbone || isLegacy || isMobile || isStrict || isUnderscore) {
writeFile(debugSource, 'lodash.custom.js');
callback(path.join(cwd, 'lodash.custom.js'), debugSource);
minify(source, 'lodash.custom.min', function(result) {
writeFile(result, 'lodash.custom.min.js');
minify(source, 'lodash.custom.min', function(source) {
if (isStrict) {
// inject "use strict" directive
source = source.replace(/^(\/\*![\s\S]+?\*\/\n;\(function[^)]+\){)([^'"])/, '$1"use strict";$2');
}
callback(path.join(cwd, 'lodash.custom.min.js'), postMinify(source));
});
}
else {
minify(source, 'lodash.min', function(result) {
writeFile(result, 'lodash.min.js');
minify(source, 'lodash.min', function(source) {
callback(path.join(cwd, 'lodash.min.js'), postMinify(source));
});
}
}
/*--------------------------------------------------------------------------*/
// expose `build`
if (module != require.main) {
module.exports = build;
}
else {
// or invoked directly
build(process.argv.slice(2), function(filepath, source) {
fs.writeFileSync(filepath, source);
});
}
}());