TIL: Importing Lodash into Angular, the better way.

Update — December 2018: be sure to check out the article: The Correct Way to Import Lodash Libraries — A Benchmark for detailed comparisons between each import method.

I have been working on an Angular app for many months. As the app grows, our team adopt lodash to help us with achieving small tasks with shorter code. Today I learned how to import methods from lodash which produce smaller bundle size from Angular CLI’s build task.

For this post, I create a new Angular project with the CLI tool, install lodash with $ yarn add lodash . I am using Angular 4.2.5, CLI 1.2.0, and lodash 4.17.4.

This is our typical use case:

import * as _ from 'lodash';
_.forOwn((value, key) => {

_ comes with all methods in lodash. But most of the time, we use only a few methods in our components/services, and the syntax above results in the code imports too many unused methods. The bundle size is unnecessary bigger than it should be. 71.3kB just for forOwn method is way too much.

Then we discovered another import syntax which looks better — looks like we import only modules we need.

import { forOwn } from 'lodash';

But source-map-explorer still tells us that there is no difference in size than the previous import syntax.

The better way to import a lodash method is this syntax:

import { forOwn } from 'lodash/forOwn';
// or
import forOwn from 'lodash/forOwn';

Note that we are importing from lodash/forOwn instead of just lodash.

Check again with source-map-explorer, the size of lodash comes down from 71.3kB to only 4.5kB. 🎉

and when we zoom in, inside of lodash, there are only a handful of methods, which are mostly required by forOwn itself and its dependencies.

That’s cool. But why is that? To find out, check the source files!

left: node_modules/lodash/lodash.js, right: node_modules/lodash/forOwn.js

When we do import * as _ from 'lodash';, it uses the file on the left which is the “full build” of lodash — contains everything in lodash in a single file.

While doing import forOwn from 'lodash/forOwn'; uses the file on the right which contains only forOwn itself. We are “cherry-picking” only methods we want. Therefore, it imports only some parts of lodash we really need, and we get smaller bundle size from the Angular CLI/Webpack build.

Bonus: ES6 module imports & exports

Considering 2 ways to import and export a value in ES6: named export and default export.

Named Exports

Named export is prefixing a value to export with export keyword. Multiple export can be in the same file.

// myModules.ts
export const myFunc = function() { }
export class AwesomeClass { }
export interface JustAnotherInterface {}

To use named export, we have to import the values with the same name of what was exported. This is called named import.

// main.ts
// import multiple objects
import { myFunc, AwesomeClass, JustAnotherInterface } from './myModules';
// or import them separately
import { myFunc } from './myModules';
import { AwesomeClass } from './myModules';
import { JustAnotherInterface } from './myModules';

Note that if we only import 1 value from myModules , we still need the curly braces { } . If we omit them, TypeScript throws an error: Module '<NAME>' has no default export. 🤔

An error message when we import named exports without curly braces.

Default Exports

When we import something without curly braces { } , it is then the default import. And as the name implies, it imports the default exported value.

Default exports are created with export default keywords. The difference is that there can be only 1 default export per file. (In fact, a default export is just another named export with a special name: default.)

// moduleA.js
export default function() {}
// moduleB.js
export default 555;
An error message when there are more than 1 default export in a file.

That’s the overview of ES6’s import and export.

Read on: Exploring ES6: Modules

Lodash, however, provides modules in CommonJS module format. I’m still digging into how they are all working together:

  • How are CommonJS modules imported in ES6 modules?
  • How does TypeScript transpile different module formats and glue them together?
  • Or how do default & named exports in ES6 modules relate to single & multiple exports in CommonJS?

I’ll post more updates when I discover more. Any guide through these unknown cave for me is more than welcome. Cheers! 🙏