From b5449ca89a6defbe78196b2686a2131bf18eb751 Mon Sep 17 00:00:00 2001 From: Mike Frawley Date: Tue, 23 Feb 2010 16:40:50 -0600 Subject: [PATCH 1/7] Add rake task to build with closure advanced optimizations. You would use this to compile your code _with_ underscore, to remove all those dead underscore functions you don't use. Slight tweak to code to `root._` code to get working, and must remove anonymous wrapper function for current closure compiler to remove dead code. --- Rakefile | 22 ++++++++++++++++++++-- underscore.js | 5 +++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Rakefile b/Rakefile index 6af14bb6d..2c085058a 100644 --- a/Rakefile +++ b/Rakefile @@ -1,9 +1,27 @@ require 'rubygems' require 'closure-compiler' - + desc "Use the Closure Compiler to compress Underscore.js" task :build do js = File.open('underscore.js', 'r') min = Closure::Compiler.new.compile(js) File.open('underscore-min.js', 'w') {|f| f.write(min) } -end \ No newline at end of file +end + +task :build_advanced do + js = File.read('underscore.js') + # remove wrapping anonymous function as this messes with closure compiler + # see + # http://groups.google.com/group/closure-compiler-discuss/browse_thread/thread/b59b54c1a0073aa5 + js.sub!('(function() {', '').chomp!("})();\n") + compiler = Closure::Compiler.new \ + :compilation_level => 'ADVANCED_OPTIMIZATIONS', + :formatting => 'PRETTY_PRINT' + min = compiler.compile(js) + File.open('underscore-min2.js', 'w') {|f| f.write(min) } + # + original_size = js.length + minimized_size = min.length + puts original_size, minimized_size +end + diff --git a/underscore.js b/underscore.js index 5e0297322..696b63f4e 100644 --- a/underscore.js +++ b/underscore.js @@ -25,8 +25,9 @@ var breaker = typeof StopIteration !== 'undefined' ? StopIteration : '__break__'; // Create a safe reference to the Underscore object for reference below. - var _ = root._ = function(obj) { return new wrapper(obj); }; - + var _ = function(obj) { return new wrapper(obj); }; + root._ = _; + // Export the Underscore object for CommonJS. if (typeof exports !== 'undefined') exports._ = _; From 164e19c121b617b53734d313e44b9f386b994100 Mon Sep 17 00:00:00 2001 From: Mike Frawley Date: Tue, 23 Feb 2010 17:13:18 -0600 Subject: [PATCH 2/7] use `var native_reduceRight` etc. variables instead of `Native = { reduceRight: ... }` This way we only get the native versions of functions we use. Will make testing non-native versions as we can't just swap do `_.Native = {}`, but this wouldn't have worked for `isArray` and `keys` anyways, as these are looked up on initialization, so I think the solution is to have an init() method on underscore where we will re-initialize functions and try and use native versions then --- underscore.js | 59 ++++++++++++++++++++++++--------------------------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/underscore.js b/underscore.js index 696b63f4e..609e5b567 100644 --- a/underscore.js +++ b/underscore.js @@ -32,31 +32,28 @@ if (typeof exports !== 'undefined') exports._ = _; // Save bytes in the minified (but not gzipped) version: - var ArrayPrototype = Array.prototype; + var Array_Prototype = Array.prototype; // Create quick reference variables for speed access to core prototypes. - var slice = ArrayPrototype.slice, - unshift = ArrayPrototype.unshift, + var slice = Array_Prototype.slice, + unshift = Array_Prototype.unshift, toString = Object.prototype.toString, hasOwnProperty = Object.prototype.hasOwnProperty, propertyIsEnumerable = Object.prototype.propertyIsEnumerable; // All native implementations we hope to use are declared here. - // 'native' is on the long list of reserved words. - // Ok to use as a property name, though jsLint doesn't agree. - var Native = _['native'] = { - forEach: ArrayPrototype.forEach, - map: ArrayPrototype.map, - reduce: ArrayPrototype.reduce, - reduceRight: ArrayPrototype.reduceRight, - filter: ArrayPrototype.filter, - every: ArrayPrototype.every, - some: ArrayPrototype.some, - indexOf: ArrayPrototype.indexOf, - lastIndexOf: ArrayPrototype.lastIndexOf, - isArray: Array.isArray, - keys: Object.keys - }; + var + native_forEach = Array_Prototype.forEach, + native_map = Array_Prototype.map, + native_reduce = Array_Prototype.reduce, + native_reduceRight = Array_Prototype.reduceRight, + native_filter = Array_Prototype.filter, + native_every = Array_Prototype.every, + native_some = Array_Prototype.some, + native_indexOf = Array_Prototype.indexOf, + native_lastIndexOf = Array_Prototype.lastIndexOf, + native_isArray = Array['isArray'], + native_keys = Object['keys']; // Current version. _.VERSION = '0.5.8'; @@ -70,7 +67,7 @@ _.forEach = function(obj, iterator, context) { var index = 0; try { - if (obj.forEach === Native.forEach) { + if (obj.forEach === native_forEach) { obj.forEach(iterator, context); } else if (_.isNumber(obj.length)) { for (var i=0, l=obj.length; i Date: Tue, 23 Feb 2010 17:26:32 -0600 Subject: [PATCH 3/7] Revert "Create #alias method, callable on any object, _ by default." This reverts commit c43de549bacd3f6def734317656ddae7fa52c4cf. Conflicts: test/objects.js --- test/objects.js | 19 ++----------------- underscore.js | 43 +++++++++---------------------------------- 2 files changed, 11 insertions(+), 51 deletions(-) diff --git a/test/objects.js b/test/objects.js index 71e46bdb6..d93d72616 100644 --- a/test/objects.js +++ b/test/objects.js @@ -11,7 +11,7 @@ $(document).ready(function() { }); test("objects: functions", function() { - var expected = ["alias", "all", "any", "bind", "bindAll", "breakLoop", "buildLookup", "clone", "compact", + var expected = ["all", "any", "bind", "bindAll", "breakLoop", "buildLookup", "clone", "compact", "compose","defer", "delay", "detect", "each", "every", "extend", "filter", "first", "flatten", "foldl", "foldr", "forEach", "functions", "head", "identity", "include", "indexOf", "inject", "intersect", "invoke", "isArguments", "isArray", "isDate", "isElement", "isEmpty", "isEqual", @@ -19,7 +19,7 @@ $(document).ready(function() { "methods", "min", "noConflict", "pluck", "range", "reduce", "reduceRight", "reject", "rest", "select", "size", "some", "sortBy", "sortedIndex", "tail", "tap", "template", "times", "toArray", "uniq", "uniqueId", "values", "without", "wrap", "zip"]; - ok(_(expected).isEqual(_.methods(_)), 'provides a sorted list of functions'); + same(expected, _.methods(_), 'provides a sorted list of functions'); var obj = {a : 'dash', b : _.map, c : (/yo/), d : _.reduce}; ok(_.isEqual(['b', 'd'], _.functions(obj)), 'can grab the function names of any passed-in object'); }); @@ -185,19 +185,4 @@ $(document).ready(function() { value(); ok(returned == 6 && intercepted == 6, 'can use tapped objects in a chain'); }); - - - test("objects: alias", function() { - _.alias('isEqual', 'isTheSame'); - ok(_.isTheSame(9, 9), 'by default aliases methods on underscore'); - delete _.isTheSame; - // - var o = { hi: function () {return 9;} }; - _.alias(o, 'hi', 'ho'); - equals(o.ho(), 9, 'can add an alias on another object'); - _.alias(o, 'hi', 'there', 'sir'); - ok(o.hi==o.sir, 'can add multiple aliases'); - }); - - }); diff --git a/underscore.js b/underscore.js index 609e5b567..8fae4566d 100644 --- a/underscore.js +++ b/underscore.js @@ -470,31 +470,6 @@ return obj; }; - // Alias a method on an object to another name(s). - // If the first argument is NOT an object, then the object is assumed to - // be underscore. - // The first string argument is the existing method name, any following - // strings will be aliased to that name. - // Returns the object for chainability. - // - // Examples: - // - // Alias isEquals to isSame and eql on underscore: - // _.alias('isEqual', 'isSame', 'eql'); - // - // Alias `toString` to `to_s` on a given object: - // _.alias({}, 'toString', 'to_s') - // - // Implementation: explicitly cast arguments to array as calling mutating - // array methods on arguments object has strange behavior (at least in FF 3.6) - _.alias = function () { - var args = _.toArray(arguments), - obj = (typeof args[0] === 'string')? _ : args.shift(), - fn = obj[args.shift()]; - each(args, function (alias) { obj[alias] = fn; }); - return obj; - }; - // Perform a deep comparison to check if two objects are equal. _.isEqual = function(a, b) { // Check object identity. @@ -658,15 +633,15 @@ // ------------------------------- Aliases ---------------------------------- - _.alias('forEach', 'each'). - alias('reduce', 'foldl', 'inject'). - alias('reduceRight', 'foldr'). - alias('filter', 'select'). - alias('every', 'all'). - alias('some', 'any'). - alias('first', 'head'). - alias('rest', 'tail'). - alias('functions', 'methods'); + _.each = _.forEach; + _.foldl = _.inject = _.reduce; + _.foldr = _.reduceRight; + _.select = _.filter; + _.all = _.every; + _.any = _.some; + _.head = _.first; + _.tail = _.rest; + _.methods = _.functions; // ------------------------ Setup the OOP Wrapper: -------------------------- From 1cf73c5474c7ba6fc9ee99aa42d5116ea2527b49 Mon Sep 17 00:00:00 2001 From: Mike Frawley Date: Tue, 23 Feb 2010 17:34:34 -0600 Subject: [PATCH 4/7] Re-arrange baseline setup section. Put all variable declartions in a row, so they can all be chained together with commas by compiler. Move wrapper creation code down in OO section. Not necissary for `var wrapper` to be defined when _ constructor is defined. Preparing way to make OO wrapping optional.. --- underscore.js | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/underscore.js b/underscore.js index 8fae4566d..7e8b24865 100644 --- a/underscore.js +++ b/underscore.js @@ -7,7 +7,6 @@ // http://documentcloud.github.com/underscore (function() { - // ------------------------- Baseline setup --------------------------------- // Establish the root object, "window" in the browser, or "global" on the server. @@ -16,21 +15,9 @@ // Save the previous value of the "_" variable. var previousUnderscore = root._; - // If Underscore is called as a function, it returns a wrapped object that - // can be used OO-style. This wrapper holds altered versions of all the - // underscore functions. Wrapped objects may be chained. - var wrapper = function(obj) { this._wrapped = obj; }; - // Establish the object that gets thrown to break out of a loop iteration. var breaker = typeof StopIteration !== 'undefined' ? StopIteration : '__break__'; - - // Create a safe reference to the Underscore object for reference below. - var _ = function(obj) { return new wrapper(obj); }; - root._ = _; - // Export the Underscore object for CommonJS. - if (typeof exports !== 'undefined') exports._ = _; - // Save bytes in the minified (but not gzipped) version: var Array_Prototype = Array.prototype; @@ -52,12 +39,21 @@ native_some = Array_Prototype.some, native_indexOf = Array_Prototype.indexOf, native_lastIndexOf = Array_Prototype.lastIndexOf, - native_isArray = Array['isArray'], + native_isArray = Array['isArray'], // use [] notation since not in closure's externs native_keys = Object['keys']; + // Create a safe reference to the Underscore object for reference below. + var _ = function(obj) { return new wrapper(obj); }; + + // Export the Underscore object for CommonJS. + if (typeof exports !== 'undefined') exports._ = _; + + // Export underscore to global scope. + root._ = _; + // Current version. _.VERSION = '0.5.8'; - + // ------------------------ Collection Functions: --------------------------- // The cornerstone, an each implementation. @@ -92,8 +88,7 @@ return results; }; - // Reduce builds up a single result from a list of values. Also known as - // inject, or foldl. + // Reduce builds up a single result from a list of values, aka inject, or foldl. // Delegates to JavaScript 1.8's native reduce if available. _.reduce = function(obj, memo, iterator, context) { if (obj.reduce === native_reduce) return obj.reduce(_.bind(iterator, context), memo); @@ -645,6 +640,11 @@ // ------------------------ Setup the OOP Wrapper: -------------------------- + // If Underscore is called as a function, it returns a wrapped object that + // can be used OO-style. This wrapper holds altered versions of all the + // underscore functions. Wrapped objects may be chained. + var wrapper = function(obj) { this._wrapped = obj; }; + // Helper function to continue chaining intermediate results. var result = function(obj, chain) { return chain ? _(obj).chain() : obj; From f591f685466bc04d52b932b021e8d72b7225a6ea Mon Sep 17 00:00:00 2001 From: Mike Frawley Date: Tue, 23 Feb 2010 18:04:47 -0600 Subject: [PATCH 5/7] make _.initWrapper() that builds the wrapper prototye The intention is that underscore Consumers MUST call _.initWrapper() in their code to allow the OO style usage, or a runtime error is thrown. This would break backwards compatability, so for the moment I call initWrapper myself at the end of underscore. However, if I comment out my call to initWrapper and don't call it in user code, then the compiler can strip an empty underscore with no user code from 2137 -> 100 bytes. Making the user explicitly say they want OO wrapping is the only way to achieve this, otherwise the compiler will always keep the OO wrapper code around. --- underscore.js | 102 +++++++++++++++++++++++++++----------------------- 1 file changed, 56 insertions(+), 46 deletions(-) diff --git a/underscore.js b/underscore.js index 7e8b24865..53d70c503 100644 --- a/underscore.js +++ b/underscore.js @@ -43,7 +43,7 @@ native_keys = Object['keys']; // Create a safe reference to the Underscore object for reference below. - var _ = function(obj) { return new wrapper(obj); }; + var _ = function(obj) { return _.buildWrapper(obj) }; // Export the Underscore object for CommonJS. if (typeof exports !== 'undefined') exports._ = _; @@ -68,8 +68,11 @@ } else if (_.isNumber(obj.length)) { for (var i=0, l=obj.length; i Date: Tue, 23 Feb 2010 18:13:46 -0600 Subject: [PATCH 6/7] modify rakefile build_advanced to strip off my call to _.initWrapper() --- Rakefile | 2 +- underscore.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 2c085058a..1b5d91615 100644 --- a/Rakefile +++ b/Rakefile @@ -13,7 +13,7 @@ task :build_advanced do # remove wrapping anonymous function as this messes with closure compiler # see # http://groups.google.com/group/closure-compiler-discuss/browse_thread/thread/b59b54c1a0073aa5 - js.sub!('(function() {', '').chomp!("})();\n") + js.sub!('(function() {', '').chomp!("_.initWrapper();\n})();\n") compiler = Closure::Compiler.new \ :compilation_level => 'ADVANCED_OPTIMIZATIONS', :formatting => 'PRETTY_PRINT' diff --git a/underscore.js b/underscore.js index 53d70c503..6260b57b8 100644 --- a/underscore.js +++ b/underscore.js @@ -697,5 +697,6 @@ }; } + // For backwards compatability, init the OO wrapper _.initWrapper(); })(); From b8fd129130f0956b43d5336452b1a5f709685b55 Mon Sep 17 00:00:00 2001 From: Mike Frawley Date: Tue, 23 Feb 2010 18:20:42 -0600 Subject: [PATCH 7/7] only initWrapper() once. make wrapper constructor accessable as `_._wrapper`. Previously, there was no way to get at the OO wrapper object. So if a user added a custom method on to _, or defined a custom alias, it would not be reflected in the wrapper. Now it is at least possible, though there is no API to do it. I alluded to making one in my commit that added _.alias() (which I recently reverted, as it added weight to the stripped version), but honestly I think the wrapping behavior should go in to a seperate library, not because it is bad, but because it is an awesome pattern that I want to use on objects other than underscore. --- underscore.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/underscore.js b/underscore.js index 6260b57b8..d8c795e7e 100644 --- a/underscore.js +++ b/underscore.js @@ -645,11 +645,17 @@ _.buildWrapper = function () { throw "Call _.initWrapper() to enable OO wrapping" } _.initWrapper = function () { + if (_._wrapper) return; // Already initialized + // If Underscore is called as a function, it returns a wrapped object that // can be used OO-style. This wrapper holds altered versions of all the // underscore functions. Wrapped objects may be chained. var wrapper = function(obj) { this._wrapped = obj; }; + // Make wrapper object public so user code can extend it. + // Otherwise, there is no way to modify its prototype + _._wrapper = wrapper; + // Overwrite method called from _() _.buildWrapper = function (obj) { return new wrapper(obj) }; @@ -698,5 +704,6 @@ } // For backwards compatability, init the OO wrapper + // the advanced minifying rake task will strip this out _.initWrapper(); })();