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.
Imagine we have an API module which exports some functions corresponding to API endpoints:
// ./api/index.js
export const getPhoto = () => {
/* … */
};
export const getUser = () => {
/* … */
};
// ./app.js
import * as Api from "./api";
Api.getUser();
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!
We hate namespace imports because, when compare to named imports, the developer UX suffers:
- VS Code’s import suggestions won't help you if you type
Api
in a file where there is no import, leaving you to write out the import manually. - Similarly to
default
exports/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.
We can combine the best of namespace imports and named imports using a technique called named namespace imports.
- Import as namespace
- Re-export the namespace as a named export
- Everywhere else, import the named namespace
// ./api/api.js
export const getPhoto = () => {
/* … */
};
export const getUser = () => {
/* … */
};
// ./api/index.js
import * as Api from "./api"; // 1
export { Api }; // 2
// ./app.js
import { Api } from "./api"; // 3
Api.getUser();
Tree shaking still works in Rollup. It doesn't work in webpack v4, but that's fixed in v5.
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;