Kit Cambridge

Programming and other observations.

Custom Builds in Lo-Dash 2.0

Lo-Dash Logo

Since its stable release nearly seven months ago, Lo-Dash has continuously emphasized the themes of consistency, customization, and performance. It has grown beyond its beginnings as a fork of the Underscore library into an extensible utility belt. Today, we’d like to cover some of the changes to custom builds in Lo-Dash 2.0.

Along with a variety of packaged builds, Lo-Dash includes a command-line tool that allows you to control every aspect of the library. You can mix and match individual functions to create a build that’s just right for you, or target common browser and server-side JavaScript implementations. As of version 2.0, builds are no longer restricted to monolithic files—with the new modularize option, you can split Lo-Dash into individual modules.

The Build Tool

The Node-based build tool makes it easy to tailor Lo-Dash to your needs. You can select from six presets—modern, mobile, csp, legacy, underscore, and backbone—or mix and match individual functions by category or method name. Additionally, you can customize the module format, immediately-invoked function expression wrapper, and generate source maps. Finally, if you’re using Grunt, you can automate builds with the official grunt-lodash plug-in.

In Lo-Dash 2.0, the builder prunes unused helpers, private variables, optimizations, and even entire code branches. This results in extremely compact builds when combined with the dead code removal features in UglifyJS and the Closure Compiler. The builder has also been moved to a dedicated lodash-cli package, reducing the size of the Lo-Dash repository for palatable consumption with package managers like Bower.

To get started, install the build tool from npm:

1
2
{sudo} npm i -g lodash-cli
lodash -h

Let’s take a closer look at some of the presets and options that you can set.

Underscore and Backbone Builds

The underscore and backbone builds are drop-in replacements, ideal for existing projects that currently depend on Underscore. They include performance improvements and limited consistency fixes; however, for maximum compatibility, some optimizations and fixes have been removed. If you’re migrating from Underscore to Lo-Dash, we recommend trying the modern build first, falling back to the underscore build only if you encounter issues. We don’t recommend these builds for new projects.

The underscore build removes AMD support, _.createCallback and the shorthand syntax, object iteration fixes, deep cloning, large array optimizations, source map support, and custom callbacks for _.isEqual, _.merge, and _.clone. It does not support implicit chaining; you must explicitly call _#chain() to enable method chaining. All new Lo-Dash methods—at, bindKey, cloneDeep, createCallback, curry, for{In, Own, InRight, OwnRight, EachRight}, find{Index, Key, Last, LastIndex, LastKey}, isPlainObject, merge, parseInt, partialRight, pull, remove, and transform—are excluded by default, but can be added using the plus option.

The backbone build is based on the underscore build, but includes only the following methods: bind, bindAll, chain, clone, contains, countBy, defaults, escape, every, extend, filter, find, first, forEach, groupBy, has, indexOf, initial, invert, invoke, isArray, isEmpty, isEqual, isFunction, isObject, isRegExp, isString, keys, last, lastIndexOf, map, max, min, mixin, omit, once, pairs, pick, reduce, reduceRight, reject, rest, result, shuffle, size, some, sortBy, sortedIndex, toArray uniqueId, value, values, and without. In practice, this build is not particularly useful, as you’re likely using more than just these methods in your Backbone project. Nonetheless, you may find it helpful if you’re not relying on Underscore at all in your domain logic.

Mobile and Modern Builds

Bottom Line: The modern build includes all the performance gains and extra features of Lo-Dash, without support for older environments. If you’re migrating from Underscore to Lo-Dash, please try the modern or compat build first. We strongly recommend that you use this build for all new projects.

Following jQuery’s lead, we’re encouraging the adoption of the modern build as the default. Although the compat and legacy builds provide consistent support for legacy environments, we want to avoid penalizing newer implementations with redundant backward-compatibility fixes.

This build excludes object iteration and array wrapper fixes. It assumes the existence of certain features standardized in the fifth edition of the ECMAScript specification, such as Object.getPrototypeOf and the "Arguments" [[Class]] name. The modern build also includes pre-compiled iteration methods, making it suitable for use in environments that enforce the content security policy. The csp build, previously recommended for this purpose, is now an alias of the modern build.

The mobile build is based on the modern build, but includes additional fixes for Safari < 5.1. These are necessary for compatibility with Mobile Safari on iOS 5.

A Note on Templates and the Content Security Policy

The template directive allows you to pre-compile Lo-Dash templates. This is necessary if your target environment—a Chrome extension or Firefox OS app, for instance—implements the content security policy.

