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

Note: This article does not apply to create-react-app projects. The current version of CRA is currently broken with respect to being able to properly setup absolute paths.

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.

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).

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' ); 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

Curious to try React/Redux?