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

TypeScript Webpack

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

What other TypeScript tips and tricks is your project missing?

Stay in the loop by signing up on my email list where I write about TypeScript and related web development topics.