Why Tsup Beats Nodemon and Ts-Node for Path Alias in TypeScript
Table of Contents
Introduction
To answer the title’s questions, it’s because no extra configuration is required for tsup! I have previously talked about how we can get a typescript projected started with tsup, typescript and express. I have also mentioned that we did not configure tsconfig.json
, so let us tackle this part with path aliases!
Note that for simplicity sake, you’d find parts where am telling you to just add lines. in those cases, you should just add those lines rather than replacing the entire file.
Setting up the Project
Let’s start by setting up a new simple typescript project project using tsup
, express
, and typescript
. We’ll also create a tsconfig.json
file to configure path aliases.
Initialize Node Project
Create a new directory for your project and navigate into it:
mkdir my-project cd my-project
Initialize a new node project. I use Yarn, but you can do the same for npm or pnpm:
yarn init -y
Since we’re at it, let’s add in our dev
and build
scripts in the package.json
file:
"scripts": { "dev": "tsup src/index.ts --watch --onSuccess 'node dist/index.js'", "build": "tsup src/index.ts" }
Install Dependencies
Install the required dependencies:
yarn add express
yarn add -D typescript tsup express @types/express
Configure tsconfig.json
So for this step, we won’t overcomplicate the situation and we’ll just initialize with the default tsconfig.json
, and we can do that with this command:
yarn tsc --init
and it will generate a relatively large json file that has all sorts of commented configurations. the cleaned up version will look as such:
{ "compilerOptions": { "target": "es2016", "module": "commonjs", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true } }
there are some of you that may wonder why I used
yarn tsc init
instead oftsc init
, and I have a future blog post that details why you should avoid using global npm CLI commands.
Initialize Express Server
Create a new TypeScript file called index.ts
in a src
directory:
mkdir src touch src/index.ts
Add the following code to src/index.ts
:
import express from 'express'; const app = express(); const port = 3000; app.get('/', (req, res) => { res.send('Hello World!'); }); app.listen(port, () => { console.log(`Server listening at http://localhost:${port}`); });
finally, we can try out to run our code with:
yarn dev
navigating to http://localhost:3000
, you should be able to see a page with Hello, World!
in it.
How to Configure Path Aliases with Tsup
Here’s the fun part, we just need to configure our tsconfig.json
for it, and tsup
will read from that file to understand our path aliases when bundling, which is awesome!
so we’ll configure our tsconfig.json
by adding baseUrl
and paths
properties in the compilerOptions
section as such:
"compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["src/*"] } }
So what is Path Alias exactly?
In the example tsconfig.json
above, we define @/*
as a path alias for src/*
. This means that any import statement that starts with @/
will be resolved to src/
.
Regardless of in which file we want to import another file from, it will always be the same:
import { User } from '@/models/user';
as oppose to the messy ../../
directory traversal that we have without path alias configured:
import { User } from '../../models/user';
Demonstrating Path Alias
Let’s create a simple example to demonstrate this. We’ll create a User
model in a file called user.ts
in the src/models
directory, and then import it into index.ts
.
Create a new file called user.ts
in the src/models
directory. If you are running those commands, then make sure you are at the project’s root:
mkdir src/models touch src/models/user.ts
Add the following code to src/models/user.ts
:
export const User = (name: string, email: string) => { return { name, email }; };
Import the User
model into index.ts
using a path alias:
import express from "express"; import { User } from "@/models/user"; const app = express(); const port = 3000; const user = User("Alice", "alice@example.com"); console.log(user); app.get("/", (req, res) => { res.send("Hello World!"); }); app.listen(port, () => { console.log(`Server listening at http://localhost:${port}`); });
Now when you run the project with yarn dev
, and we should see the User
object logged to the console.
yarn dev
output:
{ name: 'Alice', email: 'alice@example.com' } Server listening at http://localhost:3000
And there we have it! super simple to set up.
Since tsup
bundles everything into a single javascript file, then it also flattens the import statements to be from the same bundled file. What happens in this case is that as a byproduct, our bundled index.js
file will not have any import statements with the @/
path alias. This means that we don’t need to do any module resolution like how we did before when we used nodemon and ts-node.
This is a wonderful thing, and has previously bugged me for the longest time when I used ts-node
.
Finally, path aliases and hot reloading both working! Thanks a lot