Skip to content

Instantly share code, notes, and snippets.

@exonomyapp
Created September 24, 2024 11:36
Show Gist options
  • Save exonomyapp/b36fac377d8d7cb10f6c0b88189e1181 to your computer and use it in GitHub Desktop.
Save exonomyapp/b36fac377d8d7cb10f6c0b88189e1181 to your computer and use it in GitHub Desktop.

When we install packages using npm i <package> --save-dev, we are specifically telling npm to install our package as a development dependency.:

--save-dev parameter:

  • This adds our package to the devDependencies section in the package.json file.
  • Usage: This means the package is intended only for development purposes, not for production builds. When deploying the application to production (with npm install --production or similar), packages listed under devDependencies won't be installed.
  • Example: Cypress, as a testing framework, is usually not needed in production environments, so it makes sense to include it in the devDependencies section.

Other common parameters:

  • No parameter (npm i <package>): This installs our package and adds it to the dependencies section in package.json, meaning it will be installed in both development and production environments. This is less ideal, for example, for a testing tool like Cypress.
  • --global (npm i -g <package>): This installs our package globally on our system, making it accessible from any project without having to install it locally within a project folder. It doesn't modify the package.json and won’t be linked to the specific project.

Other package managers:

  • Yarn: Similar to npm, Yarn supports:

    • yarn add <package> --dev (equivalent to --save-dev in npm).
    • yarn add <package> to add Cypress to production dependencies.
    • yarn global add <package> for global installation.
  • pnpm: Another package manager with similar options:

    • pnpm add <package> --save-dev for development dependencies.
    • pnpm add <package> for production.
    • pnpm add -g <package> for global installation.

In summary, the --save-dev parameter ensures our package is installed only as a development dependency, which is typical, for example, for testing tools such as Cypress. Other package managers like Yarn and pnpm offer nearly identical functionality for specifying where a package should be installed (production vs. development).

Other package managers such as Yarn, pnpm, Bun, or Rush offer features or options for package installation that npm doesn't provide. The following comparison focuses on unique offerings of each package manager:

1. Yarn:

Yarn has some features and flags that differ from npm, giving developers more control or improved workflows:

  • Offline cache: Yarn can install packages without needing to hit the network if they have been installed before. This is not natively supported by npm, though npm has caching mechanisms (like npm ci for a clean install using the package-lock.json), but it doesn’t work entirely offline.

    • Example: If we install Cypress once, Yarn caches it locally, and we can reinstall it offline using the cache without needing internet access.
  • Workspaces: Yarn has native support for workspaces, making it easy to manage monorepos (projects with multiple packages) and share dependencies between them. npm added support for workspaces later, but Yarn’s workspaces are considered more mature and widely used.

  • Selective versions: Yarn allows installing a specific version of a package using the resolutions field in package.json. This feature is particularly useful in scenarios where we want to force a particular subdependency version across the project. npm lacks an easy native mechanism for this, though npm-force-resolutions can be used for similar functionality. When we talk about forcing a specific subdependency version across the project in Yarn, we're referring to any package that is a dependency of another package (i.e., not a direct dependency listed in package.json but a nested dependency that comes from another package). Yarn offers a feature called resolutions, which allows us to force a specific version of a subdependency, even if it conflicts with what the package’s author specified. This is useful in scenarios where we are using a package (e.g., Cypress) that depends on a specific version of another package (let's say a package like chalk), but we want to override that subdependency with a different version (perhaps due to a bug in the version that the parent package requests). In this case, the technology whose version we want to force is the subdependency package itself (like chalk in this example). So, the focus is on a nested package in the dependency tree rather than a package we directly install.

Example:

{
  "resolutions": {
    "chalk": "4.1.0"
  }
}

This forces every version of chalk (even if it's required by other dependencies) to resolve to version 4.1.0.

  • Zero-installs: This allows projects to be distributed with their node_modules folder already included via source control. When the project is cloned, no yarn install is necessary, unlike npm which requires a clean installation. Yarn's Zero-installs means that we store all the project dependencies (in the node_modules folder) directly within the repository, alongside the Yarn cache files. This allows the project to be ready to run without needing to execute yarn install after cloning. Let’s address how this interacts with the build and deployment process:

    • Excluding node_modules in the build: Yes, when using Yarn Zero-installs, you can still exclude the node_modules folder from the final build (for example, when preparing a production deployment). The node_modules folder is used for development and testing, but when building the final app, you would typically bundle the code (e.g., using Webpack, Rollup, etc.), and the node_modules would not be included in the distributed build.

    • Is node_modules included in the repository?:

      • Yes: In the Zero-installs pattern, node_modules and the .yarn/cache files are stored in the repo itself. This makes it so that after cloning the repository, we don’t need to run yarn install to have the dependencies available.
    • Is node_modules included in the final app distributed?:

      • No: Typically, the node_modules folder is not included in the build artifact that is distributed. The final build might include only the necessary bundled code. For example, in a Nuxt3 project, we might use Webpack or Vite to bundle everything into a few files, and these are the files we would deploy to production. The node_modules would stay out of the deployment or build artifact.

Summary:

  1. Subdependency version forcing: Refers to forcing the version of a nested package (subdependency) across the entire project via Yarn’s resolutions feature.
  2. Zero-installs: The node_modules folder is included in the repo for development purposes, but it can (and typically should) be excluded from the final build artifact when distributing the app. This keeps the build lean and focused only on the bundled code required to run the app.

2. pnpm:

pnpm is known for its disk space efficiency and some unique behaviors:

  • Symlinked node_modules: Unlike npm or Yarn, pnpm creates a global store of packages and symlinks dependencies to the project folder, saving disk space and improving installation speed. This approach differs fundamentally from npm, where each project has a fully installed node_modules directory.

    • For example, if multiple projects depend on Cypress, pnpm uses symlinks to avoid multiple copies of the same package, reducing redundancy.
  • Strictness: pnpm is more strict in enforcing dependency rules. For example, it ensures that packages can only access dependencies they explicitly declare, preventing accidental dependency hoisting issues seen in npm or Yarn.

  • Fast install via hard linking: Like Yarn’s offline mode, pnpm also caches packages globally but installs them much faster due to hard links from the global store rather than duplicating files. This allows quick installations across projects that share dependencies.

3. Other Package Managers:

  • Bun (an emerging package manager):

    • Native performance: Bun is built in a highly optimized environment (Zig), offering much faster package installations than npm, Yarn, or pnpm.
    • Out-of-the-box support for TypeScript: Bun also provides a built-in runtime for TypeScript and other modern languages, which can reduce the complexity of setting up and configuring builds compared to npm.
  • Rush: Focuses on large monorepos and dependency management in large-scale projects. It provides enhanced monorepo support, which can be more powerful than npm or even Yarn Workspaces for enterprise-level projects.

Summary:

  • Yarn offers offline caching, more mature workspace support, and features like selective version resolution.
  • pnpm stands out for its efficient disk usage, symlinked node_modules, and strict dependency resolution.
  • npm has caught up in areas like workspaces but lacks some of the fine-tuning features offered by Yarn and pnpm.

In short, Yarn and pnpm provide features like more efficient installations (pnpm), better monorepo management (Yarn), and performance improvements (pnpm, Bun), which npm lacks or implements differently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment