javascriptnpmdependenciesyarnpkgworkspace

Yarn Workspaces: How and where should I install my dependencies?


So, I know the tile isn't saying much, but I have the following issue. I am working on a project which uses yarn workspaces. I have 2 workspaces so far. One thing that confuses me is: should common dependencies stay in the root project (the one where workspaces is described)? I currently have some common(by common I mean they are used in both workspaces) dependencies in the root project and some(non common) in the workspace package.json.

I also have one more issue I need help with, but I feel it is connected with the previous one. Whenever I go to a workspace folder and I do yarn add <dep name> Some of my dependencies disappear. I have to go back to the root, delete yarn.lock and run yarn. Now all dependencies are installed and in the right place.

I feel like I am missing something. I have researched a lot, but wasn't able to find a good "best practices" example/article. I hope that some of you have been able to achieve the "right formula" and are going to be able to help me.


Solution

  • It has been a long time and this still hasn't been answered. However, I've not stopped searching for the answer and can share my opinion.

    If you want the TLDR: all modules, which affect the workspace should be in the root of the workspace and all project dependencies should be in each project's folder (so yeah potentially we would have one library in all project folders). Also, do your best to have the same version for node modules in all of your workspace packages. (if you use react 18.1.5 in one package it should be the same for all other workspaces)

    And now for the long story...

    First of all, things have changed, tools have evolved and many newly emerged tools help us with the task. With that in mind, I will share what I have done.

    Legacy yarn workspaces

    When I first started using workspaces they were not very refined and lerna was about to be deprecated. What I had to do then is be very careful about versioning and make sure that all projects use the same versions of a library. This is still recommended to this day, but we can work around it.

    Why was that necessary? Because of the way yarn is used to search for libraries. For example, imagine we have the following folder structure.

    node_modules (lodash v2)
    packages
    |_webapp-a (lodash v1)
      |_node_modules (lodash v1)
    |_webapp-b (lodash v2)
    

    I thought it would make sense that webapp-a here would use its v1 lodash node module, but I was completely wrong. Old versions of yarn workspaces and lerna would always use the highest found version for some reason. Recently that stopped being the case.

    New versions of yarn

    It is more or less the same story here, with the only difference being that you have more freedom to put global npm modules for each project, but it is not recommended and neither would I recommend doing this. Let me explain why it was a bad idea for me. Consider having the following folder structure:

    node_modules
    packages
    |_webapp-a (uses jQuery) // jokes on me nobody uses that in 2023
    |_webapp-b (uses jQuery)
    |_service-a (doesn't use jQuery)
    |_service-b (doesn't use jQuery)
    package.json (adds jQuery globally)
    

    Yes, this would work and even if the services a and b at some point decide to use jQuery with a higher version it wouldn’t be an issue. The biggest hurdle arrives when you want to package those projects for production. On my machine, I run yarn install and don’t care about the file size, because I want to be able to run all projects, but if I have a docker build only for service-a, when i use the global package.json would install some unnecessary libs, which would cost me build time and container disk space.

    Lerna

    I have to start by saying lerna is not a replacement for yarn workspaces. Lerna even uses yarn workspaces behind the scenes when possible. It also provides some very useful tools on top of that which help us run projects, track dependencies, give us better logs, etc. It is now maintained by Nrwl, which also has the NX build system. I use Lerna to run scripts in a group of projects and have better visibility over the console output.

    Build systems

    If you’ve decided to go the monorepo route, I would strongly suggest that you invest some time and adopt a build system. I have used NX and Bazel, which have proved very useful in different scenarios. For monorepos with loads of JS projects, I would use NX without a doubt. It has a very good documentation and provides countless tools to speed up our work. With NX all dependencies stay in the root of the project, but that is ok as we have an internal dependency graph that allows us to generate a package.json with all the dependencies needed by one monorepo project.