4

JavaScript Monorepos in 2024: Legit or Sus?

 7 months ago
source link: https://keyholesoftware.com/javascript-monorepos-in-2024-legit-or-sus/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

JavaScript Monorepos in 2024: Legit or Sus?

JavaScript Monorepos in 2024: Legit or Sus?

I’ve been developing JavaScript through all of the major existential changes we’ve had. Browser wars? I remember those. Trying to make a complex application before Firebug? Oh yeah, tell me about it. Having to roll my own AJAX request by hand? Vividly remember.

Something that I experienced in all of my large JS projects before the last few years was an eventual point of no return, a metaphorical event horizon, beyond which the amount of time it took to build the code locally as well as on the CI/CD system was just simply too long.

All projects start fine, but as they grow and evolve and change over time, the amount of build time seems to creep up until it becomes inimical to deploying and testing changes in any reasonable time frame. Further, it becomes very difficult to onboard new developers as any change they make is not isolated, and must take into account all of the other code in the app. Granted, frameworks and libraries like React do help to some extent, but there are no clean-cut boundaries on the source code with different features, it always had to be by convention.

It was during a project a few years ago that I finally put my foot down and decided that something needed to be done. Researching how other architects were doing it, I came across JavaScript monorepos. I was familiar with the concept of monorepos from my research on how Google structures their code base (they have to repos, one for YouTube and one for everything else, no joke), but had never thought to apply that same principle to JavaScript. So I dove in head first, made a lot of mistakes, iterated, and finally got to a place where I feel comfortable sharing my lessons learned.

This blog post is not an extensive study, but it is enough to get you interested in a way to solve two common problems we all have (i.e. sluggish build times and inability to effectively onboard new devs due to lack of feature separation), and give you enough of a context around how I approached the problem to determine how you should proceed.

From The Beginning

The idea of a monorepo is that you have one (git) repo that partitions disparate features into their own isolated “sandbox.” No longer do you need to have a monolithic tower that contains everything, you can have little houses that are at a feature level, making it easier to…

  1. Detect what has changed between builds and give your CI/CD a hint about what it truly needs to build, and
  2. Understand the boundaries of a feature and only need to focus on that “project” folder when you’re working on that specific project.

There are, A LOT, of options, when it comes to JavaScript monorepos. I first ran across this link and quickly got overwhelmed. I narrowed it down to just those that are specific to JS. Then, I ran them through npmtrends. This gives a great way to evaluate the usage and growth (or decline) of a particular library, based on public data of how many downloads are happening daily. What I found was shocking:

JavaScript Monorepos. Worth it?

From that chart, it’s easy to see that it rules out the nearly flatline options. Note that the chart is linear, not logarithmic.

The three obvious contenders then become Turbo, NX, and Lerna. Looking at the trajectory, NX became more popular around Q2 in 2022, Lerna seems to be consistently used and neither growing nor shrinking and Turbo has slowly been rising. NX has continued to rise as well, which to me is a good sign.

Option 0: NPM Workspaces

One thing I didn’t mention in the previous section, NPM does offer a “native” method to set up a monorepo: NPM workspaces. It’s a section in the package.json that lets you segment out logical portions of the application into disparate features. So it fits the bill philosophically with what we want, though it does have its limitations. Namely, it’s a build-every-time mentality, so it doesn’t solve what I’ve been harping on.

This is an example of what it looks like in a package.json:

Screenshot-2024-02-14-141134-300x50.jpg.webp

And if you look at the structure of the repo:

ZG3-1.jpg.webp

You’ll notice that the feature “a” has its own package.json, where it can define its own dependencies. Then when you an npm install, it creates a symlink to the “a” package from the root:

Monorepos in JavaScript

This allows other features, such as b or c, to import features from a without having to have a sub-directory build copy of a, they can all share the same a. It’s very simple to set up, as you can see, but it builds all the time, so it’s not an option for the type of apps I work on (enterprise B2B, multi-year projects).

Option 1: NX

From the npmtrends chart above, I could see that NX would likely be the one I go with, but I wanted to give the others a fair shot as well as find out where the warts are on NX. NX is not simply a monorepo tool but an umbrella tool that covers many different facets of modern JS development:

JavaScript Monorepos: what do they look like?

It has workspace analysis, caching, code generation, test runner, etc. It’s all done through its plugin architecture, so it lets you roll your own if you’d like. It integrates with Vercel tooling too. So let’s dive in and see what a simple NX project looks like:

ZG6-1.jpg.webp

Ok, so it uses npx and generates a new library for testing if something is even, and we tell it where the project is located within the monorepo and how it can be imported by other projects. Ok, cool so far. What about the structure?

Monorepo in JavaScript coding it out

That is-even.ts is where I would put the code for my feature. Very simple use case, but I can see how I could extend it beyond that. Ok, I’m liking this so far. This seems like a good contender.

Option 2: Lerna

Lerna was the very first tooling I went with back in the day for JavaScript monorepos. It worked, well, sort of… granted it wasn’t the easiest to set up and get it integrated right with the CI/CD (Team City), but we got it working, eventually.

