2.0.0

Back | Minified | Changelog | Cautionlog
2016-07-17

After a major hiatus, Sugar 2.0.0 is finally here! This version has been a labor of love. It boasts a completely new way of interacting with the library, massive new features, code modularization, tons of performance optimizations, and a brand new site to match.

Upgrading

To kick off the new features, beginning in v2.0.0 a companion upgrade script is available to make upgrading easier. This script will monitor your method calls and give information about breaking changes in real-time. See here for more.

Extending in 2.0.0

Although Sugar will continue to have the ability to modify native objects as before, v2.0.0 fundamentally changes this behavior to make it opt-in, and provides two new alternate ways of calling methods. Until now, Sugar was essentially off-limits to library or middleware developers, as extending global objects was something that could not be opted out of. Making this behavior opt-in means that it is no longer a deal breaker for writers of third-party code. Sugar will continue to endorse the safe extension of natives, but in a way that leaves it up to the end user as a choice.

Core Rewrite

To accomplish this change, Sugar's core has been fundamentally rewritten to expose a single global object. All interactions with the library now happen through this object. To begin, all methods are defined as static functions in namespaces equivalent to their native counterparts, i.e. Sugar.Array for Array methods. Instance methods always accept the instance object as their first argument, while static methods are called the same as before:

Sugar.Date.create();
Sugar.Array.unique([1,1,2]);
Sugar.Number.isOdd(3);

The previous behavior of extending natives is now controlled through a global method called extend. This method has a number of different options to allow fine grained control over what methods get extended. Additionally, each namespace has its own extend method as well, allowing them to be called individually:

// Extend all methods Sugar.extend();
// Extend Array only Sugar.Array.extend();
// Extend Array and Object Sugar.extend({ namespaces: [Array, Object] });
// Extend all except Array and Object Sugar.extend({ except: [Array, Object] });
// Extend Array#unique only Sugar.Array.extend({ methods: ['unique'] });

Chainables

The second new method of interacting with Sugar methods comes in the form of the "chainable" object. Chainables are constructors that have the exact same prototype methods as those mapped onto natives with extend, and so can call instance methods in the same way. Chainables are easy to remember, because they are the Sugar namespaces themselves. For example, Sugar.Array is both the namespace to call static array methods and also a chainable constructor:

var arr = new Sugar.Array([1,2,2]); arr.unique().raw;

When a chainable is created, it will wrap the native object passed to it as the first argument. This object can be accessed at any time as the property raw. All methods called on the chainable object will operate on this object and return a new chainable that wraps the result, allowing methods to be chained together. When the time comes to get the result, simply access it with the raw property:

// First, create the chainable object. var users = new Sugar.Array(data.users);
// All user profiles.
var profiles = users.map('profile');

// Sum of all the profile likes.
var sum = profiles.sum('likes');

// Return the value
sum.raw;
// Of course this can all be written on one line, too new Sugar.Array(data.users).map('profile').sum('likes').raw;

Chainables can even use standard Javascript operators, as their underlying value is exposed with valueOf. Be aware however that this will unwrap the chainable:

// Adding with + new Sugar.Number(5) + new Sugar.Number(5);
// Multiplying with * new Sugar.Number(5) * new Sugar.Number(5);
// Comparison operators work as well new Sugar.Number(5) > new Sugar.Number(4);

Chainables as Extended Objects

Previous versions of Sugar included a concept known as "extended objects". These were a way to circumvent the fact that Sugar does not extend Object.prototype by having an internal type that mapped Object instance methods to its own prototype instead. Chainables now replace these – in fact they are the same concept applied across all native classes. In addition to previous functionality, chainables also map native methods and are even more versatile. They include three new methods: get, set, and has. These methods by default only return the object's own properties, while excluding inherited properties. Combined with their new ability to handle deep property notation (more below), this makes the Object chainable perfect for working with complex data structures:

var obj = new Sugar.Object(user); obj.get('votes').raw;
var obj = new Sugar.Object(data); obj.get('users[1].profile.hobbies').raw;
var obj = new Sugar.Object(users); obj.map('votes').average().round().raw;
var obj = new Sugar.Object(Harry); obj.intersect(Mark).keys().raw;

