Bugsy explains
Semantic versioning
What 2.4.1 really promises, why ^ and ~ let in different updates, and what the keys you'll meet most in package.json are for.
6 min 3 toys
- packages
- javascript
- tooling
Open any package.json and you'll see versions like 2.4.1. A package's own version always has three numbers, not a single "version 7". Why three?
Because a version isn't a counter. It's a promise to everyone who uses your code: it tells them what changed and whether upgrading is safe. That idea is called semantic versioning, or semver.
Three numbers, three promises
MAJOR goes up when you break something: rename a function, remove an option, change what a call returns. Users have to change their code to upgrade.
MINOR goes up when you add a feature without breaking anything. Old code keeps working.
PATCH goes up when you fix a bug and nothing else changes for users.
Bumping a number resets the numbers after it to 0. A new feature on 2.4.1 makes 2.5.0, not 2.5.1.
Try it
Ranges: which updates do you accept?
In package.json you rarely pin one version. You write a range, a rule for which versions are fine. When you run npm install some-package, npm saves the latest release with a ^ in front, like ^1.4.2.
1.4.2 (exact) means that version and nothing else.
^1.4.2 (caret) means anything from 1.4.2 up to, but not including, 2.0.0. New features and fixes come in, a new MAJOR never does.
~1.4.2 (tilde) means from 1.4.2 up to, but not including, 1.5.0. Bug fixes only.
Caret gets careful below 1.0.0. Semver itself promises nothing there, so npm follows a convention: on 0.x the MINOR acts like MAJOR and may break things, while PATCH carries features and fixes. Caret follows it: ^0.2.3 means >=0.2.3 <0.3.0, and ^0.0.3 allows only 0.0.3.
Try it
Quick check
Why the lock file exists
Ranges have a catch. Say you install with ^1.4.2 today and get 1.4.2. Next month 1.5.0 comes out. Without a lock file, the teammate who installs after that gets 1.5.0. Same package.json, different code.
The lock file, package-lock.json, fixes that. It records the exact version of every package in the tree, including the dependencies of your dependencies, so every machine installs the same thing.
So commit it. On servers and in CI (the machine that tests every push), install with npm ci: it installs exactly what the lock file says, and if the lock file and package.json disagree it stops with an error instead of quietly updating.
Your lock file pins your own project only. People who install your package get versions picked from your ranges and their own lock file, because npm ignores the package-lock.json inside a dependency.
git add package.json package-lock.jsonnpm ci
On your machine: commit both files. On CI: npm ci.
A tour of package.json
Every key in package.json answers one question about your project. Here's a small React component library. Pick a key to see what it's for.
Try it
Quick check
Boss question
Put it all together.
Quick check
Done reading?
Solve 3 more quick checks to finish.