Fixing Node's Relative Module Imports

• yourordinarycat • ☆ dreamscape ☆

I bet you hate how this looks as much as I do:

import { thing, weee } from "../../../../utils/index.js";

// Or, if you're not using ESM just yet
const { thing, weee } = require("../../../../utils");

Say what you will about how hard this is to refactor, I just despise how it looks. Then you look up a solution, and it requires installing an npm package with 73 dependencies doing silly strange things, just to be able to write something that might just be supported out of the box on your frontend:

import { thing, weee } from "@/utils";

We won’t write exactly that, but it is how imports could look if you were using the built-in support for this in Next.js, and it’s a good approximation of what we want.

The Solution

Anyways, we’ll fix the example from the start of the article. On package.json:

{
  "imports": {
    "#utils/*": "./dist/utils/*"
  }
}

Now, we can import like this:

import { thing, weee } from "#utils/index.js";

// TODO: switch to ESM already!
const { thing, weee } = require("#utils");

Of course, I’m assuming you have that folder structure - you should change it based on yours.

TypeScript Support

What’s that? You want to use TypeScript? Of course - having this in your tsconfig.json should do:

{
  "compilerOptions": {
    "paths": {
      "#utils/*": ["./src/utils/*"]
    }
  }
}

The same import will work with TSC and the language server.

This also works pretty well with TSX, and CommonJS - you can read up on imports here, and paths here. And you don’t need to add a single dependency!