DEV Community

Cover image for TypeScript: how do you share type definitions across multiple projects?
JavaScript Room
JavaScript Room

Posted on

TypeScript: how do you share type definitions across multiple projects?

Hey, devs! I've got a question for all TS folks around. How do you handle sharing common types/interfaces/enums across multiple TypeScript projects? Do you publish them to private NPM package, include into a project as a git sub-module or something else? I'm really curious to see what other options we have nowadays. Please, share your thoughts on this... Thanks you all in advance!

Have a great weekend meanwhile! Cheers!

Oldest comments (11)

Collapse
 
bradtaniguchi profile image
Brad • Edited

We looked into a few approaches a while back:

  1. npm package
    • maintainable
    • ease of use, as it works like any other npm package.
    • costs $ for private packages
  2. github repo's where in our package.json we provide the url to the package. (git submodule)
    • cost is folded into private github repos
    • kinda maintainable as versioning isn't easy
    • requires pushing compiled ts code into source
  3. symlinks/fancier approach
    • I don't remember how this one would of worked off the top of my head, but it relied on symlinks and was more of the "magical" approach that was much more open ended.

I believe we could of went a number of other routes, but didn't look into them at the time, or don't remember them.


We eventually went with the git submodule route, but found it somewhat annoying to deal with versioning, and updates. As updating required a few manual commands every time to build, and push the code back to github. Handling versioning also became annoying as it was very easy to introduce a breaking change. We probably could of did things better at the time but didn't have time to look into it much more.

After a while we started changing to a monorepo approach, which I recommend for full-stack TS projects, as you can leverage typescript's paths + webpack to build what you want and keep everything versioned the same. (you must have a build step, otherwise the paths approach doesn't work)

I'm currently using angular+nestjs and some nrwl nx-cli packages to be able to build my front-end and back-end using the angular-cli while sharing some common code using typescript paths. I could use angular libs, but I'd consider it overkill for sharing plain types.

If I could go back and do it all again, I'd probably go the npm module route if I could pay, and the parameters of the shared code are well defined. Otherwise I would of went with the mono-repo approach if allowable, as I would be able to remove most of the overhead with sharing code, and versioning.

Collapse
 
room_js profile image
JavaScript Room

Thank you for the extensive feedback! As I see, there is no perfect solution so far... I had to deal with git submodules in one of the previous projects and remember the experience was not that nice. Also tried to publish them to NPM in another project, it also comes with annoying workflow in a fast-changing codebase. So I have to really think about the way for the next project...

Collapse
 
bradtaniguchi profile image
Brad

I consider the mono-repo approach to provide the least friction when it comes to fast changing codebases. Throw in a CI solution, automated tests+typescript and you end up with a very easy to maintain workflow.

This is especially true for full-stack TS projects, where sharing types between the client+server-side code can create an extremely "refactor-able" code base.

I also should of mentioned you could use dedicated mono-repo tools like lerna even for smaller full-stack projects to help share code. Generally though, if you package code (using npm or other systems), you have to deal with versioning pains.

Thread Thread
 
mbrtn profile image
Ruslan Shashkov • Edited

With monorepo approach beware of what you importing and where. You occasionally can import a function or class that can be shared, but importing a big module e.g. TypeOrm. As a result, you can have megabytes of unneeded code in your frontend. That always going on with Next.js monorepo projects in my case :-)

Thread Thread
 
bradtaniguchi profile image
Brad

I've seen this, but importing typeorm to the client-side usually creates other unforeseen errors related to the fact typeorm isn't suppose to work there. (Same goes for any "back-end focused" library)

Optimally, un-used code in the project is dropped via tree shaking. This is only possible if the library in question works well with it.

There's also situations where libraries don't work well with bundlers (lots of native google-cloud packages don't work with webpack) and in these cases workarounds are a must.

Collapse
 
juliang profile image
Julian Garamendy

Hi! Thanks for posting this question.

I worked in projects that shared not just type definitions, but also code in a private non registry.

I think it works well for stable libraries, but it becomes annoying when the shared code (including type definitions) keeps changing.

For that we looked into yarn workspaces and lerna and planning to move the related projects to a single repository.

I'm curious to know what other teams do.

Collapse
 
room_js profile image
JavaScript Room

Thanks for the great feedback! Lerna could be an option actually, but also not that nice, as for me... I think it's a good point to improve about TS ecosystem. Since the popularity of TS grows, the problem will become more painful. We really need to find some good solution for this.

Collapse
 
travisvalenti profile image
Travis Valenti

Hi there,

You may want to look into the new 'Project References' feature.

typescriptlang.org/docs/handbook/p...

I can't say for sure exactly how it works, but based on the documentation you would be able to use dependencies (or 'references'), from typescript projects that are in sub-directories, or parent directories.

This means that either a multi-repo or mono-repo approach would work, provided the required dependencies exist where the config says they will.

The code is then compiled with the --build(-b) flag, which will compile all dependencies for use in the project.

This could be super useful, and I'm currently looking to implement it myself in an upcoming project where I'll have lots of shared types across about 5 TS projects.

Hope I could be of at least some help, in pointing out Project References, even if I'm not an expert.

Collapse
 
room_js profile image
JavaScript Room

Hi! That's a nice one, thanks for sharing it! I think it might be useful in some cases, but what concerns me here is that you have to reproduce the exact folder's structure every time in order to make it work. And moreover, you have to keep all of your projects consuming the types within the same File System, which is not the case for most of the modern apps taking place on different "machines" in the cloud.

Collapse
 
travisvalenti profile image
Travis Valenti

Both are good points. My intuition had told me that when deploying the applications, the adjacent projects wouldn't need to be copied alongside, as they would be compiled into the current project. I may be incorrect though.
Maybe in a few weeks when I'm dozens of hours deep into a project using Project References, I'll know a little more.

Thread Thread
 
room_js profile image
JavaScript Room

Yeah, definitely. It's always difficult to be sure of something before you spent hours on it. Please feel free to share the results of your journey!