The most reliable way to avoid relative imports in Node.js
Tags: node, javascript
Published: 2021-10-15 08:52:23

Writing relative imports in Node.js is something I tend to avoid especially when it's growing larger in functionality. However, For something this basic yet it's so hard to get right. There are just many ways of doing that on the internet.

There are many ways to avoid relative imports in Node.js. One of them is either:

1. Add `NODE_PATH=./` env ( [reference](https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders) )
2. Set `"baseUrl"` in `(js|ts)config.json` ( [reference](https://dev.to/ruppysuppy/how-pros-get-rid-of-relative-imports-in-js-ts-2i3f) )
3. Use `require.main.require` ( [reference](https://stackoverflow.com/a/26163910/3908409) )
4. Directly write into `node_modules` ( [reference](https://github.com/browserify/browserify-handbook#avoiding-) )
5. Use NPM/Yarn workspaces  ( [reference](https://classic.yarnpkg.com/lang/en/docs/workspaces/) )

There are many downsides to each approach.

1. Adding an environment variable requires adding `cross-env NODE_PATH=./` to all `package.json` scripts and every time you need to run the script yourself. This behavior is also somewhat unreliable during my testing, also VSCode Intellisense won't understand what you're trying to import.
2. The `baseUrl` option from `(js|ts)config.json` seems to work out of the box, only for VSCode Intellisense. Node.JS won't understand them so I need a babel compiler to set up, it's  [explained here anyway](https://medium.com/weekly-webtips/say-good-bye-relative-imports-in-nodejs-projects-65513bcdae6c) but to me, this is way too complicated. 
3. Using `require.main.require` seems like a hack to me, it enforces me to use that in all the scripts rather than the usual `require`, which of course it's something that I don't like.
4. Directly writing to `node_modules` is something against its purpose, also would you rather be willing to move your scripts to mode_modules? I wouldn't want it. It would become a nightmare to maintain.
5. Using NPM/Yarn workspaces seems promising at first glance but it enforces me to thinking in the way it was designed for "monorepo". Monorepo is good if you have multiple projects that share code, but really it's just too much because I just work on one big node app. Note this was Yarn only feature, NPM add support too but my last experience using it was [buggy](https://github.com/npm/cli/issues/3637#issuecomment-898975594).

I have found a less popular but way more reliable: `symlink-dir`. Let's me summarize their explanation on [NPM](https://www.npmjs.com/package/symlink-dir):

> Lets suppose you'd like to self-require your package. You can link it to its own node_modules:
> `symlink-dir . node_modules/my-package`

What is by mean to "link"? It's basically creating a directory shortcut. You can [read it more here](https://www.freecodecamp.org/news/symlink-tutorial-in-linux-how-to-create-and-remove-a-symbolic-link/). NPM/Yarn workspaces internally also doing this way. 

So to use `symlink-dir`, I just need to add these values in `package.json`:

```json
{
  "scripts": {
    "postinstall": "symlink-dir src node_modules/src",
  }, 
  "dependencies": {
    "symlink-dir": "latest"
  }
}
```

This creates a symlink from `src` folder to `node_modules` in my project. After `npm i` I can use `require('src/module.js')` instead of  `require('../../../src/module.js')`. Works with ESM imports too!

You can also add more symlinks by just appending the `postinstall` like `"symlink-dir src node_modules/src && symlink-dir lib node_modules/src/libraries"` and redoing `npm i`. Out of all solutions previously, this method works best to me. Hope you like it too!