In the world of software development with JavaScript and TypeScript, it is crucial to manage the versions of packages used in your projects. This blog post will show the details of npm package management, specifically focusing on the caret (^
) and tilde (~
) symbols, as well as other versioning notations. Get ready to better understand how to manage your project's dependencies and establish reproducible builds with consistency in package versions!
Major, minor, patch
The npm ecosystem uses semantic versioning where version numbers typically consist of three parts, separated by dots: major.minor.patch
. These parts represent different types of changes made to the software, and are used to help developers and users understand the significance of a given version number.
Major version
The major version number is typically incremented when there are breaking changes to the software. It indicates that the software is no longer backwards compatible with previous versions, and users may need to make changes to their code in order to upgrade.
For instance, API contracts may change if parameters are modified:
Version 1.0.0
export function add(a: number, b: number): number {
return a + b;
}
Version 2.0.0
The API contract for Version 1.0.0 has been violated due to changes in the input parameter types for a
and b
. As a result, Version 2.0.0 must be introduced to address this breaking change:
export function add(a: string, b: string): number {
return parseInt(a, 10) + parseInt(b, 10);
}
Important: Following the npm guidelines, you can make breaking changes in your APIs as long as you are below version 1.0.0. When your package is in major version zero (e.g., 0.x.x
), the semver rules change slightly. In this case, breaking changes occur in 0.x.x
(minor-level), while new features and patches are implemented in 0.0.x
(patch-level).
Minor version
The minor version number is typically incremented when new features are added to the software, or when there are significant enhancements or improvements to existing features. These changes are usually backwards compatible, meaning that users can upgrade to the new version without having to make major changes to their code.
Version 1.0.0
export function add(a: number, b: number): number {
return a + b;
}
Version 1.1.0
The add
function has been updated to accept an indefinite amount of numbers, improving on its original implementation. This update does not affect prior API calls, as the function can still be used by providing just two numeric arguments, maintaining backwards compatibility.
In addition to the existing add
function, a new subtract
function has been added. It requires a minor release (1.1.0
) to introduce these new features:
export function add(...numbers: number[]): number {
return numbers.reduce((sum, current) => sum + current, 0);
}
export function subtract(a: number, b: number): number {
return a - b;
}
Patch version
The patch version number is typically incremented when bugs or security issues are fixed in the software. These changes are generally small, and do not involve major changes to the functionality or features of the software.
Let's assume we updated the subtract
function from version 1.1.0 to 1.2.0, adding the ability to also accept an indefinite amount of numbers as input:
Version 1.2.0
export function subtract(...numbers: number[]): number {
return numbers.reduce((sum, current) => sum - current, 0);
}
Suppose we've discovered that using 0
as the initial value for the subtract function was a mistake. Rather than subtracting from 0
, we want the function to subtract from the first number that is given as input. As a result, we'll need to release a patch version (1.2.1
) to fix this issue:
Version 1.2.1
export function subtract(...numbers: number[]): number {
return numbers.reduce((sum, current) => sum - current);
}
Understanding Caret (^
) and Tilde (~
)
When specifying dependencies in our package.json
file, the caret and tilde symbols have special significance in determining the range of acceptable package versions.
Video Tutorial
Caret (^
)
The caret symbol indicates that npm should restrict upgrades to patch or minor level updates, without allowing major version updates. For example, given "^5.0.2"
, npm will permit updates within the same major version (e.g., 5.1.0
, 5.0.3
), but not a jump to a new major version (e.g., 6.0.0
) when running npm update
.
Tilde (~
)
By altering the caret symbol to a tilde symbol, we would only receive updates at the patch level. If we were to use "~5.0.2"
, we would obtain version 5.0.3
if it were available, but not 5.1.0
.
Trick to remember
A helpful way to remember the difference between these symbols is by envisioning a house: the tilde is the floor, while the caret represents the rooftop, enabling you to reach higher version numbers:
Alternative Versioning Notations
If you're not concerned with specifying the precise minimum version and simply want to obtain the most recent version in a certain range, you can use alternative notations.
Latest Patch Version
To get the latest patch version, use the notation 5.0.x
. This will install the most recent version with the given major and minor numbers (e.g., 5.0.6
).
Latest Minor and Patch Versions
Similarly, to obtain the latest minor version, along with its most recent patch, use the notation 5.x.x
. This will permit updates within the same major version, but without restrictions on the minor version (e.g., 5.1.3
or 5.2.0
).
Updating Dependencies
It's essential to properly manage your dependency updates when changing version numbers in your package.json
file.
npm install
Executing the npm install
command installs a package's dependencies and any dependencies that these dependencies rely on (transitive dependencies). It also creates a package-lock.json
file if it doesn't exist.
Important: It is worth mentioning that npm install
will download compatible versions when there is no package-lock.json
file present. This means that the version you receive may not be the latest minor version, even if you've specified a version range using the caret symbol.
Example
If you've included typescript@^4.8.3
in the devDependencies
section of your package.json
file, you may expect to receive the latest minor version (currently 4.9.5
) when you run npm install
. However, you will receive the latest patch version (4.8.4
) as it is compatible with your version range. In order to obtain 4.9.5
, you will need to execute npm update
.
Dependency | npm install | npm update |
---|---|---|
typescript@~4.8.3 |
4.8.4 |
4.8.4 |
typescript@^4.8.3 |
4.8.4 |
4.9.5 |
typescript@4.8.X |
4.8.4 |
4.8.4 |
typescript@4.X.X |
4.9.5 |
4.9.5 |
npm update
To apply changes to the version numbers in your package.json
, use the npm update
command. Unlike npm install
, which only downloads a compatible version, npm update
will also update the package to the latest version that matches the specified range. Additionally, the package-lock.json
file will be updated.
Recording Exact Versions
You might wonder: "How can I determine which version was actually downloaded?" That's where the package-lock.json
file comes in. This file records the exact version retrieved during the initial installation, or any subsequent updates.
With a package-lock.json
file in place, future runs of npm install
will download the same build, ensuring reproducible results for your project's dependencies. This is particularly important in collaborative development environments, where multiple team members work on the same project, as it guarantees consistency across all installations.
Traffic Light Control
If you use the Yarn Package Manager instead of npm and run the command yarn upgrade-interactive --latest
, you'll see a color-coded legend that indicates the likelihood of a package update breaking your code:
- Patch updates are indicated in green, as they typically include backward-compatible fixes and pose the least risk of breaking your application
- Minor updates are indicated in yellow, as they typically involve more changes than patch updates and therefore carry a higher risk of causing issues if their package maintainers don't strictly adhere to semantic versioning guidelines
- Major updates are indicated in red, as they often include backward-incompatible changes that may require you to update your code in order to maintain compatibility
Screenshot
Conventional Commits
As we've learned, major version updates should only be issued when there are breaking changes. To draw attention to a breaking change, the Conventional Commits specification suggests using an exclamation mark (!
) after the type or scope in a commit message.
Example
refactor(api)!: use stringified numbers in calculations
Tools for publishing, such as Lerna (when using the --conventional-commit
flag), follow this convention when incrementing package versions and generating changelog files.
Publishing packages
Publishing packages with npm is a crucial step in sharing your code with the wider community. The npm CLI provides commands (npm version
and npm publish
) to assist with proper versioning and release of your own packages, in accordance with semantic versioning rules.
Video
npm version
The npm version command is used to update the version of a package in the package.json
file. If run in a Git repository, it will generate a Git tag and a new commit with a message that includes the version number.
npm publish
The npm publish command is used to publish the package to the registry, making it available for installation by others.
Release Workflow
The npm version
and npm publish
commands can be used to set up release workflows within the scripts section of a package.json
file:
{
"scripts": {
"preversion": "git checkout main && git pull && npm install && npm test && npm run build",
"release:major": "npm version major",
"release:minor": "npm version minor",
"release:patch": "npm version patch",
"postversion": "git push origin && git push origin --tags && npm publish --access public"
}
}
Conclusion
In this blog post, we've explored the significance of the caret and tilde symbols in npm versioning, as well as alternative versioning notations. We've also looked at how to use the npm update
command and the importance of the package-lock.json
file.
With a better understanding of npm package management and the tools available to manage dependency versions, you can ensure a stable, consistent, and reproducible development environment for your projects. Happy coding!
Top comments (5)
Hi @bennycode ,
I found this article while trying to learn about how npm uses the
@
symbol in package paths. I understand that the@
signifies TS, as opposed to JS code - is that documented anywhere I can use as a reference?Thanks
Usually the @ signals an organization. Where did you see it?
Example:
I'm told the difference is the non-@ is JavaScript code, while the @ version is TypeScript code. That is what I want to verify, and ideally find any documentation that states this naming convention.
That's a wrong assumption. Packages starting with an @ simply belong to an organization. Most communities (like Babel) create an organization now because that way you can be sure that the code (like "traverse") comes from them and not any other npm user. You can read more about organization scopes here: docs.npmjs.com/creating-an-organiz...
thanks