JavaScript for Enterprise Development — Part 4: Using ES6 Modules

In the last article I covered how to create, run and debug a new TypeScript project, if you’d like to check that out, go here :-).

In this article I’m going to cover how to modularise the code in your project using ES6 Modules.

A quick look at the world before ES6 Modules…

Since JavaScript was originally designed as a simple scripting language to be embedded in web pages, there was no accepted standard for separating code into modules — everything was global by default!

There were several attempts to establish standards for code modules, such as using Anonymous Closures, the Asynchronous Module Definition (AMD) specification, and the CommonJS format (which is what NodeJS uses by default). Sadly none of these ever became ubiquitous.

Happily, now ES6 has introduced ES6 Modules as a language feature, so finally we have a standard way to modularise our code! ☺

Right, Lets See ES6 Modules In Action!

First, lets create a file called people.ts in a folder called models:

Now lets consume this module. Create a file called import_by_name.ts in the root of your project with the following code:

Tidy!

Key Points about ES6 Modules

  • An ES6 Module corresponds to a single file on disk
  • Variables, Functions and Classes defined in an ES6 module file are only accessible within that module, unless they are specifically exported
  • An ES6 module is loaded exactly once, the first time it is imported. Subsequent imports will get references to the same instance of the module
  • The curly brackets { } in the import statement mean import by name. You can import anything that has been exported from the module you are referencing.
  • The string after the from statement is the path to the module you want to import. Starting with ./ means relative to the current folder. Alternatively if you need to reference a file in a parent folder, you can do e.g.import { thing } from '../other_folder/other_module';
  • There is no need to include the file extension in the from clause.

What if I don’t want to type out ALL TEH IMPORTS?!

Sometime you might find yourself writing something like this:

import { MyCustomClass1, AnotherClass, MY_CONSTANT, Class3, someFunction, SoManyImports } from './bigmodule'

Yuk! From here you have two options:

  1. Reduce the number of things in the module — ideally each module should do one thing. A long import list is often a sign you need to structure your code more concisely.
  2. Use a wildcard import. This can be done as follows:
import * as stuff from './bigmodule';

You can then reference anything exported from that module by prefixing it with stuff. for example stuff.Class3

Aliasing Import Names

Sometimes you may find that you need to use two imports from different modules that have the same name (e.g. if you are using a 3rd party library). In this case you can use the as keyword to ‘alias’ the imports:

import { load as loadCalendar } from './components/calendar';
import { load as loadTasks } from './components/tasks';
loadCalendar();
loadTasks();

Default Exports

ES6 Modules can also have one default export, by adding the default keyword to an export statement, as shown below in product.ts:

You can then import the default export of a module by omitting the curly braces:

While they can be useful in some cases, I generally recommend avoiding default exports because:

  • You can use any name you like when importing them (as demonstrated above), which can lead to confusion and issues with refactoring.
  • You can only have one per module

Lovely, now I can split my code into short, easy to maintain modules!

Yep! Later in the series, we’ll look at how to use tools such as Webpack to bundle all these small files back into a single blob for delivery to a web browser via a single <script> tag.

Cool! Whats next?!

In the next article we’ll look at a fundamental concept of JavaScript development — dealing with Asynchronous Operations.