DEV Community

Cover image for Resilient Imports with Auto-Generated TypeScript Aliases
Zach
Zach

Posted on

Resilient Imports with Auto-Generated TypeScript Aliases

If you're not familiar, TypeScript paths allow you to assign an alias to some path in your project. This is especially useful for larger projects.

Working Harder

For example, say you're editing code in a path like components/MyComponent/src/hooks/myHook.ts, and you need to import some services code. Your services code might sit at the project root next to components/. Naively, you might use a relative path like:

import { myService } from '../../../services/my-service'
Enter fullscreen mode Exit fullscreen mode

Import tools will often take care of some dirty work for you, such as updating imports when you move files. This relative path is still fragile, and lends itself to tedious work if you decide to move either the component or the service.

Working Smarter

Now to fix this, you'd add paths in your tsconfig.json like:

{
   "compilerOptions":{
      ...
      "paths":{
         "@services":[
            "services/index"
         ],
         "@services/*":[
            "services/*",
            "services/index"
         ]
      }
   },
   ...
}
Enter fullscreen mode Exit fullscreen mode

which would change your fragile import into something more resilient, that won't break when moved to a new folder:

import { myService } from '@services/my-service'
Enter fullscreen mode Exit fullscreen mode

And the best part is that vscode users will get free path intellisense from these aliases too. You'll be able to ctrl+. and add imports via your aliases.

Make it Even Easier with Automation

But if you're working with a large project, why not spare yourself the work of manually writing globs and copy-pasting your heart out in JSON?

I created tsconfig-paths-autogen (1kB minified, 0 dependencies), which will automatically alias all of your project folders. The big change is moving from tsconfig.json to tsconfig.js. I'll explain how this works.

Suppose this is your project directory structure:

src
├── a
├── b
│   └── c
│       └── d
└── e
    └── f
Enter fullscreen mode Exit fullscreen mode

If you want to alias all of them such that you can:

  • reference the project root and src,
  • import index files (with and without an trailing /),
  • go through to subfolders after an alias

then your paths would look like:

"paths": {
  "@a": ["a/index"],
  "@a/*": ["a/*", "a/index"],
  "@d": ["b/c/d/index"],
  "@d/*": ["b/c/d/*", "b/c/d/index"],
  "@c": ["b/c/index"],
  "@c/*": ["b/c/*", "b/c/index"],
  "@b": ["b/index"],
  "@b/*": ["b/*", "b/index"],
  "@f": ["e/f/index"],
  "@f/*": ["e/f/*", "e/f/index"],
  "@e": ["e/index"],
  "@e/*": ["e/*", "e/index"],
  "@/*": ["./*"],
  "~/*": ["../*"]
}
Enter fullscreen mode Exit fullscreen mode

It's tedious to get all these right and maintain them. So, your new tsconfig.js would generate the paths and export itself to json, something like:

// tsconfig.js
const { generatePaths } = require('tsconfig-paths-autogen');
const { onmyjs } = require('onmyjs'); // or any other tool to export this file to JSON.

module.exports = {
  compilerOptions: {
    ...
    paths: generatePaths(baseUrl, {
      // whatever you'd like to start your aliases with, can be empty.
      rootAlias: '@',
      // how far deep to alias from baseUrl
      maxDirectoryDepth: 2,
      // alias components folder but none under it.
      excludeAliasForSubDirectories: ['components'],
      // aliases for potentially excluded directories
      includeAliasForDirectories: {
        common: 'components/common',
      },
    }),
  }
}
// export to tsconfig.json
onmyjs(module.exports, 'tsconfig.json', true);
Enter fullscreen mode Exit fullscreen mode

Integrating this into your build scripts

So now you're probably asking what to do, because your tooling needs to re-generate tsconfig.json and react to changes. Here's an example for your package.json using nodemon (more on npm):

  "scripts": { 
      "dev": "nodemon -w tsconfig.js --exec \"run-s build:tsconfig dev:parcel\"",
      "build:tsconfig": "node tsconfig.js"
      "dev:parcel": "parcel src/index.html"
  }
Enter fullscreen mode Exit fullscreen mode

Note that this example uses parcel, but it is tooling-agnostic. Use it in webpack or whatever you want!

I hope this helps you have a smoother time with your imports. If you have any questions, leave a comment below. Thanks for reading!

Top comments (0)