You can perform compilation directly from the command line with the lodash template=/path/to/templates/*.jst directive. Alternatively, you can use fs.watch or integrate with the grunt-lodash plug-in to automatically recompile templates as you’re working on them .

Template pre-compilation is configurable. You can specify Node and AMD support with the exports and moduleId options, or provide custom delimiters and other template settings with the settings option.

Compatibility and Legacy Build

The compat build includes support for older engines, but optimizes for newer implementations where available. The legacy build, however, is the inverse of the modern build—it removes all optimizations for newer implementations, and specifically targets legacy engines. Unlike the modern build, which won’t work in older environments, both the compat and legacy builds are backward-compatible. compat is a “one size fits all” package; legacy is ideal if you’re conditionally serving Lo-Dash to browsers.

Under the hood, the legacy build does not test for Object.getPrototypeOf, Function#bind, Object.keys, or Array.isArray, even if they are supported and faster than the vanilla JavaScript equivalent. This build also assumes that the "Arguments" [[Class]] name is not present; consequently, isArguments uses a less stringent duck-typing check to detect arguments objects.

Note that the compat build is generated by default if the preset (modern, legacy, underscore, etc.) is omitted.

Modular Builds

The modularize directive instructs the build tool to create individual modules for each function, instead of aggregating them into a single file. This directive may be combined with other options and presets: for example, lodash modularize underscore include=cloneDeep exports=amd creates a modular Underscore build with deep merging support. To reduce clutter, internal helpers are separated into distinct files, and loaded by their dependents. Transitive dependencies are automatically resolved.

To help you get started, two packages containing the modern, compat, and underscore builds are available: lodash-amd for AMD loaders, and lodash-node for Node modules. The AMD builds have been tested with RequireJS, curl, and the Dojo AMD loader.

All modular functions may be loaded by function name, category, or build from these packages. Functions may be referenced directly in the form of build/category/function, or aliased in your AMD loader’s packages array:

1
2
3
4
5
// In Node.
var assert = require('assert'),
    isEqual = require('lodash-node/modern/objects/isEqual');

assert(isEqual([1, 2, 3], [1, 2, 3]));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// In an AMD loader (RequireJS, `curl`, etc).
require(['lodash-amd/modern/objects/isEqual'], function(isEqual) {
  console.assert(isEqual([1, 2, 3], [1, 2, 3]));
});

// Using the `packages` array of an AMD loader to specify a default build.
require({
  'packages': [{
    'name': 'lodash',
    'location': 'lodash-amd/modern'
  }]
}, ['lodash/objects/isEqual'] function(isEqual) {
  console.assert(isEqual([1, 2, 3], [1, 2, 3]));
});

You can also import entire categories and builds. Note that using the packages array allows you to alias the Underscore build as the 'underscore' module, and load it side by side with the modern or compat build. This is convenient if you haven’t migrated all your existing modules to Lo-Dash, but still want to use some Lo-Dash features:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Loading a category in Node.
var assert = require('assert'),
    collections = require('lodash-node/modern/collections');

assert(!collections.every([true, true, false]));

// Loading the Lo-Dash and Underscore builds in Node.
var lodash = require('lodash-node/modern'),
    underscore = require('lodash-node/underscore');

var numbers = [{
  'name': 'John-David',
  'lucky': [21, 47, 46]
}, {
  'name': 'Mathias',
  'lucky': [62, 89, 16]
}, {
  'name': 'Blaine',
  'lucky': [7, 33, 75]
}, {
  'name': 'Kit',
  'lucky': [86, 69, 123]
}];

assert(lodash.find(numbers, { 'lucky': [86] } ).name == 'Kit');
var blaine = underscore.findWhere(numbers, { 'name': 'Blaine' });
assert(lodash.isEqual(blaine.lucky), [7, 33, 75]);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Aliasing the Underscore build as the `'underscore'` module using
// `packages`.
require({
  'packages': [{
    'name': 'lodash',
    'location': 'lodash-amd/modern'
  }, {
    'name': 'underscore',
    'location': 'lodash-amd/underscore'
  }]
}, ['underscore', 'lodash/collections', 'lodash/objects/isEqual'] function(_, collections, isEqual) {
  var numbers = [{
    'name': 'John-David',
    'lucky': [21, 47, 46]
  }, {
    'name': 'Mathias',
    'lucky': [62, 89, 16]
  }, {
    'name': 'Blaine',
    'lucky': [7, 33, 75]
  }, {
    'name': 'Kit',
    'lucky': [86, 69, 123]
  }];

  console.assert(collections.find(numbers, { 'lucky': [86] } ).name == 'Kit');
  var blaine = _.findWhere(numbers, { 'name': 'Blaine' });
  console.assert(isEqual(blaine.lucky), [7, 33, 75]);
});

Additionally, all Lo-Dash methods in the modern build are available as individual packages on npm. Now, including a deep equality function in your Node module is as easy as running npm i --save lodash.isequal. You’ll receive all the performance and consistency benefits of Lo-Dash without the additional cruft. This also makes it easier for other libraries to consume Lo-Dash.

New Features in 2.0

Lo-Dash 2.0 contains eleven new methods, including _.curry and right-associative collection methods, and broad optimizations. Notably, the transformation functions—_.bind, _.bindKey, _.curry, _.partial, and _.partialRight—have been further optimized to avoid rebinding previously-bound functions. _.throttle and _.debounce now amortize all function invocations, reducing the overhead of unnecessary timer queueing.

Other consistency fixes have been made to _.at, _.createCallback, _.first, _.last, and _.zipObject, and all array methods now support arguments objects. This release also includes one backward-incompatible change to _.after for parity with the current Underscore implementation. Finally, we’ve migrated Lo-Dash to the centralized lodash organization on GitHub, and added Blaine Bublitz as a new core team member.

Lo-Dash 2.0 is the most extensive release of Lo-Dash yet…and just in time for JSConf.eu 2013. Enjoy!

— John-David, Blaine, Mathias, and Kit