Production vs development dependencies
This lesson preview is part of the Bundling and Automation in Monorepos course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.
Get unlimited access to Bundling and Automation in Monorepos, plus 90+ \newline books, guides and courses with the \newline Pro subscription.

[00:00 - 00:14] A question that comes up in normal everyday development when we add dependencies is should this dependency be added as a production dependency or as a dev dependency? The answer, as in most things, is that it depends.
[00:15 - 00:21] But let's go over some examples. I'm going to create a directory for this called dependencies-example.
[00:22 - 00:47] I'm going to cd into it and then I'm going to run "pnpm init" to create a simple package.json. Then I'll run "pnpm install" which will just set up the "packageManager" field for us. And finally I'm going to add two devDependencies: "prettier" and "pretty-quick". When I run "pnpm list", it's going to give me those two dependencies as the only dependencies that I have.
[00:48 - 00:56] However, I also want to examine our indirect dependencies. So I'm going to run "pnpm list --depth=100".
[00:57 - 01:20] And then we can see that prettier doesn't have any extra dependencies, but pretty-quick has quite a few of them. If we go and look at pretty-quick's package.json, we're going to see that it has those seven top-level production dependencies that we saw listed, but it also has a lot of dev dependencies.
[01:21 - 01:33] However, none of those were installed in our repo. Why is that? When you install a package from npm, only its production dependencies will be added to your own project.
[01:34 - 01:50] That's one of the reasons why public packages need to be careful about what they add to their production dependencies. By convention, production dependencies on npm packages should only contain code that is strictly necessary for that package to work.
[01:51 - 02:04] However, when we think about our own projects, especially in a monorepo context, there aren't clear rules that we can follow. Instead, there is a set of guiding principles.
[02:05 - 02:23] In general, we want anything that is used directly in any production code to be a production dependencies. While stuff that's only used for running tests, building, bundling, linting, we want that as a dev dependency.
[02:24 - 02:57] I'm going to go to our monorepo root, and I'm going to show you code that's taken from later in this course. This code is something that we're going to build later, but I'm using it here just because it helps us illustrate the type of decisions we have to make when we choose whether something should be a dev dependency or a production dependency. I'm going to print all package.json files in the monorepo as a tree, and open that in vim so we can look at it.
[02:58 - 03:26] We have a package.json in the root of our monorepo that defines our dependencies. We have our three applications, each defining its own dependencies, and we have two shared packages: one that's a config package and one that's a UI package. The UI package contains shared React components that are used by the frontend-vite and frontend-nextjs apps.
[03:27 - 03:35] We're first going to look at the root monorepo package.json. In here we should only have dev dependencies.
[03:36 - 03:51] We don't want any of our applications to be using a package exposed from our root. What this leaves us with is that the root package.json should only contain tooling that applies to the whole monorepo.
[03:52 - 04:04] In this case we only have prettier and a plugin of it. It makes sense to have prettier in the root of the monorepo, because it applies uniform styling throughout the whole project.
[04:05 - 04:19] Back to the command line. I'm going to print the list of package.json files again because it makes it easier to reference. And the next one I'm going to open is apps/frontend-nextjs/package.json.
[04:20 - 04:34] Here in production dependencies we have Next.js. It's a production dependency because next can be used as a server for your application when you need to run it inside a Docker container.
[04:35 - 04:57] There are ways to build a next app that don't require Next.js to be a production dependency, but as a matter of consensus, next is usually put alongside production dependencies, even when you build it as a single page app. The next two production dependencies that we have are "react" and "react-dom".
[04:58 - 05:16] Again, you can build your application in a way where your bundler would completely consume these packages and you would never actually require them in production. But as a matter of community consensus, react and react-dom are usually considered production dependencies.
[05:17 - 05:35] The last one that we want to consider is "@monorepo/ui" - it's our own shared code that exports React components. I have set this one as a production dependency because our production code has an import that uses this package.
[05:36 - 05:59] We import a button component from it. So again, as a rule of thumb, even though this package would be consumed by a bundler, I want to have it in production dependencies because it's required by our production code. From the dev dependencies, I want to draw attention to another internal package - "@monorepo/config".
[06:00 - 06:17] This package is used to provide a shared TypeScript configuration, because the @monorepo/config package is never imported in our production code, I consider it a dev dependency. Next, let's look at our frontend-vite app.
[06:18 - 06:37] Here things are a little bit different - for production dependencies, we only have React and our @monorepo/ui package. However, Vite is just a bundler, and by default, vite-create-app puts vite as a dev dependency.
[06:38 - 06:46] Unlike next, it's never used in production to run code. So that's why the convention is that it goes into devDependencies.
[06:47 - 07:03] The final one that we are going to look at is our server-express app here, the distinction is actually clearer. Our server must import express in runtime, so express must be a production dependency.
[07:04 - 07:20] However, all other dependencies are just used to build TypeScript, validate, and test, and therefore, we put those in our devDependencies. It's important to consider that these rules are only guidelines.
[07:21 - 07:52] In practice, nothing is stopping you from putting all your code in production dependencies. It will still run, and depending on your bundler, it might not even affect performance in terms of bundle size. However, I suggest you structure your dependencies with a clear separation between production dependencies, meaning things that either by convention are considered production dependencies or that are actually required in your code.
[07:53 - 08:16] Versus devDependencies, which is everything supporting us that allows you to do your day-to-day development. Other than conveying the intent of a given dependency, another good reason to separate dev from prod dependencies is that you can force pnpm to install only production dependencies.
[08:17 - 08:35] If we do this in our example repo, we're going to see that installing only production dependencies removes over 600 dependencies from our project. This can be used to optimize Docker setup, and it's something that we are going to be looking at later in the course.
[08:36 - 08:46] And with that, we can now make informed decisions about when to use production versus devDependencies.