Ernesto Wulff

Software Engineer
{ Home / About / Blog }

Tue Jan 23

Organising import statements in a typescript project

The modularity ECMAScript modules provide is really flexible and powerful. So much so that reusing ESM modules via imports can quickly become a significantly large part of your applications. Think of the following example:

import core from "@acme/core";
import type { UserDetails } from "./userTypes";
import { fetchData, postData } from "./api";
import extension, { optional } from "@/local-module";
import { parser } from "@/local-module";
import mathFunctions from "./math";
import messageUtils from "./messages";
import { capitalize, reverse } from "./stringUtils";
import { logError, logInfo, anUnusedImport } from "./logger";
import type { LogData } from "./loggerTypes";

In this not-so-large chunk of imports, we have types, default and named imports coming from possibly npm packages (@acme/core), potentially reused exports via typescript path aliases and even an unused import.

It will be tough for a developer not familiar with this code base to understand what each of those imports is used for. Did someone leave that unused import for some reason? In react, a typical “hack” to fix jest test suites is to import the whole react library on each component you are testing, a solution far from ideal.

Many engineers argue that imports should be something to which not much attention should be paid. Some argue that they shouldn’t be treated as code. I tend to disagree with such statements and highly recommend automating the maintainability of your imports at an early development stage.

IDE Support

A good first step to improve your imports styling is using your code editor on save function. For VS Code, add the following to your .vscode/settings.json configuration file:

{
  "source.organizeImports": true
}

This will work well as long as you are the only one working on a project or the use of VS Code with this particular settings.json is enforced across all the developers working on the project.

Prettier

Prettier is an opinionated code formatter. It can be installed as a dev dependency on the root of a project, be it a monorepo or a single package or application. It can be configured via a prettier.js file and allows enforcing things such as the usage of single vs double quotes, spaces vs tabs, etc. Its a great tool to automate code format across projects and teams, but it does not provide an out-of-the-box solution for our problem.

@trivago/prettier-plugin-sort-imports

prettier-plugin-sort-imports is an extension that hooks into the prettier commands and helps formatting code from what we had before:

import core from "@acme/core";
import type { UserDetails } from "./userTypes";
import { fetchData, postData } from "./api";
import extension, { optional } from "@/local-module";
import { parser } from "@/local-module";
import mathFunctions from "./math";
import messageUtils from "./messages";
import { capitalize, reverse } from "./stringUtils";
import { logError, logInfo, anUnusedImport } from "./logger";
import type { LogData } from "./loggerTypes";

into something more readable:

import core from "@acme/core";

import extension, { optional } from "@/local-module";
import { parser } from "@/local-module";

import type { UserDetails } from "./userTypes";
import { fetchData, postData } from "./api";
import mathFunctions from "./math";
import messageUtils from "./messages";
import { capitalize, reverse } from "./stringUtils";
import { logError, logInfo, anUnusedImport } from "./logger";
import type { LogData } from "./loggerTypes";

Readability has already vastly improved, but we still have a couple of issues:

The reason for this is that this prettier plugin follows the prettier non-destructive principles. Maybe anUnusedImport is there for a reason, so we will just leave it as is. The solution is to add yet one more plugin.

prettier-plugin-organize-imports

prettier-plugin-organize-imports is another prettier plugin that leverages the organizeImports TypeScript feature: the same that “source.organizeImports” uses under the hood.

I find this plugin great because:

If you are starting a greenfield project, this might be the only plugin you want to use. However, if a stricter import order is required or demanded by the team, it won’t be enough.

Next steps

Our imports are now merged, sorted, cleaned up and organised automatically, but only after manually running prettier.

Git hooks and husky

Have a looks a husky pre-commits. A great way of keeping your code “prettified” is by automatically running prettier before saving each commit.

While being a nice step in the right direction, this can be bypassed by:

CI/CD

Prettier allows for performing checks via CLI commands. Integrate this with your CI/CD pipelines as an isolated step named format or similar. This will help preventing any unformatted import to leave new code contributions.

Summary

As usual in the JS/TS ecosystem, the number of choices is high, and the cognitive overload for choosing something like a nice code formatter can lead to too many headaches.

As of 2024, I would recommend using just prettier-plugin-organize-imports as I find it a cleaner choice for most mid-size projects. However, for larger projects where hundreds of dependencies need to be organised, the level of customisation @trivago/prettier-plugin-sort-imports offers will come in handy. There’s a feature request for the latter asking for supporting merge imports. If this ever hits a release, then things may change and prettier-plugin-organize-imports may no longer offer a benefit over the other plugin.