Then about a year later, we ran into issues with how long it took to detect package changes as well as builds. Then its long-term maintenance was moved over to NX, and Lerna now uses NX under the covers. So it’s a bit faster I suppose, but it’s not really getting used anymore. Bottom line, I don’t think it’s a great long-term option.

Option 3:Turbo

Turbo was the last option I considered. It’s yet another Vercel offering (they seem to be taking over modern JS development). It’s a build system written in Rust so it’s highly performant, intrigued me so far. Doesn’t seem to be as broad as NX, but that can be OK if it does its job very well.

Let’s run through the setup process:

JavaScript Monorepo: Lerna

Uses npx to run a process to build out the monorepo, ok, no difference there.

JavaScript Monorepo with Turbo

Similar in structure to how NX set it up, no difference there. What features does it have?

JavaScript Monorepos list

A subset of NX.

NX vs. Turbo

So now, I’m really learning towards NX, but I want to get the opinions of some other folks who have researched the topic. I would rather learn from their mistakes than have to make them myself, especially if they are willing to share their thoughts.

The first article I found was from August 2022, a little outdated, but still valid intel.

ZG11-1.jpg.webp

The summary was that NX was a better option in their opinion. Interesting, but to confirm, let’s look for a second opinion.

I found this article in August 2023, which concurred.

image-9.jpg.webp

Their rationale I liked better; it gave more context around what they would recommend in what scenario. Given that I work on enterprise B2B applications, it’s sounding more and more like NX is the way to go.

Giving NX a Try

The most fundamental decision, no takebacks, you need to make with NX is which type of JavaScript monorepo you want: package or integrated. What does that actually…mean? It depends on how you want to manage your dependencies. 😊

If you want each package in the JavaScript monorepo to have its own isolated set of dependencies, and not manage it at the root level, then you should go with package-based. If you want to manage all the dependencies and their versions at the root so that there is high consistency between the projects, go with integrated.

How I’ve done it on all projects in the past is the package mode, and to be honest, I’m not a fan. We ran into package size issues when deploying because we were using different versions of the same dependency, bloating the JS sent to the client. So, I’ll opt for integrated mode for now.

Then we have to pick which library we’re using, which is React. Use something else? No cap.

JavaScript Monorepo with NX

Then we pick the meta-framework we want to use. Let’s go with Next because #YOLO.

ZG14-1.jpg.webp

Then we get asked about which style we want. Let’s go with integrated.

JavaScript Monorepo with NX

Then we get a fun confirmation screen. So far, so good!

JavaScript Monorepo with NX

The structure is pretty simple, nothing too crazy.

ZG17-1.jpg.webp

Then I have to add my first project. I need to use the npx command like Option 1 above.

JavaScript Monorepo with NX

In this example, I’m adding a project for a fictitious auto dealership’s inventory page. It asks me what test runner I want, as well as which bundler. Let’s go with Vite, another Vercel offering.

ZG19-1.jpg.webp

So then I get to see my first project. Here’s the structure in VS Code:

ZG20-1.jpg.webp

Lots of stuff – try not to get overwhelmed. What you really want to look at is the src/lib. That’s where the components for my project will be located. The package.json in libs/auto-inventory should be pretty minimal and only include dependencies that I have also defined at the root level.

Moving on, let’s run a build to make sure it works.

JavaScript Monorepo with NX

Fast, but that’s to be expected – I don’t have anything in there. What does it output?

image-10.jpg.webp

Again, very minimal, just my one function.

Let’s try adding another project, this time it’ll be the financing screen.

ZG23-1.jpg.webp

Similar setup, nothing crazy. Now, head back to the root level tsconfig for the magic.

ZG24-1.jpg.webp

So we can now see that if I want one package to bring in code from another, I can use the @keyhole local dependency, and it’ll use the symlink to pull in the build version. Similar to what we saw with the NPM workspaces option, but built into TS this time.

Now, caveat, when should you have one package depend upon another in a monorepo? I would only recommend it if you have “core” or “common” level code, outside of a business function. Similar to what we do in microservices, there may be a shared library that everyone brings in, but it should be purely technical in nature, not specific to any one business vertical or functionality.

Why? Well, because if it is then you’re violating the bounded context nature of microservices, bleeding responsibility out to anyone in the near vicinity. This leads to the same issues we had in monoliths, not ideal especially as a project grows.

So, although we could set up every package to bring in functionality from every other, we should only do so if we have a legitimate use case. Additionally, that code should be platform or framework-level code, not business-use case specific. That said, let’s look at what a local dependency would look like.

ZG25-1.jpg.webp

NeXt Steps (Pun Intended)

At this point, I’m sold. I can easily see how I can extend the existing setup to have additional projects that live and grow independently of each other, can be built only when changed, and give me everything I wanted when I set out on this quest.

Two resources I’d point out to anyone who wants to learn more about NX as a JavaScript monorepo tool would be this tutorial on their site about React with an integrated monorepo and this tutorial on the meta-framework Next that I was using in the monorepo setup.

I am cautiously optimistic about NX and will recommend it to folks looking for an option in a similar context and requirements space to what I have. I’m eager to be able to use it on a larger project, and given that so many people in the community are successfully building on top of it, it’s a solid choice for a modern JavaScript monorepo tool in 2024.

Stay tuned for updates and additional content by following the Keyhole Dev Blog.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK