| |

Yarn 04 ๐Ÿงถ Managing Dependencies and yarn.lock

โฑ๏ธ Estimated read: 10 min | |

Every dependency you add, upgrade, or remove triggers a two-file update: package.json records the intent (a version range like ^4.18.0), and yarn.lock records the reality (the exact version and integrity hash that was resolved). The manifest is what you write; the lockfile is what Yarn writes. Understanding this division is the difference between a project that installs reproducibly on every machine and one that drifts silently until something breaks in production.

Key point: package.json declares what you want; yarn.lock guarantees what you get. Never edit the lockfile by hand, always commit it to version control, and treat its changes as the authoritative record of every resolved dependency.


Why yarn.lock exists

The version drift problem. Without a lockfile, yarn install resolves version ranges against the registry at install time. If package.json says "express": "^4.18.0", a fresh install six months later might fetch 4.19.2 while another developer installed 4.18.1 last week. The code runs on different dependency trees, and the bug that appears on one machine but not the other has no obvious cause. The lockfile eliminates this by pinning every transitive dependencyโ€”not just your direct dependenciesโ€”to an exact version and integrity hash .

The transitive explosion problem. A mid-sized project with 20 direct dependencies can easily have 300 transitive dependencies. You cannot list them all in package.json, and you should not try. The lockfile captures the entire resolved tree, including the exact version of every dependency-of-a-dependency, so the full graph is reproducible without manual intervention .

The CI consistency problem. A CI runner starts from a clean checkout with no cached packages. If the lockfile is not committed, CI resolves version ranges independently and may install versions that differ from what developers tested locally. Committing yarn.lock ensures CI and local environments install byte-identical dependency trees .

The install behavior contract. Yarn’s install logic is strict: if yarn.lock exists and satisfies every range in package.json, the exact locked versions are installed and the lockfile is left unchanged. Only when the lockfile is missing or insufficient does Yarn resolve new versions and update the lockfile . The --frozen-lockfile flag goes further: it fails the install if the lockfile would need to change, making CI builds deterministic .

The trade-off. Committing yarn.lock to version control means merge conflicts when two branches modify dependencies. The resolution is always to regenerate, never to hand-merge. The cost is occasional conflict resolution; the benefit is every other install for the life of the project.


a. Adding, upgrading, and removing dependencies

Yarn’s dependency commands modify both package.json and yarn.lock in a single operation. The CLI is the only safe way to manage dependenciesโ€”editing package.json manually and running yarn install works, but it risks version range typos and inconsistent lockfile updates.

# Add a dependency to "dependencies"
yarn add express

# Add to "devDependencies"
yarn add --dev typescript

# Add a specific version
yarn add express@4.18.2

# Add a tag (e.g., beta)
yarn add react@beta

Each yarn add command writes the version range to package.json and resolves the exact version into yarn.lock . The range uses the caret (^) by default, allowing patch and minor updates but not major. For stricter control, specify a tilde (~1.2.3, patch updates only) or an exact version (1.2.3, no updates).

Upgrading follows the same pattern. yarn upgrade express upgrades within the range declared in package.json. To upgrade beyond the declared rangeโ€”for example, from ^4.18.0 to ^5.0.0โ€”you either run yarn upgrade express@latest (which may modify the range in package.json) or explicitly add the new version range .

# Upgrade within declared range
yarn upgrade express

# Upgrade to latest, potentially changing the range
yarn upgrade express@latest

# Upgrade to a specific version
yarn upgrade express@5.0.0

# Remove a dependency
yarn remove express

Removal deletes the entry from package.json and prunes the package and any dependencies that are no longer required from yarn.lock .


b. The lockfile: format, behavior, and integrity

The yarn.lock file is auto-generated and should never be edited directly. It contains a # THIS IS AN AUTOGENERATED FILE header, followed by entries for every resolved package. Each entry maps a version range to an exact version, a registry URL, and an integrity hash .

# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1

express@^4.18.0:
  version "4.18.2"
  resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#..."
  integrity sha512-...
  dependencies:
    accepts "~1.3.8"
    body-parser "1.20.1"

The dependencies block inside each entry lists the package’s own dependencies, which are also resolved and recorded. This recursive structure captures the entire tree without requiring a separate lockfile in every package’s directory. Yarn reads only the top-level yarn.lock during install .

The integrity hash is the security layer. If a registry serves a package whose content does not match the hash recorded in the lockfile, Yarn rejects the install. This prevents supply-chain attacks where a malicious actor replaces a package version with modified code .


c. Lockfile conflicts and regeneration

Merge conflicts in yarn.lock are common when two branches modify dependencies. The file is not designed for manual mergingโ€”resolving conflict markers by hand is error-prone and can produce a lockfile that no longer matches package.json.

The correct resolution is regeneration: resolve package.json first, then delete the conflicted yarn.lock, and run yarn install to generate a fresh lockfile from the merged manifest.

# After resolving package.json conflicts
git checkout --theirs yarn.lock   # or delete the file
yarn install                       # regenerate from package.json
git add yarn.lock

Yarn Classic (1.x) had built-in merge conflict handling that attempted to resolve lockfile conflicts during install, but this behavior is not guaranteed across versions and should not be relied upon . The safe approach in every case is to regenerate from the manifest.

In CI, yarn install --frozen-lockfile (or --immutable in Yarn Berry) prevents accidental lockfile updates. If the lockfile is out of sync with package.json, the install fails rather than silently resolving new versions .


Complete Example Session

# ============================================
# PART 1: ADDING A RUNTIME DEPENDENCY
# ============================================

yarn add express
# package.json: "express": "^4.18.2"
# yarn.lock: express@^4.18.2 resolved to 4.18.2
# ============================================
# PART 2: ADDING A DEV DEPENDENCY
# ============================================

yarn add --dev typescript
# package.json: "typescript": "^5.4.5" (devDependencies)
# yarn.lock: typescript@^5.4.5 resolved to 5.4.5
# ============================================
# PART 3: ADDING A SPECIFIC VERSION
# ============================================

yarn add lodash@4.17.21
# package.json: "lodash": "4.17.21" (exact, no caret)
# yarn.lock: lodash@4.17.21 resolved to 4.17.21
# ============================================
# PART 4: INSPECTING THE LOCKFILE
# ============================================

grep -A 3 'express@' yarn.lock
# express@^4.18.0:
#   version "4.18.2"
#   resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz"
#   integrity sha512-...
# ============================================
# PART 5: UPGRADING WITHIN RANGE
# ============================================

yarn upgrade express
# Upgrades to latest 4.x.x, lockfile updated
# package.json range unchanged: ^4.18.0
# ============================================
# PART 6: UPGRADING BEYOND RANGE
# ============================================

yarn upgrade express@latest
# May upgrade to 5.x.x
# package.json range may change to ^5.0.0
# ============================================
# PART 7: REMOVING A DEPENDENCY
# ============================================

yarn remove lodash
# package.json: lodash entry removed
# yarn.lock: lodash and its unique transitive deps pruned
# ============================================
# PART 8: FROZEN LOCKFILE FOR CI
# ============================================

yarn install --frozen-lockfile
# Fails if yarn.lock would need to change
# Ensures CI uses exactly the committed lockfile
# ============================================
# PART 9: REGENERATING AFTER CONFLICT
# ============================================

# Resolve package.json conflicts first
git checkout --theirs yarn.lock
yarn install
git add yarn.lock
# Fresh lockfile generated from merged package.json

The nine parts above cover the full lifecycle: adding runtime and dev dependencies, pinning exact versions, inspecting the lockfile, upgrading within and beyond ranges, removing packages, enforcing frozen lockfiles in CI, and regenerating after merge conflicts. Each command modifies both files, and the lockfile records the resolved outcome.


Quick Reference

Dependency Commands

CommandEffect
yarn add <pkg>Add to dependencies
yarn add --dev <pkg>Add to devDependencies
yarn add <pkg>@<version>Add exact version
yarn upgrade <pkg>Upgrade within declared range
yarn upgrade <pkg>@latestUpgrade to latest, may change range
yarn remove <pkg>Remove from manifest and lockfile

Lockfile Behavior

ScenarioYarn Action
Lockfile satisfies package.jsonInstall locked versions, no lockfile change
Lockfile missing or insufficientResolve new versions, update lockfile
--frozen-lockfileFail if lockfile would change
--no-lockfileDo not read or generate lockfile

Version Range Syntax

RangeMeaning
^1.2.3>=1.2.3 <2.0.0
~1.2.3>=1.2.3 <1.3.0
1.2.3Exactly 1.2.3
latestLatest stable (resolved at install)

Best Practices

โœ… Do This:

yarn add express
<!-- CLI updates both manifest and lockfile -->        # โœ…
git add yarn.lock
<!-- Commit lockfile to version control -->            # โœ…
yarn install --frozen-lockfile
<!-- CI enforces exact lockfile match -->             # โœ…
yarn upgrade express@latest
<!-- Explicit upgrade when changing major version -->  # โœ…

โŒ Don’t Do This:

# Edit yarn.lock manually
<!-- Lockfile is auto-generated -->                   # โŒ
# Ignore lockfile changes in PR
<!-- Dependency changes must be reviewed -->          # โŒ
# Delete lockfile "to fix" install issues
<!-- Regenerate from package.json instead -->         # โŒ
# Use --no-lockfile in CI
<!-- Produces non-reproducible builds -->             # โŒ

Common Pitfalls

PitfallWhy It HappensFix
Different versions across machinesLockfile not committedCommit yarn.lock to git
CI installs different versionsLockfile absent or out of syncUse --frozen-lockfile in CI
Merge conflict in lockfileTwo branches changed depsRegenerate: delete, yarn install
Manual lockfile edit breaks installSyntax error or stale integrity hashRevert, regenerate from package.json
Transitive dependency not pinnedLockfile missing entryRun yarn install to resolve and write
Upgrade not appliedRange constraint blocks upgradeUse @latest or update range explicitly

Real-World Examples

1. Add Runtime Dependency

yarn add express

2. Add Dev Dependency

yarn add --dev jest

3. Pin Exact Version

yarn add lodash@4.17.21

4. Upgrade Within Range

yarn upgrade axios

5. Upgrade to Latest

yarn upgrade axios@latest

6. Remove Dependency

yarn remove lodash

7. Frozen Install in CI

yarn install --frozen-lockfile

8. Regenerate After Conflict

git checkout --theirs yarn.lock && yarn install

9. Inspect Locked Version

grep -A 2 'axios@' yarn.lock

10. Check for Outdated Packages

yarn outdated

Visual

Two-File Update on Every Command

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  yarn add express                            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                   โ”‚
                   โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  package.json (intent)                       โ”‚
โ”‚                                              โ”‚
โ”‚  "dependencies": {                           โ”‚
โ”‚    "express": "^4.18.0"                      โ”‚
โ”‚  }                                           โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                   โ”‚
                   โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  yarn.lock (reality)                         โ”‚
โ”‚                                              โ”‚
โ”‚  express@^4.18.0:                            โ”‚
โ”‚    version "4.18.2"                          โ”‚
โ”‚    resolved "https://..."                    โ”‚
โ”‚    integrity sha512-...                      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The manifest records the range; the lockfile records the resolved truth. Both must be committed.

Install Decision Flow

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  yarn install                                โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                   โ”‚
                   โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  yarn.lock exists and satisfies              โ”‚
โ”‚  package.json ranges?                        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
           โ”‚ YES                   โ”‚ NO
           โ–ผ                       โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Install locked      โ”‚  โ”‚  Resolve new         โ”‚
โ”‚  versions exactly    โ”‚  โ”‚  versions, update    โ”‚
โ”‚  Lockfile unchanged  โ”‚  โ”‚  yarn.lock           โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
           โ”‚
           โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  --frozen-lockfile?                          โ”‚
โ”‚  โ”œโ”€ YES: fail if lockfile would change       โ”‚
โ”‚  โ””โ”€ NO:  allow lockfile update               โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The --frozen-lockfile flag is the CI safeguard. It converts “Yarn would update the lockfile” into a hard failure.


Summary

ItemValue
Manifest recordsVersion ranges (intent)
Lockfile recordsExact versions and hashes (reality)
Add commandyarn add <pkg>
Remove commandyarn remove <pkg>
Upgrade commandyarn upgrade <pkg>
CI install flag--frozen-lockfile
Lockfile editingNever; auto-generated only
Commit to VCSAlways
Conflict resolutionRegenerate from package.json
Lockfile scopeEntire transitive tree

Key takeaways:

  • Two files, two purposes. package.json declares version ranges; yarn.lock pins exact resolved versions and integrity hashes .
  • The CLI is the only safe editor. yarn add, upgrade, and remove update both files atomically. Manual edits risk inconsistency .
  • Commit the lockfile. Without it, installs are non-reproducible across machines and CI environments .
  • --frozen-lockfile enforces determinism. In CI, this flag fails the build if the lockfile would need to change, preventing silent version drift .
  • Merge conflicts require regeneration. Resolve package.json, delete the conflicted lockfile, and run yarn install. Never hand-merge conflict markers .
  • Integrity hashes are security. Yarn verifies every package against the hash recorded in the lockfile, rejecting tampered packages .

Remember: package.json is what you ask for; yarn.lock is what you get. The manifest travels through code review as a declaration of intent. The lockfile travels alongside it as the proof of resolution. Commit both, trust neither to manual editing, and let --frozen-lockfile enforce the contract in every CI run.

Was this helpful?


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!