When you start of trying to learn to write code in javascript, you probably begin with a tutorial telling you to run npm install
or yarn install
. After some confused Googling, you may have gotten npm
installed and been on your way. However, the learning tends to stop around there. There are a few concepts that I think are important to know if you are working in the JavaScript world.
Let’s start at the beginning. What is node and why are we talking about node packages? Node is a javascript runtime; which is another way of saying it is a spot where you can run your javascript code. Other javascript runtimes that you are likely to encounter are the browser, or maybe the new kid on the block, Deno.
Why does this matter? Javascript is javascript, right? Why does it matter if you’re just writing code in this programming language? Well, if you are writing javascript code to work in the browser, then you might want things like the window a
nd you might not need things seen on a server like the filesystem
. Throw in the fact that javascript (and thus these different runtimes), keeps adding new features, and you’ll start to encounter things that don’t work together.
What Are Packages
In its simplest form, packages are sets of files that you may want to download and use to accomplish something in your own project. We’ll call a package that you want a “dependency.” In npm
, when you install a package, the npm
tool downloads that set of files, as well as any dependencies that the creators of your dependency relied on. Think of it this way. Suppose that you want to use two magical tools, cat-me
and dogyears
. You can install them using npm install cat-me dogyears
. You will now have a node_modules folder looking like:
node_modules
├──cat_me
│ └──node_modules
└──dogyears
└──node_modules
Package Versions
It starts getting more complicated because each of those dependencies has a version. And cat_me may use some of the same dependencies as dogyears
, but those sub-dependencies may have different versions. npm
is able to save the right version for each and keep each of those sub-dependencies happily using their preferred version.
node_modules
├──cat_me (version 1.0.3)
│ └──node_modules
│ └── counter (version 2)
└──dogyears
└──node_modules
└── counter (version 2.5)
Node packages use a versioning system called semantic versioning. This versioning uses two decimal places and follows a process of incrementing the version number after each update. The docs will show the format like MAJOR.MINOR.PATCH, but i think that it is easier to see it.
1.0.0 <= Our first version
1.0.1 <= small bug fix (aka "patch release"
1.0.2 <= second small bug fix
1.1.0 <= new feature without breaking changes ( aka "minor" release)
2.0.0 <= big new feature with breaking changes ( aka "major" release)
Installing Packages
To install a package with npm
, you can use the command npm install
. For example, npm install dogyears
. This will by default install the latest version. You will then see within your package.json file a new dependency listed. which right now might look like
"dependencies": {
"dogyears": "^1.1.6"
}
If someone else now checks out your repo with this dependency, they can run npm install
. That carrot (^
) character means that npm
will allow the user to install the latest version of dogyears
as long as the version isn’t a breaking change (e.g. anything less than 2.0.0).1
When you install with npm
, it will generate a package.lock
file. This file should be checked into your version control (e.g. github), and it will keep a record of the last version of the packages installed for your dependency and their sub-dependencies.
If you want to start updating packages, then you can run the npm update
command. This will update all of your dependencies, as well as their sub-dependencies…. with the caveat that versions can have something like that ^ to limit the version.
If you want to install a specific version of one of your packages, you can use the @
character. For instance, npm install dogyears@1.1.6
will install that package and only allow 1.1.6 to be installed.
Overriding Versions of Your Sub-dependencies
Sometimes, packages have security issues that mean that they should be updated. This is straightforward for direct dependencies. We’ve covered installing specific versions or running the update
command.
A drawback of using a package that someone else maintains, is that they may not be updating their dependencies to fix those vulnerabilities very quickly. It is possible to override the versions that they are using by adding an “overrides” section to your package.json
. This would looks something like
{
"overrides": {
"dogyears": {
"helper": "1.1.7"
}
}
}
and now the helper package in dogyears
will be installed as version 1.1.7 instead of the 1.1.5 they might have indicated in their own package.json.
DevDependencies vs. Dependencies
We’ll end this week’s article with the difference between devDependencies and Dependencies. The short of it is, sometimes you install dependencies that you only need while working on your code. Perhaps these are testing tools like jest
. These dependencies don’t need to be installed to run the code; they just need to be installed for running tests. When you run npm install
, you install both types of dependencies. When you are running an install command to build your application in CI (e.g. Jenkins), you don’t want to install these dev dependencies. If you want a dependency to only be a devDependency, then you can install it by adding the —save-dev flag (e.g. `npm install <package-name> --save-dev
).
When you get ready to build your project, you can then run npm ci to install all of the required dependencies and skip the dev dependencies.2
Wrap up
This covers a lot of the package version questions that I stumbled through early on. There’s plenty of rabbit holes to stumble through.
Next time, we’ll talk a little more about working with your package.json a few tricks, and a quick comparison of npm vs yarn .
Thanks for reading.
Stephen
npm
uses the ^
to allow any patch fixes or minor releases. The ~
only allows patch fixes.
Note that if you don’t already have your package.lock
file from running npm install
, this won’t work