Absolute Paths in Typescript & NodeJS

Absolute Paths in Typescript & NodeJS

Relative Paths

I might be super late to the party, but I learned something new the other day that I wanted to share. While this is common place in typescript...

import Bar from '../some/path/foo';

which can be super useful when it comes to refactoring entire folder / subfolder structures. It can get confusing since you always have to be conscious of how deep you are in your project directory.

example 1:

/* path = src/glorp */
import Bar from '../some/path/foo';

/* path = src */
import Bar from './some/path/foo';

/* path = src/some/path */
import Bar from './foo'

/* path = test/glorp/some/path */
import Bar from '../../../src/some/path/foo';

In the above example we have different paths to import the same source file, and all of it has to be relative to the directory distance between your current source file and the one you desire to import.

Enter absolute paths!

Absolute Paths

With absolute paths we could just write all our imports like...

import Bar from 'some/path/foo';

instead of how we have them in listed in example 1

This can be easily accomplished by just updating your tsconfig.json to include the "baseUrl: "src".

Example Config

{
    "compilerOptions": {
        "baseUrl": "src",
        "outDir": "./dist",
        "esModuleInterop": true,
        "moduleResolution": "node",
        "module": "commonjs",
        "target": "es5",
        "lib": ["es6", "dom"],
        ...
    },
    "include": [
        "src/**/*",
        "test/**/*"
    ],
    "exclude": [
        "node_modules"
    ],
}

In this example all source files in our src + test directory know to respect the absolute paths we specify, with the "baseUrl" of src as stated by our config.

Compiled TSC

The above config is all we need in order to develop using absolute paths, but you will run into an issue when it comes to building and deploying your code.

The tsc used to build our code will not try to resolve the absolute paths that get compiled down to regular old javascript. So while our /dist folder might look like this...

dist/
  - index.js
  - some/
      - path/
         - foo.js

The references to some/path/foo will cause node to be like WTF!?.

node_modules

The solution the to the problem above is actually pretty simple. But instead of giving you the straight forward one liner solution, its important to understand whats going on under the hood.

When we look at our compiled code...

// no issues
var express_1 = __importDefault(require("express"));

looks like an absolute path but it doesn't give us issues, unlike how our custom code will.

// node will complain
var utils = __importDefault(require("utils"));

so what gives?

The issue is that node has it built in to scan the ./node_modules folder when it runs into one of these absolute paths.

If it doesn't find a directory labeled express or utils it will recursively check all its parents ../node_modules ../../node_modules. If no express or utils is found it will then throw an error.

Solution

The solution here is to specify an environment variable called NODE_PATH

export NODE_PATH=./dist
...
node dist/index.js

this essentially tells node "Hey instead of just looking at node_modules, this ./dist directory might have the directories you are looking for"

With that we should be good to go to use absolute paths in not just our development environment, but with what we release to production.

Warning: Be conscious of conflicts between libraries + directory names that could exist in both node_modules + NODE_PATH. Either prepend all your absolute paths with something you know that will be unique, or just ensure the uniqueness between a npm install utils library vs your directory src/utils