Importing with Absolute Paths using webpack in JavaScript/TypeScript

Using relative paths in your import statements is great for “Hello World” examples and blog posts. But when used in large projects with hundreds of files and deep hierarchical directory structures, relative paths become a nightmare (see Rob Ashton’s post Stop using relative paths in your JavaScripts for some of the reasons why this is so).

Relative paths aren’t entirely bad. For example, when importing a closely related file, something that would be considered part of the same module (likely within the same directory), using a relative path is succinct and can document how closely related the files are. But in my experience, relative path imports are used in all cases, throughout the codebase.

I’m assuming this is because relative paths work out of the box. No additional configuration is needed to support them–which is not the case for absolute paths. This situation is unlike most other programming languages (Java, C/C++, Ruby, etc.), where both options are readily available, and convention has people using absolute paths more frequently than relative paths.

webpack Configuration

It’s easy to configure webpack to look for your source files using an absolute path. Just add a root to your resolve section:


var path = require('path');

// ...
resolve: {
  root: [
    path.resolve('./src'),
  ],
}

From the webpack documentation

The directory (absolute path) that contains your modules. May also be an array of directories. This setting should be used to add individual directories to the search path.

Now, instead of this:


import { DateFormatter } from '../../../../shared/format/dateFormatter';

You’ll be able to import like this:


import { DateFormatter } from 'shared/format/dateFormatter';

Aliases

In some cases, full-length absolute paths might be a bit unwieldy. For example, if you’ve got a file with commonly used helper functions, located deep within your directory structure (and it makes organizational sense for it to be there), you might end up frequently importing something like this:


import { add, subtract } from 'common/tools/utils/helpers/math/arithmetic';

By specifying an alias in your webpack config, you could instead import this:


import { add, subtract } from 'math/arithmetic';

This is done by specifying an alias in the resolve section:


resolve: {
  alias: {
    math: path.resolve('./src/common/tools/utils/helpers/math')
  }
},

I’ve done this type of thing on projects before, and it works really well. But there is a downside of using these kinds of aliases. They can trick developers into thinking there’s a top-level math directory, when in reality, it’s just an alias.

Guilherme Oenning has a good suggestion in his How to avoid relative path hell in JavaScript/TypeScript projects post, which I came across while doing research for this post. He suggests prefixing your aliases with an @ to differentiate them from npm module imports and normal absolute paths.

This is how the import would look in that case:


import { add, subtract } from '@math/arithmetic';

I haven’t done this in practice, but I like the idea, and I’ll probably try it on a future project.

TypeScript Compiler

If you’re using TypeScript, you’ll need to make a change to your tsconfig.json in order for the TypeScript compiler to be able to resolve the paths.


compilerOptions: {
  // ...

  "baseUrl": "./src",
  "paths": {
    "math/*": [
      "common/tools/utils/helpers/math/*"
    ],
  }
}

ts-node

Unfortunately, the ts-node command line tool doesn’t seem to honor the compilerOptions paths out of the box. If you run into trouble with ts-node, you can try using the tsconfig-paths package:


terminal> yarn add tsconfig-paths

And then include it on the command line whenever using ts-node:


terminal> ts-node -r tsconfig-paths/register main.ts

Mocha

The mocha command line utility also needs to be told about the tsconfig-paths package in order for it to property resolve the paths in TypeScript. I got it to work by adding the following to my mocha.opts file:


--compilers ts:ts-node/register -r tsconfig-paths/register

Conclusion

Once all of these have been configured, it’s not something you need to think about again. You’ll no longer be living in relative path hell, so you can focus on writing great code instead of counting the number of times “../” appears in an import statement.

Conversation
  • Thomas says:

    Nice article, but I wanted to add a comment about:

    > He suggests prefixing your aliases with an @ to differentiate them from npm module imports and normal absolute paths.

    “@math/arithmetic” is a valid npm module since they introduced scoped packages: https://docs.npmjs.com/getting-started/scoped-packages so this doesn’t solve the ambiguity regarding aliases and npm packages. Or am I missing something?

    An alternative would be to use “~” instead of “@”.

  • Comments are closed.