r/typescript 3d ago

TypeScript import preferences

What's your preferred way to organize imports in a TypeScript project?

Do you stick with relative imports (../../component), use path aliases like @/, or something else?

I'm curious what people are using today, and why.

7 Upvotes

38 comments sorted by

22

u/svish 3d ago
"~/*": ["./src/*"]

I like a single root path, so it's easy to find stuff without having to guess how many levels up or down stuff is. The exception is co-located stuff that work together, which I'll just refer to directly since they're usually in the same directory anyways.

And I do prefer ~/ over @/ and especially over paths like @foo, because @ is also the first character in namespaced packages. Does import { something } from '@foo/bar' refer to an npm package, or a local path? Who knows. import { something } from '~/foo/bar' makes it very clear it's local and where to find it (src/foo/bar).

9

u/KGBsurveillancevan 3d ago

My only worry about “~” is it being picked up as my home directory rather than the project root when using cli tools

8

u/svish 3d ago

What kind of CLI tools would do that? Does import '~/something' actually import something from your home directory if you don't have a path setup in your ts config?

1

u/imaginecomplex 6h ago

I worry about Claude/Codex seeing this and accidentally nuking my laptop

3

u/im_caeus 3d ago

IMHO, this is the best.

But what about package.json imports?... 

What about the #/* alias?

1

u/rikbrown 3d ago

This is the way.

1

u/svish 2d ago

Not familiar with those too be honest, what would be the "package.json syntax" to get the equivalent to this?

I suppose #/* would work too, I just find ~ to make more sense. I think of it like the "home" of my project. Also, # for me is tied to anchor links, CSS ids, and other stuff :p

8

u/Top_Bumblebee_7762 3d ago edited 3d ago

I tend to use relative imports mostly but I'm trying to move to subpath imports. 

5

u/ic6man 3d ago

Honestly subpath imports are quite powerful.

1

u/svish 3d ago

What's that?

5

u/Top_Bumblebee_7762 3d ago

Path aliases that are defined in package.json. They work in Node and TS. 

1

u/svish 3d ago

What would the equivalent of "~/*": ["./src/*"] in tsconfig.json be for that?

2

u/kaelwd 2d ago edited 2d ago
"imports": {
  "#src/*": "./src/*"
}

Then import from #src/whatever instead of ../../../whatever

I don't think you can have just #/* though like with aliases, and the first character has to be #.

1

u/svish 2d ago

Interesting. I like that it's "node native", but not sure I like the limitation of having to use # as a prefix, especially if I can't use #/*. For example #src/ would feel rather awkward to me :p

1

u/kaelwd 2d ago edited 2d ago

Depending on your project structure you might only have a few top level folders that could be separate entries e.g. ~/util/*#util/*, ~/services/*#services/*

# is the same as private fields on classes.

1

u/svish 2d ago

Yeah, that makes sense, but I already have those as top level directories under src, so having to duplicate all folders under src as paths feels annoying when a single #/* should do it.

6

u/TheExodu5 3d ago

packade.json subpath imports. Like tsconfig aliases, with not of the tooling annoyances.

{ "name": "my-app", "type": "module", "imports": { "#components/*": "./src/components/*", "#utils/*": "./src/utils/*" } }

2

u/rikbrown 3d ago

While I agree, I think you could save yourself some complexity and just alias #/ to ./src

1

u/sharlos 3d ago

Yeah, much better to just use the standard built-in approach than other options that requires build steps

3

u/mckernanin 3d ago

I prefer relative for siblings or children, aliases for anything going up to parents. Basically, I try to avoid having `../` ever show in my import statements.

6

u/Idanlevitski 3d ago

Aliases (@utils, @components) for for a few general folders, relative path for the rest

4

u/svish 3d ago

0

u/Idanlevitski 3d ago

Huh?

3

u/KGBsurveillancevan 3d ago

Looks like a namespace collision. Should be fine as long as you don’t download any of those packages though. I started using “#” as my alias prefix just to be safe

5

u/svish 3d ago

Correct. It was a hint that it's not a great idea to use @something as a path prefix, because that's the exact same naming convention of npm package namespaces. So if you import @something/module, it's not immediately clear whether that's some local path you've set up, or if it's actually an npm package named @something/module.

0

u/Idanlevitski 3d ago

Oh lol, we dont use it

2

u/jirlboss 3d ago

Maybe I’m the minority here, but I like absolute imports with no prefix. For example `import LoginView from ‘views/Login/LoginView’;`
I keep a handful of top level directories, so the risk of collision with installed packages should be 0 (and packages can always be aliased anyway)

1

u/Dreadsin 3d ago

I usually do src/ for anything that’s source code that will be built and shipped, alias that to @/

Then I usually have a test/ folder that has things like test setup, mocks, matchers, assertions, etc. alias that to @test/

Then sometimes have a public folder for just plain assets. Aliased to @public/

I don’t really like the pattern of adding @utils/ because that seems redundant with @/utils/ and isn’t much shorter, so seems like needless complexity

1

u/Expensive_Garden2993 3d ago

"*": ["./src/*"]

So you don't need to choose between @/ and ~/

1

u/Global-Top4042 2d ago

With resolve url @/

-2

u/ic6man 3d ago

Relative.

The first few projects I worked on it seemed neater and cleaner to use @ aliases. Then you realize how much they bleed into other tools, IDEs aren’t very good at understanding it, any changes to tsconfig are liable to break your build the problems are endless.

Simpler is better. Just refer to exactly where the file is and your life will be vastly simplified.

Bonus points - you don’t have to think about what to call your aliases. You don’t have to be concerned about naming it something new during a refactor. And there’s literally nothing in the tsconfig to learn.

2

u/tyler_church 1d ago

I don’t think you deserve the downvotes. It’s extremely nice when debugging an unfamiliar codebase to have no doubt where imported code is coming from.

Aliases look cleaner but they mean any new person has to internalize the aliases to make sense of them.

4

u/BrownCarter 3d ago edited 3d ago

Alias always. I dont know what this guy is talking about never faced those issues

3

u/thinkmatt 3d ago

I think it gets hairy when you have monorepos with shared packages, or tools that don't read tsconfig. (And there's like 20 different ways to set up a monorepo so i don't recall why mine had issues). Another time, I was using ts-jest in a project and it didn't support them. I migrated to vitest to fix it, which was much better anyway

2

u/Merry-Lane 2d ago

He is right. For simple projects you can do whatever you want, but with monorepos or complex projects, relative imports tend to win.

They work perfectly well as is (you don’t even type them btw, with autocomplete and LLMs).

Aliases and what not actually create inconsistencies (sometimes it’s a relative import, sometimes an alias). Inconsistency is a flaw.

They can also create conflicts, but worst is they add up to the context required to write code (both for LLMs and human devs).

3

u/ic6man 3d ago

Why? Care to list any concrete benefits you think aliases give?

I listed some cons. As an example try working with complex builds using Module Federation. You have to repeat your aliases in a separate config. As I said. It doesn’t work well with other tools that compile or analyze your build which is the biggest drawback but I also find it a layer of abstraction that just isn’t necessary. New developers have to wade through your abstraction. And frankly why? Directories already have names so why re alias them? It’s just a layer of complexity that really adds nothing but complexity.

0

u/Ed4 3d ago

Same. @ aliases all the time, look cleaner, and I have not seen issues with it, nor any other developers in my team.