One of the great features of ECMAScript 2015 (previously known as ES6) is built in module loading. In some respects this is nothing new. CommonJS and AMD have been around for a while both of which fill the same role. The key difference is that it is now built into the language spec though it has been designed to work with existing CommonJS and AMD modules.

Here are some examples of how to write and load code using the new syntax

Assume we have a very simple library for converting currencies from GBP to USD. This contains a variable used to store the conversion rate and a function to perform the conversion

// lib/CurrencyConvertor.js

const conversion_rate = 1.54

function convert(value) {
  value * conversion_rate;
}

We want to reuse these in another script. The first thing we need to do is to mark this in our library as entities that can be loaded externally. We do this with the export keyword. We can either mark each entity individually or declare all the exports together. The first option simply involves prefixing each declaration.

// lib/CurrencyConvertor.js

export const conversion_rate = 1.54

export function convert(value) {
  value * conversion_rate;
}

Alternatively we can do them together in a single export declaration

// lib/CurrencyConvertor.js

const conversion_rate = 1.54

function convert(value) {
  value * conversion_rate;
}

export {conversion_rate, convert}

This export declaration can be anywhere in the script we wish. It doesn't have to be at the end.

Then in our script we use the import keyword to load the entities we need. We only need to import the things we wish to reference directly in our script. For example, the convert function above makes use of the conversion_rate constant. However we do not need to import the constant in order to use the function.

import { convert } from './lib/CurrencyConvertor';

try {
  console.log(convert(2));
  console.log(conversion_rate);
}
catch(err) {
  console.log(err.message);
}

generates the following output

3.08
conversion_rate is not defined

If we do need to get to the conversion rate we can include that in our list of imports

import { convert, conversion_rate } from './lib/CurrencyConvertor';

try {
  console.log(convert(2));
  console.log(conversion_rate);
}
catch(err) {
  console.log(err.message);
}

We now get the following output

3.08
1.54

As you can see this makes the conversion_rate constant and convert function directly available in our script. This is fine for a small library but for a larger library or if using a large number of libraries we may wish to isolate each library in its own namespace. We can do this as follows

import * as cc from './lib/CurrencyConvertor';

try {
  console.log(cc.convert(2));
  console.log(cc.conversion_rate);
  console.log(convert(2));
}
catch(err) {
  console.log(err.message);
}

This generates the following output.

3.08
1.54
convert is not defined

convert and conversion_rate are now only accessible through the cc namespace keeping our top-level namespace nice and clean.

Finally we can also rename values we import in the importing file using the as keyword

import { convert, conversion_rate as rate } from './lib/CurrencyConvertor';

try {
  console.log(convert(2));
  console.log(rate);
  console.log(conversion_rate);
}
catch(err) {
  console.log(err.message);
}

which gives us

3.08
1.54
conversion_rate is not defined

Default exports

The examples above are all what are called named exports and are useful when we have multiple entities defined in a file. Often though a file only contains a single entity, usually a class definition. In these cases we can mark the entity as the default export for the file.

For example, assume we have a class to implement a very simplistic currency formatter

// lib/CurrencyFormatter.js

class CurrencyFormatter {
  constructor(currency_symbol) {
    this.currency_symbol = currency_symbol;
  }

  format(value) {
    return this.currency_symbol + value;
  }
}

We can mark this as the default export for this file by prefixing the definition with the export default keywords.

// lib/CurrencyFormatter.js

export default class CurrencyFormatter {
  constructor(currency_symbol) {
    this.currency_symbol = currency_symbol;
  }

  format(value) {
    return this.currency_symbol + value;
  }
}

Then to import it into our script we use the same syntax as before but omit the curly braces

import CurrencyFormatter from './lib/CurrencyFormatter';

try {
  console.log(new CurrencyFormatter('£').format(2));
}
catch(err) {
  console.log(err.message);
}

Running this gives us the output

£2

We can rename our class as we import it if we so wish. For example

import Formatter from './lib/CurrencyFormatter';

try {
  console.log(new Formatter('£').format(2));
}
catch(err) {
  console.log(err.message);
}

In fact, we don't even need to name our class when we define it in our module. The above script would work just fine if in our module we had

// lib/CurrencyFormatter.js

export default class {
  // ... contents of the class
}

We can still export multiple other entities whilst specifying a default. So our library may look something like

// lib/CurrencyFormatter.js

export const pound_symbol = '£';
export const dollar_symbol = '$';

export default class {
  constructor(currency_symbol = pound_symbol) {
    this.currency_symbol = currency_symbol;
  }

  format(value) {
    return this.currency_symbol + value;
  }
}

We can use these in a script as follows

import Formatter, { dollar_symbol } from './lib/CurrencyFormatter';

try {
  console.log(new Formatter().format(2));
  console.log(new Formatter(dollar_symbol).format(2));
}
catch(err) {
  console.log(err.message);
}

which then generates the output

£2
$2

Aggregating modules

Imagine we now want to combine our conversion and formatting libraries into a single, albeit slightly incongruous, library that exports the contents of both. There is a special export from shorthand designed for exactly this purpose.

// lib/Currency.js
export * from './CurrencyConvertor';
export * from './CurrencyFormatter';
export {default as CurrencyFormatter} from './CurrencyFormatter';

We can then just import from Currency.js in our scripts

import { convert, CurrencyFormatter, pound_symbol } from './lib/Currency';

try {
  console.log(
    new CurrencyFormatter(pound_symbol)
      .format(
        convert(2)
      )
  )
}
catch(err) {
  console.log(err.message);
}

which gives us the output

£3.08

Further reading

Hopefully this has served as a useful introduction to the ECMAScript 2015 module loading syntax. I've pushed a bunch of these examples up to Github.

If you're after more information I'd recommend checking out the Mozilla hacks modules episode of the ES6 in depth series, the exploring js section on modules and jsmodules.io which also includes a handy side by side comparison to the CommonJS syntax.