Using TypeScript Project References to share common code
Tags: javascript, typescript, beginners
Published: 2021-03-30 22:50:43

Ever wondered if you can share interfaces, types and functions between TypeScript projects?

I'm currently developing a project consisting of two separate TypeScript applications, one being a React.js dashboard and the other an Azure Function app written in Node.js. As part of the project, the dashboard calls an API in the Azure Function app. This got me thinking, as I'm in control of both the data source and the application that uses the data, is there a way that I can share certain interfaces between the two projects?

The answer is yes, since version 3 of TypeScript you can use [Project References](https://www.typescriptlang.org/docs/handbook/project-references.html) to share code between TypeScript projects. When using Project References in my project, however, I couldn't find any official examples on how to use them - hence this post! 

*While the implementation below is what has worked for me, if you have any improvements, let me know in the comments.*

## What are [Project References](https://www.typescriptlang.org/docs/handbook/project-references.html)?

> Project references allow you to structure your TypeScript programs into smaller pieces. By doing this, you can greatly improve build times, enforce logical separation between components, and organize your code in new and better ways.

## How to use

Take a project that consists of a frontend and a backend written in TypeScript. Both contain an interface called `IData` which is exactly the same. Currently, each time I make a change, I have to duplicate it in the other file (which is extremely annoying). 

The directory of the project is:
```
myproject
- frontend
  - app.ts
  - interfaces
    - IData.ts
  - tsconfig.json
- backend
  - server.ts
  - interfaces
    - IData.ts
  - tsconfig.json
```

In order to use a single `IData.ts` file between both projects, we can use Project References.

### Adding the common TypeScript project

We will start by creating a third TypeScript project called `common`, adding an empty `tsconfig.json` file and copying the `IData.ts` interface over. We can also remove it from the `frontend` and `backend` apps. So the directory structure will be:

```
myproject
- frontend
  - app.ts
  - tsconfig.json
- backend
  - server.ts
  - tsconfig.json
- common
  - interfaces
    - IData.ts
  - tsconfig.json
```

This isn't enough though. In the `common` app's `tsconfig.json` we need to add the following:

```json
{
    "compilerOptions": {
        "target": "es5", // Or whatever you want
        "module": "es2015", // Or whatever you want
        "declaration": true,
        "declarationMap": true,
        "outDir": "./dist",
        "composite": true
    }
}
```

The key parts are:
* `declaration`: Generates a declaration file that the `frontend` and `backend` apps can use to reference items in the `common` app.
* `composite`: Ensures TypeScript can quickly determine where to find the outputs of the referenced project
* `declarationMap`: Enables editor features like “Go to Definition” and Rename to transparently navigate and edit code across project boundaries in supported editors

### Referencing the common project in `frontend`/`backend`

To reference the common `IData` interface in the `frontend` and `backend` apps we need to make a simple change to both of their `tsconfig.json` files. Add the `references` property to your existing `tsconfig.json`.

```json
{
    "compilerOptions": {
        // The usual
    },
    "references": [
        { "path": "../common" }
    ]
}
```

### Building the `frontend`/`backend` apps

Now that we've added the reference to the common app in order to access its interfaces we need to compile both the `frontend` and `backend` apps. 

When doing so, ensure you use the `--build` option so that TypeScript automatically builds all referenced projects.

```
tsc --build .
```

Note: If you're using Next.js with TypeScript, I didn't need to do this. Both `next dev` and `next build` kept working just the same.

### Importing the common interface into `frontend`/`backend`

This is easier than you might first think, just import `IData` using its relative path. TypeScript will do the magic when you compile it.

```typescript
import IData from '../common/interfaces/IData'
```

*Note: if your compiler has problems with the `IData` file, you can add `type` after the `import`. See: https://dev.to/uppajung/comment/1f6bc*

## Summary

In this post, I've demonstrated how to use TypeScript [Project References](https://www.typescriptlang.org/docs/handbook/project-references.html) to use a common project for shared interfaces, functions, types and more!

Feedback on my approach is appreciated! As I said above, I couldn't find an official example to guide me on how to use Project References so any feedback in the comments will help me improve this tutorial and my own TypeScript projects!

Thanks for reading!