From 8abf58d2fa0cd36cfc5ac063fd4b32d43b9b7b8d Mon Sep 17 00:00:00 2001 From: Isaac Mann Date: Thu, 2 Feb 2023 13:12:07 -0500 Subject: [PATCH] docs(core): dependency management guide --- docs/generated/manifests/menus.json | 16 ++++++++++++++ docs/generated/manifests/nx.json | 20 ++++++++++++++++++ docs/map.json | 5 +++++ docs/shared/concepts/dependency-management.md | 21 +++++++++++++++++++ .../concepts/integrated-vs-package-based.md | 4 ++-- 5 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 docs/shared/concepts/dependency-management.md diff --git a/docs/generated/manifests/menus.json b/docs/generated/manifests/menus.json index f33e5e2825c22..ad90417771080 100644 --- a/docs/generated/manifests/menus.json +++ b/docs/generated/manifests/menus.json @@ -1018,6 +1018,14 @@ "children": [], "disableCollapsible": false }, + { + "name": "Dependency Management", + "path": "/more-concepts/dependency-management", + "id": "dependency-management", + "isExternal": false, + "children": [], + "disableCollapsible": false + }, { "name": "Using Nx at Enterprises", "path": "/more-concepts/monorepo-nx-enterprise", @@ -1157,6 +1165,14 @@ "children": [], "disableCollapsible": false }, + { + "name": "Dependency Management", + "path": "/more-concepts/dependency-management", + "id": "dependency-management", + "isExternal": false, + "children": [], + "disableCollapsible": false + }, { "name": "Using Nx at Enterprises", "path": "/more-concepts/monorepo-nx-enterprise", diff --git a/docs/generated/manifests/nx.json b/docs/generated/manifests/nx.json index f8c41114c4f88..21b7b4dd6c83e 100644 --- a/docs/generated/manifests/nx.json +++ b/docs/generated/manifests/nx.json @@ -1265,6 +1265,16 @@ "path": "/more-concepts/why-monorepos", "tags": [] }, + { + "id": "dependency-management", + "name": "Dependency Management", + "description": "", + "file": "shared/concepts/dependency-management", + "itemList": [], + "isExternal": false, + "path": "/more-concepts/dependency-management", + "tags": [] + }, { "id": "monorepo-nx-enterprise", "name": "Using Nx at Enterprises", @@ -1440,6 +1450,16 @@ "path": "/more-concepts/why-monorepos", "tags": [] }, + "/more-concepts/dependency-management": { + "id": "dependency-management", + "name": "Dependency Management", + "description": "", + "file": "shared/concepts/dependency-management", + "itemList": [], + "isExternal": false, + "path": "/more-concepts/dependency-management", + "tags": [] + }, "/more-concepts/monorepo-nx-enterprise": { "id": "monorepo-nx-enterprise", "name": "Using Nx at Enterprises", diff --git a/docs/map.json b/docs/map.json index f863d60f00330..540b5c58e74c4 100644 --- a/docs/map.json +++ b/docs/map.json @@ -403,6 +403,11 @@ "id": "why-monorepos", "file": "shared/guides/why-monorepos" }, + { + "name": "Dependency Management", + "id": "dependency-management", + "file": "shared/concepts/dependency-management" + }, { "name": "Using Nx at Enterprises", "id": "monorepo-nx-enterprise", diff --git a/docs/shared/concepts/dependency-management.md b/docs/shared/concepts/dependency-management.md new file mode 100644 index 0000000000000..603720c1e543d --- /dev/null +++ b/docs/shared/concepts/dependency-management.md @@ -0,0 +1,21 @@ +# Dependency Management Strategies + +Where do you define the dependencies of your projects? Do you have a single `package.json` file at the root or a separate `package.json` for each project? What are the pros and cons of each approach? + +## Independently Maintained Dependencies + +Without tools like Nx, this is the only dependency maintenance strategy possible. Each project is responsible for defining which dependencies to use during development and which dependencies to bundle with the deployed artifact. For javascript, these dependencies are specified in a separate `package.json` file for each project. Each project's build command is responsible for bundling the packages in the `dependencies` section into the final build artifact. Typically packages are installed across the repo using yarn/npm workspaces. + +This strategy makes it very easy to have different versions of the same dependency used on different projects. This seems helpful because the code for each project exists in the same folder structure, but that code can't really be shared any more. If project1 and project2 use two different versions of React, what version of React will their shared code be written against? No matter how you answer, there will be bugs introduced in the system and often these bugs occur at runtime and are very difficult to diagnose. + +Another potential problem with this approach is that sometimes a developer may have one version of a dependency installed in the root `node_modules` and a different version specified in the project's `package.json`. This can lead to a scenario where the app works correctly on the developer's machine but fails in production with the bundled version of the dependency. This is once again a bug that is difficult to diagnose. + +## Single Version Policy + +With this strategy, developers define all dependencies in the root `package.json` file. This enforces a single version for all dependencies across the codebase, which avoids the problems listed above. Individual projects may still have `package.json` files, but they are used only for the metadata defined there, not as a way of defining the dependencies of that project. + +If there are React and Angular apps in the same repo, we don't want both frameworks bundled in the build artifacts of the individual apps. That's why the plugins Nx provides come with executors that use Nx's graph of dependencies to automatically populate the `dependencies` section of the individual `package.json` files in the build output and pre-populate a lock file for you. This enables your build artifacts to only contain the dependencies that are actually used by that app. As soon as a developer removes the last usage of a particular dependency, that dependency will be removed from the bundle. + +The primary concern people have with this approach is that of coordinating updates. If two different teams are working on React apps in the same repo, they'll need to agree about when to upgrade React across the repo. This is a valid concern and beyond the scope of Nx to solve. If the developers can't cooperate, they should probably work in separate repos. On the other hand, if the teams can agree, it becomes much less work to upgrade the whole repo at the same time rather than performing that same upgrade process multiple times spread out over months or years. Performing the same code manipulation 100 places all at once is much easier than remembering how to perform that code manipulation 100 different times spread out over a year. + +Consult the Nx executor's reference page to find options for populating dependencies and a lock file. If you would like to use Nx's graph to populate the dependencies of a project in your own scripts or custom executor, read about how to [prepare applications for deployment via CI](/recipes/ci/ci-deployment). diff --git a/docs/shared/concepts/integrated-vs-package-based.md b/docs/shared/concepts/integrated-vs-package-based.md index daa42323d18f2..0f19af2482df7 100644 --- a/docs/shared/concepts/integrated-vs-package-based.md +++ b/docs/shared/concepts/integrated-vs-package-based.md @@ -13,7 +13,7 @@ There are two styles of monorepos that you can build with Nx: integrated repos a ## Package-Based Repos -A package-based repo is a collection of packages that depend on each other via `package.json` files and nested `node_modules`. With this setup, you typically have different dependencies for each project. Build tools like Jest and Webpack work as usual, since everything is resolved as if each package was in a separate repo and all of its dependencies were published to npm. Moving an existing package into a package-based repo is very easy since you generally leave that package's existing build tooling untouched. Creating a new package inside the repo is just as difficult as spinning up a new repo since you have to create all the build tooling from scratch. +A package-based repo is a collection of packages that depend on each other via `package.json` files and nested `node_modules`. With this setup, you typically have [different dependencies for each project](/more-concepts/dependency-management). Build tools like Jest and Webpack work as usual, since everything is resolved as if each package was in a separate repo and all of its dependencies were published to npm. Moving an existing package into a package-based repo is very easy since you generally leave that package's existing build tooling untouched. Creating a new package inside the repo is just as difficult as spinning up a new repo since you have to create all the build tooling from scratch. Lerna, Yarn, Lage, [Turborepo](/more-concepts/turbo-and-nx) and Nx (without plugins) support this style. @@ -23,7 +23,7 @@ Lerna, Yarn, Lage, [Turborepo](/more-concepts/turbo-and-nx) and Nx (without plug ## Integrated Repos -An integrated repo contains projects that depend on each other through standard import statements. There is typically a single version of every dependency defined at the root. Sometimes build tools like Jest and Webpack need to be wrapped to work correctly. It's harder to add an existing package to this repo style because the build tooling for that package may need to be modified. It's straightforward to add a brand-new project to the repo because all the tooling decisions have already been made. +An integrated repo contains projects that depend on each other through standard import statements. There is typically a [single version of every dependency](/more-concepts/dependency-management) defined at the root. Sometimes build tools like Jest and Webpack need to be wrapped to work correctly. It's harder to add an existing package to this repo style because the build tooling for that package may need to be modified. It's straightforward to add a brand-new project to the repo because all the tooling decisions have already been made. Bazel and Nx (with plugins) support this style.