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

385
build.js
View File

@@ -2,111 +2,19 @@
;(function() { ;(function() {
'use strict'; 'use strict';
/** The debug version of `source` */
var debugSource;
/** Load modules */ /** Load modules */
var fs = require('fs'), var fs = require('fs'),
path = require('path'), path = require('path'),
vm = require('vm'), vm = require('vm'),
minify = require(path.join(__dirname, 'build', 'minify')); minify = require(path.join(__dirname, 'build', 'minify')),
_ = require(path.join(__dirname, 'lodash'));
/** The arguments passed to build.js */
var argv = process.argv;
/** The current working directory */ /** The current working directory */
var cwd = process.cwd(); 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 */ /** Shortcut used to convert array-like objects to arrays */
var slice = [].slice; 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 */ /** Used to associate aliases with their real names */
var aliasToRealMap = { var aliasToRealMap = {
'all': 'every', 'all': 'every',
@@ -286,23 +194,6 @@
'zip': ['max', 'pluck'] '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` */ /** Used to inline `iteratorTemplate` */
var iteratorOptions = [ var iteratorOptions = [
'args', 'args',
@@ -329,9 +220,9 @@
/** Collections of method names */ /** Collections of method names */
var excludeMethods = [], var excludeMethods = [],
includeMethods = [], includeMethods = [],
allMethods = Object.keys(dependencyMap); allMethods = _.keys(dependencyMap);
var underscoreMethods = lodash.without.apply(lodash, [allMethods].concat([ var underscoreMethods = _.without.apply(_, [allMethods].concat([
'countBy', 'countBy',
'forIn', 'forIn',
'forOwn', 'forOwn',
@@ -346,27 +237,6 @@
'where' '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];
}, '');
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
/** /**
@@ -389,7 +259,7 @@
' lodash exclude=... Comma separated names of methods to exclude from 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', ' lodash include=... Comma separated names of methods to include in the build',
'', '',
' All arguments, except `exclude` with `include` & `legacy` with `csp` / `mobile`,', ' All arguments, except `exclude` with `include` & `legacy` with `csp`/`mobile`,',
' may be combined.', ' may be combined.',
'', '',
' Options:', ' Options:',
@@ -432,8 +302,8 @@
function getDependants(funcName) { function getDependants(funcName) {
// iterate over `dependencyMap`, adding the names of functions that // iterate over `dependencyMap`, adding the names of functions that
// have `funcName` as a dependency // have `funcName` as a dependency
return lodash.reduce(dependencyMap, function(result, dependencies, otherName) { return _.reduce(dependencyMap, function(result, dependencies, otherName) {
if (lodash.contains(dependencies, funcName)) { if (_.contains(dependencies, funcName)) {
result.push(otherName); result.push(otherName);
} }
return result; return result;
@@ -457,7 +327,7 @@
} }
// recursively accumulate the dependencies of the `funcName` function, and // recursively accumulate the dependencies of the `funcName` function, and
// the dependencies of its dependencies, and so on. // 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)); result.push.apply(result, getDependencies(otherName).concat(otherName));
return result; return result;
}, [])); }, []));
@@ -672,17 +542,6 @@
.replace(/ *\|\| *\(noNodeClass *&&[\s\S]+?\)\)\)/, ''); .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`. * Removes a given variable from `source`.
* *
@@ -764,26 +623,156 @@
} }
/** /**
* Writes `source` to a file with the given `filename` to the current * Post-process the minified source.
* working directory.
* *
* @private * @private
* @param {String} source The source to write. * @param {String} source The minified source to process.
* @param {String} filename The name of the file. * @returns {String} The modified source.
*/ */
function writeFile(source, filename) { function postMinify(source) {
// correct overly aggressive Closure Compiler minification // correct overly aggressive Closure Compiler minification
source = source.replace(/prototype\s*=\s*{\s*valueOf\s*:\s*1\s*}/, 'prototype={valueOf:1,y:1}'); return 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);
} }
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
/**
* 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 // report invalid arguments
if (invalidArgs.length) { if (invalidArgs.length) {
console.log( console.log(
@@ -792,26 +781,26 @@
' passed: ' + invalidArgs.join(', ') ' passed: ' + invalidArgs.join(', ')
); );
displayHelp(); displayHelp();
process.exit(); return;
} }
// display help message // display help message
if (lodash.find(argv, function(arg) { if (_.find(options, function(arg) {
return /^(?:-h|--help)$/.test(arg); return /^(?:-h|--help)$/.test(arg);
})) { })) {
displayHelp(); displayHelp();
process.exit(); return;
} }
// display `lodash.VERSION` // display `lodash.VERSION`
if (lodash.find(argv, function(arg) { if (_.find(options, function(arg) {
return /^(?:-V|--version)$/.test(arg); return /^(?:-V|--version)$/.test(arg);
})) { })) {
console.log(lodash.VERSION); console.log(_.VERSION);
process.exit(); return;
} }
/*--------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
// don't expose `_.forIn` or `_.forOwn` if `isUnderscore` is `true` unless // don't expose `_.forIn` or `_.forOwn` if `isUnderscore` is `true` unless
// requested by `include` // requested by `include`
@@ -841,11 +830,11 @@
if (filterType == 'exclude') { if (filterType == 'exclude') {
// remove excluded methods from `methodNames` // remove excluded methods from `methodNames`
includeMethods = lodash.without.apply(lodash, [methodNames].concat(excludeMethods)); includeMethods = _.without.apply(_, [methodNames].concat(excludeMethods));
} }
else if (filterType) { else if (filterType) {
// merge `methodNames` into `includeMethods` // merge `methodNames` into `includeMethods`
includeMethods = lodash.union(includeMethods, methodNames); includeMethods = _.union(includeMethods, methodNames);
} }
else { else {
// include only the `methodNames` // include only the `methodNames`
@@ -855,10 +844,10 @@
return true; return true;
}); });
/*--------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
// add category methods // add category methods
argv.some(function(value) { options.some(function(value) {
var categories = value.match(/^category=(.*)$/); var categories = value.match(/^category=(.*)$/);
if (!categories) { if (!categories) {
return false; return false;
@@ -872,11 +861,11 @@
if (filterType == 'exclude') { if (filterType == 'exclude') {
// remove excluded methods from `categoryMethods` // remove excluded methods from `categoryMethods`
includeMethods = lodash.without.apply(lodash, [categoryMethods].concat(excludeMethods)); includeMethods = _.without.apply(_, [categoryMethods].concat(excludeMethods));
} }
else if (filterType) { else if (filterType) {
// merge `categoryMethods` into `includeMethods` // merge `categoryMethods` into `includeMethods`
includeMethods = lodash.union(includeMethods, categoryMethods); includeMethods = _.union(includeMethods, categoryMethods);
} }
else { else {
// include only the `categoryMethods` // include only the `categoryMethods`
@@ -886,7 +875,7 @@
return true; return true;
}); });
/*--------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
// custom build // custom build
(function() { (function() {
@@ -907,8 +896,8 @@
includeMethods = getDependencies(includeMethods); includeMethods = getDependencies(includeMethods);
// remove methods that aren't named in `includeMethods` // remove methods that aren't named in `includeMethods`
lodash.each(allMethods, function(otherName) { _.each(allMethods, function(otherName) {
if (!lodash.contains(includeMethods, otherName)) { if (!_.contains(includeMethods, otherName)) {
source = removeFunction(source, otherName); source = removeFunction(source, otherName);
} }
}); });
@@ -921,7 +910,7 @@
} }
}()); }());
/*--------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
// simplify template snippets by removing unnecessary brackets // simplify template snippets by removing unnecessary brackets
source = source.replace( source = source.replace(
@@ -932,11 +921,11 @@
RegExp("{(\\\\n' *\\+\\s*.*?\\+\\n\\s*' *)}(?:\\\\n)?' *\\+", 'g'), "$1;\\n'+" RegExp("{(\\\\n' *\\+\\s*.*?\\+\\n\\s*' *)}(?:\\\\n)?' *\\+", 'g'), "$1;\\n'+"
); );
/*--------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
// DRY out isType functions // DRY out isType functions
(function() { (function() {
var iteratorName = lodash.find(['forEach', 'forOwn'], function(funcName) { var iteratorName = _.find(['forEach', 'forOwn'], function(funcName) {
return !isRemoved(source, funcName); return !isRemoved(source, funcName);
}); });
@@ -948,7 +937,7 @@
objectSnippets = []; objectSnippets = [];
// build replacement code // build replacement code
lodash.forOwn({ _.forOwn({
'Arguments': 'argsClass', 'Arguments': 'argsClass',
'Date': 'dateClass', 'Date': 'dateClass',
'Number': 'numberClass', 'Number': 'numberClass',
@@ -989,7 +978,7 @@
); );
}()); }());
/*--------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
if (isLegacy) { if (isLegacy) {
['isBindFast', 'nativeBind', 'nativeIsArray', 'nativeKeys'].forEach(function(varName) { ['isBindFast', 'nativeBind', 'nativeIsArray', 'nativeKeys'].forEach(function(varName) {
@@ -1037,7 +1026,7 @@
if (isMobile) { if (isMobile) {
// inline all functions defined with `createIterator` // 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` // match `funcName` with pseudo private `_` prefixes removed to allow matching `shimKeys`
var reFunc = RegExp('(\\bvar ' + funcName.replace(/^_/, '') + ' *= *)createIterator\\(((?:{|[a-zA-Z])[\\s\\S]+?)\\);\\n'); var reFunc = RegExp('(\\bvar ' + funcName.replace(/^_/, '') + ' *= *)createIterator\\(((?:{|[a-zA-Z])[\\s\\S]+?)\\);\\n');
@@ -1120,7 +1109,7 @@
}())); }()));
} }
/*--------------------------------------------------------------------------*/ /*------------------------------------------------------------------------*/
// modify/remove references to removed methods/variables // modify/remove references to removed methods/variables
if (isRemoved(source, 'isArguments')) { if (isRemoved(source, 'isArguments')) {
@@ -1209,15 +1198,33 @@
// begin the minification process // begin the minification process
if (filterType || isBackbone || isLegacy || isMobile || isStrict || isUnderscore) { 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) { minify(source, 'lodash.custom.min', function(source) {
writeFile(result, 'lodash.custom.min.js'); 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 { else {
minify(source, 'lodash.min', function(result) { minify(source, 'lodash.min', function(source) {
writeFile(result, 'lodash.min.js'); 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);
}); });
} }
}()); }());