Product · August 20, 2019

Named namespace imports

If you’re organising JavaScript/TypeScript code into modules, at some point you’re going to need to consider how you’re naming your…

Oliver Joseph Ash
Oliver Joseph Ash
Photo by chuttersnap on Unsplash

If you’re organising JavaScript/TypeScript code into modules, at some point you’re going to need to consider how you’re naming your imports and exports. At Unsplash, the issue we’ve found is that names either have too little information or too much—the former leads to naming conflicts and the latter leads to very long names.

Namespace imports are designed to help with this, but they have several disadvantages when compared with named imports. This article introduces named namespace imports, a technique which we’ve adopted at Unsplash to combine (as the name suggests) the best of both namespace imports and named imports.

Namespace imports

Imagine we have a module for our API which exports some functions corresponding to API endpoints:

// ./api/index.js
export const getPhoto = () => {
/* … */
};
export const getUser = () => {
/* … */
};
view raw index.js hosted with ❤ by GitHub
// ./app.js
import * as Api from ″./api”;
Api.getUser();
view raw app.js hosted with ❤ by GitHub

The good

We love namespace imports because they provide context. For example, instead of getUser(), which is ambiguous (“where are we getting a user from?”), we will see Api.getUser().

(The alternative would be to prefix the name of each named export, but that’s tedious.)

We were initially worried about whether tree shaking would still work with namespace imports, but it does!

The bad

We hate namespace imports because, when compared to named imports, the developer UX suffers:

  • VS Code’s import suggestions only currently work with named and default exports. So if you type Api in a file where there is no import for it yet, you will have to write that import out by hand. That said, we remain hopeful that VS Code will improve on this.
  • Similarly to default imports, each instance of a namespace import must have its own name—unlike named imports, these imports do not share a common name. This makes it difficult to enforce consistent naming. If we use VS Code’s rename functionality on a namespace import, it will only update the imported name in the current module.

Let’s eat our cake and have it

We can combine the best of namespace imports and named imports using a technique called named namespace imports.

  1. Import as namespace
  2. Re-export the namespace as a named export
  3. Everywhere else, import the named namespace
// ./api/api.js
export const getPhoto = () => {
/* … */
};
export const getUser = () => {
/* … */
};
view raw api.js hosted with ❤ by GitHub
// ./api/index.js
import * as Api from ″./api”; // 1
export { Api }; // 2
view raw index.js hosted with ❤ by GitHub
// ./app.js
import { Api } from ”./api”; // 3
Api.getUser();
view raw app.js hosted with ❤ by GitHub

Tree shaking still works in Rollup. It doesn’t work in webpack v4, but that’s fixed in v5.

If you want to take this one step further, you can remove the intermediary file by using a circular import to import the current module:

// ./api.js
export const getPhoto = () => {
/* … */
};
export const getUser = () => {
/* … */
};
import * as Api from ″./api”;
export { Api };
view raw api.js hosted with ❤ by GitHub

Can we do better?

The downside to named namespace imports is that you have to define an intermediary module to import and then re-export the namespace.

I wish ES modules had a way to say “export this whole module as a named export”. Something like:

// ./api.js
export const getPhoto = () => {
/* … */
};
export const getUser = () => {
/* … */
};
// Pseudo code:
export * as Api;
view raw api.js hosted with ❤ by GitHub

Update: there is some new syntax which brings us closer to this:

// ./api.js
export const getPhoto = () => {
/* … */
};
export const getUser = () => {
/* … */
};
export * as Api from ”./api”;
view raw api.js hosted with ❤ by GitHub
Share article