nrwl / monorepo.tools

Your defacto guide on monorepos, and in depth feature comparisons of tooling solutions.
https://monorepo.tools
MIT License
290 stars 31 forks source link

Evaluation Criteria: Vendor lock-in, complexity & ease of learning #18

Closed bennycode closed 2 years ago

bennycode commented 2 years ago

Hello, I recently discovered Understanding Monorepos and it's a great resource for people who want to get an overview of the current JavaScript monorepo landscape.

I've been using monorepos with Lerna myself for the past 3 years (Example: wire-web-packages) and am currently examining Turborepo and nx. I noticed substantial differences compared to Lerna and I think these should be highlighted in the tools review.

What I really like about Lerna is how easy it is to get started. Lerna just sits on top of plain npm packages and gives you the possibility to link them together (lerna bootstrap) and run npm scripts in all of them simultaneously (e.g. lerna run --parallel build). On top of that, it can make use of Yarn Workspaces and streamline the release process (lerna publish). If you are already familiar with npm packages, you will quickly find your way around Lerna.

The same applies to Turborepo. You can actually use Turborepo in connection with Lerna and benefit from Turborepo's improved task scheduler (read more here). In a Turborepo monorepo, you will also have all your individual packages with their package.json files (see here). This makes it easy to switch tools if they are discontinued because you will still have standard npm packages.

As you can see, Lerna and Turborepo have an easy learning curve because you can achieve a lot if you are already familiar with standardized npm packages.

The situation is very different with nx. When starting out with a simple npx create-nx-workspace --preset=react you will get introduced to Nx Cloud and Nx CLI. Once the monorepo is then created, there are no more individual package.json files (it's actually the most requested nx feature). Instead you will be left with project.json files and/or a monolithic workspace.json file. That being said, the mental model is different from standard npm packages. You will also have to learn about executors, generators, and plugins.

On top of the different namings ("package.json" becomes "project.json", "scripts" become "targets", ...) there is a large dependency in the execution of commands. For example, linter packages like eslint are wrapped in packages like @nrwl/eslint-plugin-nx and should be executed through @nrwl/linter:eslint.

What was once:

"scripts": {
  "lint": "eslint --ext .js,.ts apps/yolo-e2e"
}

Now turns into:

"targets": {
  "lint": {
    "executor": "@nrwl/linter:eslint",
    "outputs": ["{options.outputFile}"],
    "options": {
      "lintFilePatterns": ["apps/yolo-e2e/**/*.{js,ts}"]
    }
  }
}

In addition, nx is very opinionated and comes with a .eslintrc.json that makes use of vendor-specific plugins such as plugin:@nrwl/nx/typescript. I see the benefit of it (sensible defaults, compatibility of tooling, bundled updates) but it also requires increased training time and makes it hard to move back to standard npm packages (which is why I see a vendor lock-in here).

I am currently missing my mentioned differences in the tools review. Complexity, vendor lock-in and ease of learning should become additional evaluation criteria as these can influence the decision for a monorepo system.

juristr commented 2 years ago

Thanks for the feedback, this is really appreciated! To answer, let me start with the last paragraph and the core part of the question :)

I am currently missing my mentioned differences in the tools review. Complexity, vendor lock-in, and ease of learning should become additional evaluation criteria as these can influence the decision for a monorepo system.

True, that's what we've been thinking about as well and are still doing so. We didn't want to include it in the "first version" because it isn't easy to incorporate in a clear, understandable way. Mainly because

Comparing it from a feature perspective is easy and what we did in this first version of the page. But we're definitely thinking about how to incorporate something like this. Maybe we could have something like a coordinate system space with x/y axis and simple/complex compared with monorepo size? And align the various tools along those axes 🤔


Since the rest of the things you mention are more directly towards Nx vs Lerna, let me go much more into those details.

I've been using monorepos with Lerna myself for the past 3 years (Example: wire-web-packages) and am currently examining Turborepo and nx. I noticed substantial differences compared to Lerna and I think these should be highlighted in the tools review. What I really like about Lerna is how easy it is to get started. Lerna just sits on top of plain npm packages and gives you the possibility to link them together (lerna bootstrap) and run npm scripts in all of them simultaneously (e.g. lerna run --parallel build).

Sure, there are quite some differences as many of the listed tools do much more than Lerna, Nx included :). But in your specific case, given you already worked a lot with Lerna, here would be some good resources:

I'd be curious in your opinion/journey and what you find most difficult. These are great opportunities to improve even more. Also, if you want to look into existing repos using Lerna + Nx (there are many), one that comes to my mind is the Storybook repo.

On top of that, it can make use of Yarn Workspaces and streamline the release process (lerna publish). If you are already familiar with npm packages, you will quickly find your way around Lerna. The same applies to Turborepo. You can actually use Turborepo in connection with Lerna and benefit from Turborepo's improved task scheduler (read more here).

Absolutely, very much the same applies for Nx. You can easily use Nx with an existing Yarn workspace for instance (and also with Lerna as explained before). We on purpose created a npx add-nx-to-monorepo for that scenario. With that you end up with pretty much the same experience as you'd have with Turborepo. Parallel runs, task scheduling, caching etc.

