Say Goodbye to ‘../../../..’ in your TypeScript Imports

Don’t you hate writing import lines and not being sure how many dot-dot-slashes you need to get to the right place? Sure, you can look over at the project tree but there are so many files that you’ve got to scroll and scroll.

Oh no… Was that '../../../../' or '../../../../../'? [scrolls back down]. It drives me bananas.

Things are even worse if you restructure your project tree and some files move higher or lower. Or you copy-paste import sections between files that are at different levels in the project tree. Now your watch window is a sea of red.

But… it shouldn’t matter if your dependency is up four directories vs. five. Unfortunately, that’s just how module resolution works in Node.js.

Wouldn’t it be great if you could forget about relative paths entirely?

You could have the deepest, most complex project structure… bring it on.

Instead of importing like this:

import { getUsers } from '../../../selectors/userSelectors';
import { loadUsersRequest } from '../../../actions/userActions';
import { ErrorMessage } from '../shared/messages';
import { UserList } from './userList';
import { logger } from '../../../../util/logger';

You could import like this:

import { getUsers } from 'selectors/userSelectors';
import { loadUsersRequest } from 'actions/userActions';
import { ErrorMessage } from 'ui/shared/messages';
import { UserList } from 'ui/users/userList';
import { logger } from 'logger';

No matter where your file sits in the tree. It Just Works.

tsconfig.json Updates

The solution is to define the paths and baseUrl properties in the compilerOptions section in your tsconfig.json file. These properties first showed up in TypeScript 2.0.

{
    "compilerOptions": {
        "module": "commonjs",
        "moduleResolution": "node",
        "jsx": "react",
        "baseUrl": "src",
        "paths": {
            "actions/*": [ "app/actions/*" ],
            "selectors/*": [ "app/selectors/*" ],
            "ui/*": [ "app/ui/*" ],
            "logger": [ "util/logger" ],
        }
    }
}

Notice that we can specify both an exact string (e.g. ‘logger’) and a path with an asterisk to match all subpaths (e.g. ‘ui/*’ matches ‘ui/users/userList’ and ‘ui/shared/messages’ etc).

webpack.config.js Updates

You will also need to configure the resolve.alias section in your webpack.config.js file because the TypeScript compiler doesn’t rewrite the paths in the emitted JavaScript.

const path = require('path');

// This helper function is not strictly necessary.
// I just don't like repeating the path.join a dozen times.
function srcPath(subdir) {
    return path.join(__dirname, "src", subdir);
}

module.exports = {
    resolve: {
        alias: {
            actions: srcPath('app/actions'),
            selectors: srcPath('app/selectors'),
            ui: srcPath('app/ui'),
            logger: srcPath('util/logger'),
        },
        // ...
    },
    // ...
};

Further Reading

Want Modern web development to be easier?

I've got lots of tools, tips, best practices, and code to share with you.
Sign up on my list to be notified when new posts are published.

(100% spam free and one-click unsubscribe)

Say Goodbye to ‘../../../..’ in your TypeScript Imports

5 thoughts on “Say Goodbye to ‘../../../..’ in your TypeScript Imports

  1. “paths”: [
    “actions/*”: [ “app/actions/*” ],
    “selectors/*”: [ “app/selectors/*” ],
    “ui/*”: [ “app/ui/*” ],
    “logger”: [ “util/logger” ],
    ]

    you square brackets should be braces.
    Cheers for the article

Leave a Reply

Your email address will not be published. Required fields are marked *