Article summary
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.
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 “@”.