A Better Way to Import Local Node.js Modules
Tags: node, npm, javascript
Published: 2020-04-22 11:42:06

This article is more or less an advertisement for an npm package that I have just released: [**basetag**](https://github.com/janniks/basetag). I want to share how the package came to be and why I believe it’s pretty awesome.

## A Bit of Backstory

Node.js projects — like all software development projects — can get somewhat complex over time: developers often refactor functionality into separate modules, subdirectories, and helper classes; in less stressful times [tech debt](https://martinfowler.com/bliki/TechnicalDebt.html) can be paid off.

> As a system evolves, its complexity increases unless work is done to maintain or reduce it.
> — Lehman’s Laws of Software Evolution

Also nowadays, the [monorepo](https://en.wikipedia.org/wiki/Monorepo) has become increasingly popular again. This shows that projects and their structures can become very large in scope. Different programming languages have different approaches to working with this. Most modern programming languages use [namespaces](https://en.wikipedia.org/wiki/Namespace) and modules/packages. Some example are listed below.

- [Swift has modules and implicit namespaces](https://twitter.com/clattner_llvm/status/474730716941385729) (e.g. `import Foundation`)
- [Python has modules](https://docs.python.org/3/tutorial/modules.html) (e.g. `import pandas as pd`)
- [Java has packages](https://en.wikipedia.org/wiki/Java_package) (e.g. `import java.util.Date`)
- [Ruby](https://ruby-doc.com/docs/ProgrammingRuby/html/tut_modules.html), [Rust](https://doc.rust-lang.org/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html), [C++](https://docs.microsoft.com/en-us/cpp/cpp/modules-cpp?view=vs-2019), and many more have similar concepts somewhere. Heck, Linux itself has a [namespaces(7)](http://man7.org/linux/man-pages/man7/namespaces.7.html) API!

Yet, in Node.js we can only import local modules via _relative path_…

## Node.js Imports

If you’ve used Node.js, you know this and have seen many statements like the following one.

```js
const myModule = require('./MyModule')
```

Now that doesn’t seem too bad… But let’s consider a more complex project. Most of the time we will be importing modules that aren’t far away. Yet, it can happen that we have modules that are loosely coupled and far away (in terms of files). Please consider the following directory structure (although it might be a fabricated example and maybe even indicate some code smells).

```
example/
├── its/
│   ├── …
│   └── baseballs/
│       ├── …
│       └── all/
│           ├── …
│           └── the/
│               ├── …
│               └── way/
│                   ├── …
│                   └── down.js
├── somewhere/
│   ├── …
│   └── deep/
│       ├── …
│       └── and/
│           ├── …
│           └── random.js
├── …
└── index.js
```

You get the picture — we have a bunch of directories with a bunch of files. Now say we want to `reference example/somewhere/deep/and/random.js` from `example/its/baseballs/all/the/way/down.js`. In other languages we could probably `import somewhere.deep.and.random as rand`, but in Node.js this gets pretty messy and would look like the following import statement.

```js
const randomRelative = require('../../../../../somewhere/deep/and/random')
```

This has always frustrated me considerably and I started doing some research. It turns out that there are a lot of tweets and blog posts that complain about this problem. But there are also some projects that try to tackle the problem.

One approach (the npm package [`app-root-path`](https://www.npmjs.com/package/app-root-path) tries to find the root path of a project and lets you import relative to that path. They even include a nifty `.require` method that you can reuse. This is already pretty cool!

```js
const projectRoot = require('app-root-path');
const random = require(projectRoot + '/somewhere/deep/and/random.js');

// OR using .require

const requireLocal = require('app-root-path').require
const random = requireLocal('somewhere/deep/and/random');
```

You could even store the `requireLocal` method into your globals 😱 in your entry file and it would be available in all other executed files. The package is great but I wanted to find something that feels even more _native_.

---

![Screenshot of basetag repository on GitHub](https://dev-to-uploads.s3.amazonaws.com/i/916q7qo4t2bq4zzowbx9.png)

I continued my search and came across some blog posts that proposed [symlinks](https://arunmichaeldsouza.com/blog/aliasing-module-paths-in-node-js) to reference to the base path of a project.

That’s how the idea for [**basetag**](https://github.com/janniks/basetag) was born.

The basetag package consists only of a `postinstall` script that adds a symlink `$` inside `node_modules`. That symlink points to the base path of your project. Node.js now basically thinks that there is a `$` module installed and you can require submodules of `$` (which in turn just point to your project files).

```js
const randomRelative = require('../../../../../somewhere/deep/and/random')

// Using 'basetag' becomes...

const randomBasetag = require('$/somewhere/deep/and/random')
```

All you need to do is install basetag (e.g. via `npm i -S basetag`) and you can start using the `$/…` prefix in require statements.

- The prefixed require is very readable, simple, and it’s pretty obvious what’s going on.
- The prefixed require is still mixable with traditional relative requires. Since Node.js is literally using the same files (just routing differently), the imports are cached correctly.
- The solution works with Node.js versions ≥ v4.x.
- The package is super simple and has zero dependencies.

---

Well, that was my path to creating the tiny package [**basetag**](https://github.com/janniks/basetag). Feel free to check it out and use it in your projects. Be aware that this package is stable but still very young — make sure to have all your files safe in version-control before using it. Due to the simple nature of the project there will probably not be a lot of updates to be expected…