As our JavaScript code grows in site, there comes a times where including one file in not enough. In this article we'll have a look at how to structure our code across multiple files using modules.

Enable modules

Given an index.html file that includes a app.js file using a script tag, to enable module, we'll have to include type="module":

index.html
<html>
  <head>...</head>
  <body>
    ...
    <script src="app.js" type="module"></script>
  </body>
</html>

Default Import/Export

Now, can include functions defines in other files:

app.js
import f1 from './dependency.js';
...
f1();

... where f1 is exported from within the dependency:

dependency.js
function f1() {
  ...
}
export { f1 as default };

Named Import/Export

An alternative that is usually used when multiple 'transfers' are required is using named exports/imports:

dependency.js
function f1() {
  ...
}
function f2() {
  ...
}
export { f1, f2};

On import, the names must match the exact names from export (order not relevant):

app.js
import {f1, f2} from './dependency.js';
...
f1();

Renamed Export

Function can be renamed as part of the export:

dependency.js
function f1() {
  ...
}
function f2() {
  ...
}
export { f1, f2 as f3};

Now, it's imported as f3:

app.js
import {f1, f3} from './dependency.js';
...
f3();

Renamed Import

Function can also be renamed as part of the import:

dependency.js
function f1() {
  ...
}
function f2() {
  ...
}
export { f1, f2};

Now, it's imported as f3:

app.js
import {f1, f2 as f3} from './dependency.js';
...
f3();

Inline exports

The exports can also happen inline for each function that needs it:

dependency.js
export function f1() {
  ...
}
export function f2() {
  ...
}

Combined imports: Default & Named

Both of the previous methods can be combined for some interesting results:

dependency.js
function f1() {
  ...
}
function f2() {
  ...
}
export { f1 as default, f2};

Now, the import might look a bit strange:

app.js
import f1, {f2} from './dependency.js';

Passthrough Exports

Say we have 2 dependencies and we don't want to have to import both.

Given a second dependency:

dependency2.js
function f2() {
  ...
}
export { f2 };

We can simply "pass it through" in the first dependency:

dependency1.js
function f1() {
  ...
}
export { f1 };
export { f2 } from './dependency2.js';

Now app.js will only have to import from one dependency:

app.js
import {f1, f2} from './dependency1.js';

Import all

A great technique that sames keystrokes on import is the ability to import all functions together.

Given:

dependency.js
function f1() {
  ...
}
function f2() {
  ...
}
export { f1, f2};

All exports can be imported together, but the usage is slightly different:

app.js
import * as dependant from './dependency.js';
dependant.f1();

Finally

Classes can also be exported and imported.

Unfortunately, modules are not available in IE (see MDN docs) but are in Edge.

To achieve backward compatibility and avoid multiple HTTP requests in a production environment, we should probably use a module bundler such as Webpack or Babel.