diff --git a/Makefile b/Makefile deleted file mode 100644 index e965bd291..000000000 --- a/Makefile +++ /dev/null @@ -1,39 +0,0 @@ -BUILD_DIR = ${CURDIR}/build -DIST_DIR = ${CURDIR}/dist -VENDOR_DIR = ${CURDIR}/vendor - -JS_ENGINE ?= `which node nodejs 2>/dev/null` - -LODASH_TMP = ${BUILD_DIR}/lodash.tmp.js -LODASH_COMPILER = ${DIST_DIR}/lodash.compiler.js -LODASH_UGLIFY = ${DIST_DIR}/lodash.uglify.js - -UGLIFY = ${VENDOR_DIR}/uglifyjs/bin/uglifyjs -CLOSURE_COMPILER = java -jar ${VENDOR_DIR}/closure-compiler/compiler.jar - -PRE_COMPILER = ${JS_ENGINE} ${BUILD_DIR}/pre-compile.js -POST_COMPILER = ${JS_ENGINE} ${BUILD_DIR}/post-compile.js - -core: - mkdir -p ${DIST_DIR} - cp ${CURDIR}/lodash.js ${LODASH_TMP} - ${PRE_COMPILER} ${LODASH_TMP} - - ${CLOSURE_COMPILER} \ - --compilation_level=ADVANCED_OPTIMIZATIONS \ - --language_in=ECMASCRIPT5_STRICT \ - --warning_level=QUIET \ - --js ${LODASH_TMP} \ - --js_output_file ${LODASH_COMPILER} - ${POST_COMPILER} ${LODASH_COMPILER} - gzip -9f -c ${LODASH_COMPILER} > ${LODASH_COMPILER}.gz - - ${UGLIFY} \ - --unsafe \ - --max-line-len 500 \ - -o ${LODASH_UGLIFY} \ - ${LODASH_TMP} - ${POST_COMPILER} ${LODASH_UGLIFY} - gzip -9f -c ${LODASH_UGLIFY} > ${LODASH_UGLIFY}.gz - - rm -f ${LODASH_TMP} diff --git a/build.js b/build.js new file mode 100755 index 000000000..df17919d1 --- /dev/null +++ b/build.js @@ -0,0 +1,348 @@ +#!/usr/bin/env node + +;(function () { + /* Load the Node file system, path, and child process modules. */ + var fs = require("fs"), path = require("path"), spawn = require("child_process").spawn, + + /* Load the UglifyJS compressor. */ + uglifyJS = require(path.join(__dirname, "vendor", "uglifyjs", "uglify-js")), + + /* The distribution directory. */ + distribution = path.join(__dirname, "dist"), + + /* The pre-processed Lodash source. */ + source = preprocess(fs.readFileSync(path.join(__dirname, "lodash.js"), "utf8")); + + /* Create the destination directory if it doesn't exist. */ + if (!path.existsSync(distribution)) { + fs.mkdirSync(distribution); + } + + /* Compress and `gzip` Lodash using the Closure Compiler. */ + compile(source, function (exception, results) { + if (exception) { + throw exception; + } + // Post-process the minified source. + var source = postprocess(results); + // Save the final minified version. + fs.writeFileSync(path.join(distribution, "lodash.compiler.js"), source); + gzip(source, function (exception, results) { + if (exception) { + throw exception; + } + // Save the `gzip`-ed version. The explicit `binary` encoding is + // necessary to ensure that the stream is written correctly. + fs.writeFileSync(path.join(distribution, "lodash.compiler.js.gz"), results, "binary"); + }); + }); + + /* Compress and `gzip` Lodash using UglifyJS. */ + uglify(source, function (results) { + var source = postprocess(results); + fs.writeFileSync(path.join(distribution, "lodash.uglify.js"), source); + gzip(source, function (exception, results) { + if (exception) { + throw exception; + } + fs.writeFileSync(path.join(distribution, "lodash.uglify.js.gz"), results, "binary"); + }); + }); + + /* Compresses a `source` string using UglifyJS. Yields the result to a + * `callback` function. This function is synchronous; the `callback` is used + * for symmetry. + */ + function uglify(source, callback) { + var results = uglifyJS.uglify.gen_code( + // Enable unsafe transformations. + uglifyJS.uglify.ast_squeeze_more( + uglifyJS.uglify.ast_squeeze( + // Munge variable and function names, excluding the special `define` + // function exposed by asynchronous module loaders. + uglifyJS.uglify.ast_mangle(uglifyJS.parser.parse(source), { + "except": ["define"] + } + ))), { + "ascii_only": true + }); + callback(uglifyJS.uglify.split_lines(results, 500)); + } + + /* Compresses a `source` string using the Closure Compiler. Yields the + * minified result to a `callback` function. + */ + function compile(source, callback) { + var compiler = spawn("java", [ + // Load the Closure Compiler and set the compression options. + "-jar", path.join(__dirname, "vendor", "closure-compiler", "compiler.jar"), + "--compilation_level=ADVANCED_OPTIMIZATIONS", + "--language_in=ECMASCRIPT5_STRICT", + "--warning_level=QUIET" + ]), stdout = "", stderr = ""; + // Explicitly set the encoding of the output and error streams. + compiler.stdout.setEncoding("utf8"); + compiler.stderr.setEncoding("utf8"); + compiler.stdout.on("data", function (data) { + stdout += data; + }); + compiler.stderr.on("data", function (data) { + stderr += data; + }); + compiler.on("exit", function (status) { + var exception = null; + if (status) { + exception = new Error(stderr); + exception.status = status; + } + callback(exception, stdout); + }); + compiler.stdin.end(source); + } + + /* Compresses a `source` string using the Unix `gzip` commands. Yields the + * result, and any exceptions encountered, to a `callback` function. + */ + function gzip(source, callback) { + var compressor = spawn("gzip", ["-9f", "-c"]), stdout = "", stderr = ""; + compressor.stdout.setEncoding("binary"); + compressor.stderr.setEncoding("utf8"); + compressor.stdout.on("data", function (data) { + stdout += data; + }); + compressor.stderr.on("data", function (data) { + stderr += data; + }); + compressor.on("exit", function (status) { + var exception = null; + if (status) { + exception = new Error(stderr); + exception.status = status; + } + callback(exception, stdout); + }); + // Proxy the source string to the `gzip` executable. + compressor.stdin.end(source); + } + + /* Post-processes a compressed `source` string. */ + function postprocess(source) { + /** The minimal license/copyright header */ + var license = + '/*!\n' + + ' Lo-Dash @VERSION github.com/bestiejs/lodash/blob/master/LICENSE.txt\n' + + ' Underscore.js 1.3.3 github.com/documentcloud/underscore/blob/master/LICENSE\n' + + '*/'; + + /*--------------------------------------------------------------------------*/ + + // set the version + license = license.replace('@VERSION', (/VERSION:([\'"])(.*?)\1/).exec(source).pop()); + + // move vars exposed by Closure Compiler into the IIFE + source = source.replace(/^([^(\n]+)\s*(\(function[^)]+\){)/, '$2$1'); + + // use double quotes consistently + source = source.replace(/'use strict'/, '"use strict"'); + + // add license + return license + '\n;' + source; + } + + /* Pre-processes an uncompressed `source` string. */ + function preprocess(source) { + /** Used to minify variables embedded in compiled strings */ + var compiledVars = [ + 'accumulator', + 'array', + 'arrayClass', + 'bind', + 'callback', + 'className', + 'collection', + 'computed', + 'concat', + 'current', + 'false', + 'funcClass', + 'hasOwnProperty', + 'identity', + 'index', + 'indexOf', + 'Infinity', + 'initial', + 'isArray', + 'isEmpty', + 'length', + 'object', + 'Math', + 'property', + 'result', + 'slice', + 'source', + 'stringClass', + 'target', + 'thisArg', + 'toString', + 'true', + 'undefined', + 'value', + 'values' + ]; + + /** Used to minify string values embedded in compiled strings */ + var compiledValues = [ + 'arrays', + 'objects' + ]; + + /** Used to minify `iterationFactory` option properties */ + var iterationFactoryOptions = [ + 'afterLoop', + 'args', + 'array', + 'beforeLoop', + 'bottom', + 'exits', + 'inLoop', + 'init', + 'iterate', + 'loopExp', + 'object', + 'returns', + 'top', + 'useHas' + ]; + + /** Used to minify variables and string values to a single character */ + var minNames = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); + + /** Used protect the specified properties from getting minified */ + var propWhitelist = [ + '_', + 'amd', + 'chain', + 'clearInterval', + 'criteria', + 'escape', + 'evaluate', + 'interpolate', + 'isEqual', + 'isFinite', + 'lodash', + 'setTimeout', + 'templateSettings', + 'toArray', + 'value', + 'variable' + ]; + + /*--------------------------------------------------------------------------*/ + + /** + * Remove copyright to add later in post-compile.js + */ + source = source.replace(/\/\*![\s\S]+?\*\//, ''); + + /** + * Correct JSDoc tags for Closure Compiler. + */ + source = source.replace(/@(?:alias|category)[^\n]*/g, ''); + + /** + * 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('\\.(' + iterationFactoryOptions.concat(propWhitelist).join('|') + ')\\b', 'g'), "['$1']"); + + /** + * Minify `sortBy` and `template` methods. + */ + ['sortBy', 'template'].forEach(function(methodName) { + var properties = ['criteria', 'value'], + snippet = source.match(RegExp('(\\n\\s*)function ' + methodName + '[\\s\\S]+?\\1}'))[0], + result = snippet; + + // minify property strings + properties.forEach(function(property, index) { + result = result.replace(RegExp("'" + property + "'", 'g'), "'" + minNames[index] + "'"); + }); + + // remove escaped newlines in strings + result = result.replace(/\\n/g, ''); + + // replace with modified snippet + source = source.replace(snippet, result); + }); + + /*--------------------------------------------------------------------------*/ + + /** + * Minify all `iterationFactory` related snippets. + */ + source.match( + RegExp([ + // match variables storing `iterationFactory` options + 'var [a-zA-Z]+FactoryOptions\\s*=\\s*\\{[\\s\\S]+?};\\n', + // match the the `iterationFactory` function + '(\\n\\s*)function iterationFactory[\\s\\S]+?\\1}', + // match methods created by `iterationFactor` calls + 'iterationFactory\\((?:[\'{]|[a-zA-Z]+,)[\\s\\S]+?\\);\\n' + ].join('|'), 'g') + ) + .forEach(function(snippet, index) { + var result = snippet; + + // add `true` and `false` arguments to be minified + if (/function iterationFactory/.test(snippet)) { + result = result + .replace(/(Function\('[\s\S]+?)undefined/, '$1true,false,undefined') + .replace(/\)\([^)]+/, '$&,true,false'); + + // replace with modified snippet early and clip snippet + source = source.replace(snippet, result); + snippet = result = result.replace(/\)\([\s\S]+$/, ''); + } + + // minify snippet variables/arguments + compiledVars.forEach(function(variable, index) { + result = result.replace(RegExp('([^.]\\b|\\\\n)' + variable + '\\b(?!\'\\s*[\\]:])', 'g'), '$1' + minNames[index]); + // correct `typeof x == 'object'` + if (variable == 'object') { + result = result.replace(RegExp("(typeof [^']+')" + minNames[index] + "'", 'g'), "$1object'"); + } + // correct boolean literals + if (variable == 'true' || variable == 'false') { + result = result + .replace(RegExp(':\\s*' + minNames[index] + '\\s*,', 'g'), ':' + variable + ',') + .replace(RegExp('\\s*' + minNames[index] + '\\s*;', 'g'), variable + ';'); + } + }); + + // minify snippet values + compiledValues.forEach(function(value, index) { + result = result.replace(RegExp("'" + value + "'", 'g'), "'" + minNames[index] + "'"); + }); + + // minify iterationFactory option property strings + iterationFactoryOptions.forEach(function(property, index) { + if (property == 'array' || property == 'object') { + result = result.replace(RegExp("'" + property + "'(\\s*[\\]:])", 'g'), "'" + minNames[index] + "'$1"); + } else { + result = result.replace(RegExp("'" + property + "'", 'g'), "'" + minNames[index] + "'"); + } + }); + + // remove escaped newlines in strings + result = result.replace(/\\n/g, ''); + + // replace with modified snippet + source = source.replace(snippet, result); + }); + + /*--------------------------------------------------------------------------*/ + + // write to the same file + return source; + } +}()); \ No newline at end of file diff --git a/build/post-compile.js b/build/post-compile.js deleted file mode 100644 index e781fb7a2..000000000 --- a/build/post-compile.js +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env node -;(function() { - 'use strict'; - - /** The filesystem module */ - var fs = require('fs'); - - /** The JavaScript source */ - var src = fs.readFileSync(process.argv[2], 'utf8'); - - /** The minimal license/copyright header */ - var license = - '/*!\n' + - ' Lo-Dash @VERSION github.com/bestiejs/lodash/blob/master/LICENSE.txt\n' + - ' Underscore.js 1.3.3 github.com/documentcloud/underscore/blob/master/LICENSE\n' + - '*/'; - - /*--------------------------------------------------------------------------*/ - - // set the version - license = license.replace('@VERSION', /VERSION:([\'"])(.*?)\1/.exec(src).pop()); - - // move vars exposed by Closure Compiler into the IIFE - src = src.replace(/^([^(\n]+)\s*(\(function[^)]+\){)/, '$2$1'); - - // use double quotes consistently - src = src.replace(/'use strict'/, '"use strict"'); - - // add license - src = license + '\n;' + src; - - // write to the same file - fs.writeFileSync(process.argv[2], src, 'utf8'); -}()); diff --git a/build/pre-compile.js b/build/pre-compile.js deleted file mode 100644 index 62c021878..000000000 --- a/build/pre-compile.js +++ /dev/null @@ -1,204 +0,0 @@ -#!/usr/bin/env node -;(function() { - 'use strict'; - - /** The filesystem module */ - var fs = require('fs'); - - /** The JavaScript source */ - var src = fs.readFileSync(process.argv[2], 'utf8'); - - /** Used to minify variables embedded in compiled strings */ - var compiledVars = [ - 'accumulator', - 'array', - 'arrayClass', - 'bind', - 'callback', - 'className', - 'collection', - 'computed', - 'concat', - 'current', - 'false', - 'funcClass', - 'hasOwnProperty', - 'identity', - 'index', - 'indexOf', - 'Infinity', - 'initial', - 'isArray', - 'isEmpty', - 'length', - 'object', - 'Math', - 'property', - 'result', - 'slice', - 'source', - 'stringClass', - 'target', - 'thisArg', - 'toString', - 'true', - 'undefined', - 'value', - 'values' - ]; - - /** Used to minify string values embedded in compiled strings */ - var compiledValues = [ - 'arrays', - 'objects' - ]; - - /** Used to minify `iterationFactory` option properties */ - var iterationFactoryOptions = [ - 'afterLoop', - 'args', - 'array', - 'beforeLoop', - 'bottom', - 'exits', - 'inLoop', - 'init', - 'iterate', - 'loopExp', - 'object', - 'returns', - 'top', - 'useHas' - ]; - - /** Used to minify variables and string values to a single character */ - var minNames = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); - - /** Used protect the specified properties from getting minified */ - var propWhitelist = [ - '_', - 'amd', - 'chain', - 'clearInterval', - 'criteria', - 'escape', - 'evaluate', - 'interpolate', - 'isEqual', - 'isFinite', - 'lodash', - 'setTimeout', - 'templateSettings', - 'toArray', - 'value', - 'variable' - ]; - - /*--------------------------------------------------------------------------*/ - - /** - * Remove copyright to add later in post-compile.js - */ - src = src.replace(/\/\*![\s\S]+?\*\//, ''); - - /** - * Correct JSDoc tags for Closure Compiler. - */ - src = src.replace(/@(?:alias|category)[^\n]*/g, ''); - - /** - * Add brackets to whitelisted properties so Closure Compiler won't mung them. - * http://code.google.com/closure/compiler/docs/api-tutorial3.html#export - */ - src = src.replace(RegExp('\\.(' + iterationFactoryOptions.concat(propWhitelist).join('|') + ')\\b', 'g'), "['$1']"); - - /** - * Minify `sortBy` and `template` methods. - */ - ['sortBy', 'template'].forEach(function(methodName) { - var properties = ['criteria', 'value'], - snippet = src.match(RegExp('(\\n\\s*)function ' + methodName + '[\\s\\S]+?\\1}'))[0], - result = snippet; - - // minify property strings - properties.forEach(function(property, index) { - result = result.replace(RegExp("'" + property + "'", 'g'), "'" + minNames[index] + "'"); - }); - - // remove escaped newlines in strings - result = result.replace(/\\n/g, ''); - - // replace with modified snippet - src = src.replace(snippet, result); - }); - - /*--------------------------------------------------------------------------*/ - - /** - * Minify all `iterationFactory` related snippets. - */ - src.match( - RegExp([ - // match variables storing `iterationFactory` options - 'var [a-zA-Z]+FactoryOptions\\s*=\\s*\\{[\\s\\S]+?};\\n', - // match the the `iterationFactory` function - '(\\n\\s*)function iterationFactory[\\s\\S]+?\\1}', - // match methods created by `iterationFactor` calls - 'iterationFactory\\((?:[\'{]|[a-zA-Z]+,)[\\s\\S]+?\\);\\n' - ].join('|'), 'g') - ) - .forEach(function(snippet, index) { - var result = snippet; - - // add `true` and `false` arguments to be minified - if (/function iterationFactory/.test(snippet)) { - result = result - .replace(/(Function\('[\s\S]+?)undefined/, '$1true,false,undefined') - .replace(/\)\([^)]+/, '$&,true,false'); - - // replace with modified snippet early and clip snippet - src = src.replace(snippet, result); - snippet = result = result.replace(/\)\([\s\S]+$/, ''); - } - - // minify snippet variables/arguments - compiledVars.forEach(function(variable, index) { - result = result.replace(RegExp('([^.]\\b|\\\\n)' + variable + '\\b(?!\'\\s*[\\]:])', 'g'), '$1' + minNames[index]); - // correct `typeof x == 'object'` - if (variable == 'object') { - result = result.replace(RegExp("(typeof [^']+')" + minNames[index] + "'", 'g'), "$1object'"); - } - // correct boolean literals - if (variable == 'true' || variable == 'false') { - result = result - .replace(RegExp(':\\s*' + minNames[index] + '\\s*,', 'g'), ':' + variable + ',') - .replace(RegExp('\\s*' + minNames[index] + '\\s*;', 'g'), variable + ';'); - } - }); - - // minify snippet values - compiledValues.forEach(function(value, index) { - result = result.replace(RegExp("'" + value + "'", 'g'), "'" + minNames[index] + "'"); - }); - - // minify iterationFactory option property strings - iterationFactoryOptions.forEach(function(property, index) { - if (property == 'array' || property == 'object') { - result = result.replace(RegExp("'" + property + "'(\\s*[\\]:])", 'g'), "'" + minNames[index] + "'$1"); - } else { - result = result.replace(RegExp("'" + property + "'", 'g'), "'" + minNames[index] + "'"); - } - }); - - // remove escaped newlines in strings - result = result.replace(/\\n/g, ''); - - // replace with modified snippet - src = src.replace(snippet, result); - }); - - /*--------------------------------------------------------------------------*/ - - // write to the same file - fs.writeFileSync(process.argv[2], src, 'utf8'); -}());