Modularized Builds

Modules in previous versions of Sugar were entire blocks of code that were added or removed together, and only through the site's rather primitive build system. v2.0.0 features a full package dependency model that allows custom builds at the method level. This site will host a build system that makes it easy to create a bundle of any method or date locale available. Even better, npm packages are now fully modularized as well and can also be used to create custom builds with the help of browserify or similar build tools:

// npm packages can now require methods individually: var unique = require('sugar/array/unique'); var dateCreate = require('sugar/date/create'); var dateAdvance = require('sugar/date/advance');
// Packages can also require entire modules... var Sugar = require('sugar/array');
// ... which have the module's methods defined on them:
Sugar.Array.unique(arr);
Sugar.Array.flatten(arr);

Sugar as an Ecosystem

As part of Sugar's modularization, its core functionality has been pulled out into its own npm package. In addition to making the library more modular, the core also exposes an API that allows new methods to be defined. Defining a method will allow interaction in all three forms of use – as a static method on the global, an instance method on a chainable, or in the global namespace in extended mode. All other Sugar modules now go through this API. However, separating the core is even more exciting as it means that third-party developers can author plugins too. Custom Sugar builds can now be composed not only of the Sugar library itself, but other plugins as well. This means that specialized use cases can be delegated to plugins and no longer have to belong to the main library itself.

// The sugar-core package exposes a method defining API. var Sugar = require('sugar-core');
Sugar.String.defineInstance('hello', function() {
  return 'hello!';
});
// Once defined the method can now be called statically... Sugar.String.hello();
// ...as a chainable... var str = new Sugar.String(); str.hello().raw;
// ...or in extended mode. 'say'.hello();

Deep Property Notation

Sugar methods that accept a string as a reference to an object property can now refer to deep properties using the dot . or square bracket [] operators. This ability begins with the new Object.get and Object.set methods but also includes methods that allow mapping function shortcuts such as Array#map, Array#unique, Array#groupBy, Array#sortBy, and more. Square bracket syntax also allows for negative array indexes counted from the end of an array, and range syntax with .. to retrieve a subset of an array.

Object.get(user, 'currencies.usd.balance');
users.map('profile.hobbies');
users.map('profile.addresses[0].city');
users.map('profile.addresses[-1].city');
users.map('profile.addresses[0..1].city');
users.unique('id');
'Mr. {names[0].last}'.format(Harry);

Date strftime format

Date#format now allows strf tokens in addition to standard "ldml" tokens.

today.format('%Y-%m-%d');
today.format('{yyyy}-{MM}-{dd}');

Array Iteration from an Index

Previous versions of Sugar had the ability to iterate over an array starting from a specific index and, optionally, looping again from the start. However the implementation was scattered across Array#each, Array#findFrom, and usage was confusing. v2.0.0 cleans this up by providing all ES5 and ES6 methods with consistently named aliases ending in FromIndex. All methods are their "enhanced" versions, and so included mapping/matching shortcuts.

['skip', 'skip', 'hello!'].forEachFromIndex(2, log);
['Happy', 'Harry', 'Holly'].findFromIndex(1, /^H/);
['one', 'two', 'three'].mapFromIndex(1, true, 'length');

Performance

Although performance changes in 2.0.0 vary from method to method, overall this version has seen the highest attention to performance so far. Date parsing has seen especially major gains due to major refactoring, and a number of core methods were reworked as well, including string formatting that feeds String#format (previously String#assign) and Date#format.

Semver

As Sugar is now far more relevant to library and middleware developers than it was before, it will begin strictly following Semver. Previously, MINOR verisions would occasionally have breaking changes. Going forward, a roadmap will be made available on Github, and breaking changes will always prompt a MAJOR version increment.

New Methods

Renamed Methods

Removed Methods

Other Changes

Callbacks in object iteration methods like Object.forEach (previously Object.each), Object.filter, etc. are now value, key where they were previously key, value. This change helps keep Sugar in line with other libaries as well as keep parity with these methods on Arrays.

Bugfixes