Resilient Imports with Auto-Generated TypeScript Aliases
Tags: webdev, typescript, alias, productivity
Published: 2022-05-06 23:55:11

If you're not familiar, TypeScript [paths](https://www.typescriptlang.org/tsconfig#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:

```typescript
import { myService } from '../../../services/my-service'
```

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:
```json
{
   "compilerOptions":{
      ...
      "paths":{
         "@services":[
            "services/index"
         ],
         "@services/*":[
            "services/*",
            "services/index"
         ]
      }
   },
   ...
}
```
which would change your fragile import into something more resilient, that won't break when moved to a new folder:

```typescript
import { myService } from '@services/my-service'
```

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](https://www.npmjs.com/package/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
```
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:

```json
"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"],
  "@/*": ["./*"],
  "~/*": ["../*"]
}
```
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:
```javascript
// 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);
```

### 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 ](https://www.npmjs.com/package/nodemon)(more on npm):
```json
  "scripts": { 
      "dev": "nodemon -w tsconfig.js --exec \"run-s build:tsconfig dev:parcel\"",
      "build:tsconfig": "node tsconfig.js"
      "dev:parcel": "parcel src/index.html"
  }
```
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!