In a Turborepo monorepo, you will also have all your individual packages with their package.json files (see here). This makes it easy to switch tools if they are discontinued because you will still have standard npm packages.

That's totally possible with Nx as well. Again, using the npx add-nx-to-monorepo command. Nx is able to pick up npm scripts without any issue, so it would continue to work just as before (+ obviously the added benefits of Nx task runner etc..)

More details here: https://nx.dev/configuration/packagejson

The situation is very different with nx. When starting out with a simple npx create-nx-workspace --preset=react you will get introduced to Nx Cloud and Nx CLI.

Sure, this is our recommended setup for getting the "full" Nx experience. And of course, this one is much more opinionated based on our experience working with large scale monorepos. Nx Cloud is an optional opt-in (although we're about to make some changes there s.t. it is basically free for everyone 🤫). Sure, the Nx CLI is custom 😃. You could also just map it via npm scripts and keep using your monorepo as you were accustomed to. It'll be easier and more powerful though just using nx <target> <project> in the long run. But yeah, it's a change.

Once the monorepo is then created, there are no more individual package.json files (it's actually the most requested nx feature). Instead you will be left with project.json files and/or a monolithic workspace.json file.

This point is really a discussion about single version vs. multiple version policy. We at Nrwl strongly believe in the former. If you have multiple versions, you are free to install whatever packages you want inside the individual monorepo projects. That might give a lot of freedom but comes at the same time with a lot of potential issues in the long run as well. Such as version conflicts, tech debt of projects not being updated etc. As a monorepo grows, we've seen the single-policy approach to work much better in the long-run. But sure thing, as with many things, there are also downsides. It's a matter of tradeoffs.

Also, if you have a monorepo where it is mostly about code colocation with a couple of common utils, then having multiple package.json files is not that big of a problem and might actually make sense. In the monorepos we usually work, there are a lot of relationships among the packages, reaching from rich feature libraries implementing business logic to more loosely coupled utils and UI design libs that get consumed by all those business logic libs and applications in the workspace. In those scenarios single-policy has worked much better :)

That said, you can obviously use the multiple package.json approach with the add-to-nx-monorepo feature mentioned before, where you have Lerna or Yarn workspaces manage the installations of the packages.

That being said, the mental model is different from standard npm packages.

Yeah I think we should adjust that docs page for this one to make it more clear (docs are hard!).

You can have both, and the most common scenarios we see are:

To talk more about your specific use case of open source npm packages. You can totally have that:

npx create-nx-workspace@latest nxsimple --preset=core

This comes with just the Nx CLI (& required deps) + a Yarn workspaces setup. Oh, and Prettier, because we strongly believe no one should fight over code formatting issues. But works best for publishing npm packages :). Read more about it here.

You will also have to learn about executors, generators, and plugins.

You don't have to, but sure. If you want to go deeper, you might want to also get a deeper understanding of what an executor is, what a generator is and how you can extend the workspace even with your own plugins etc. But again, this is kinda optional and something in addition Nx gives. You could also just go with npm scripts as mentioned before :)

Sorry, this got pretty lengthy. But def let me know what you think.

bennycode commented 2 years ago

Thank you very much for your detailed answer, @juristr. That's definitely a plus for Nx!

I like the idea of adding a coordinate system space to show complexity and scope. This can help people who just need "something quick" or are looking for a "full fledged solution".

Your answer gave me another idea which is worth to be listed in the comparision table which is Professional Support. Some tools may not offer support for enterprises because they are hobby-based, so highlighting this is important.

Thank you also for pointing me to Nx's core setup. I totally missed that from your frontpage as I was just clicking on "Create Nx Workspace" which was bringing me directly to: npx create-nx-workspace --preset=ts

That being said, I am happy that Nx offers simple code colocation as well. I will give it a try! 👍

emmanuelbuah commented 2 years ago

This was helpful to read as well. I will add a few comments @juristr to @bennycode feedback to help improve the docs.

My biggest miss with Nx was that fact that it wasn't clear to me that if a lib is a non-buildable/publishable (has no package.json), it gets bundled into its consuming application during application build while a publishable lib isn't bundled but just referenced. In fact, it took sometime to pick up on that subtle difference and that helped clarify Nx's take on what you refer to as "typical company monorepo". Thus, if you mostly deploy applications, that is for you. Most node dev don't expect that behavior. In fact, building/bundling server-side code via webpack is not the default mental-mode wise if you come from the typescript camp and once they catch on, they end up taking it to the extreme assume they can bundle everyone (including node_module) which is not advisable for many other reasons.

In any case, I waned to share my Nx aha moment and hope it helps improve the docs.

@juristr I'd like your feedback on two questions

  1. Is it possible to have a lib as publishable while also making it "bundlable" in my app if needed?
  2. What's your recommendation in handling the "typical company monorepo" that has this issue

    From my memory one of the popular ORM package Prisma was not working well with this. When you npm install it'll generate a companion package and compiles a binary executable. Your code only imports the generated package instead of the original one. In this case nx wouldn't be able to detect it. Vercel's ncc bundler works amazingly well in this scenario. on Dec 5, 2021