Npm Tips and Tricks
Node.js developers use npm to install their favorite dependencies every day. However, far beyond the very famous npm install, npm offers a wide bunch of commands to ease our daily job. Most of them are unknown, so let’s enlight them.
The Most Basic Command: install
The most famous command of npm is probably the install one. Here are two ways to install lodash:
npm install lodash
npm i lodash
Note the default alias at the second line. No need to write the whole install word, a single i is enough.
Yet, it won’t save the dependency in our package.json file, and thus, our co-workers won’t install lodash automatically at their next npm install. We can of course use the --save (or --save-dev) option, or be lazier and use their respective shortcuts: -S and -D:
npm install --save lodash
npm i -S lodash
npm install --save-dev lodash
npm i -D lodash
Five Types of Dependencies
npm has five types of different dependencies, each of them having a given purpose.
- Production dependencies (related to the
dependenciesproperty inpackage.json) are dependencies our project needs to work. For instance, we may find hereexpressorkoa. - Development dependencies (attached to the
devDependenciesproperty) are dependencies which are useful only to developers. They are not required in production. Typically, we find here all testing related libraries (mocha, sinon, etc.) and module bundlers such as Webpack or Gulp. - Peer dependencies are a special kind of production dependencies. Declared as
peerDependenciesin thepackage.jsonfile, they are dependencies required by the project in production, but not embedded directly in current module. For instance, in ng-admin, we have declaredangularas a peer dependency. Indeed, this is a module for Angular, and we assume our final users would already have Angular configured in their own project. No need to duplicate source code. - Optional dependencies are another kind of dependencies stored in
optionalDependenciesproperty (we can install them using--save-optionalflag). Imagine we want to give our users the ability to choose which rich text editor they can use. We can specify tinyMCE and Quill as optional dependencies. Then, in our code, just check which one is configured to embed features from one or the other:
const config = require("config");
if (config.rte === "tinymce") {
const tinymce = require("tinymce");
// configure TinyMCE field
} else if (config.rte === "quill") {
const quill = require("quill");
// configure Quill
}
- Finally, the Bundled Dependencies (
bundledDependenciesproperty) behave like the normal production dependencies, except they are not installed fromnpm. This may be useful if we want to retrieve a private module without setting up a whole private repository. But, as we are using Git for all our projects, we can just use Git repositories instead.
Using Git Repositories Instead of npm Modules
Sometimes, we don’t want to use npm modules directly. Either we are maintaining a private repository, or we are waiting for a critical branch to be merged. We can simply use a Git repository URL directly in the package.json file:
{
"dependencies": {
"angular-ui-codemirror": "git+ssh://git@github.com:jpetitcolas/ui-codemirror.git#di"
}
}
We chose to use the di branch in previous example (the part after the final #). We may also have used a commit hash or a tag name.
If we are using GitHub, the dependency URL can be simplified even more:
{
"dependencies": {
"angular-ui-codemirror": "jpetitcolas/ui-codemirror#di"
}
}
Cleaning Up Not Declared Modules
Sometimes, we may forget to include the --save or --save-dev option when calling npm install, causing the “but it works on my machine” syndrom. To prevent it, just run the prune command from time to time:
npm prune
This command simply removes all dependencies that are not present in the package.json file. Trying to build your project after that is a good way to detect missing dependencies.
It may also be used in production. Imagine we are working on our deployment process, and that something broke. We are going to debug in production directly (even if we know it is bad). And we may sometimes make a standard npm install command instead of a npm install --production one. Not a big deal, but how can we clean up all unrequired development dependencies? Using the following command:
npm prune --production
Blazing Fast Documentation Browsing
npm is shipped with two useful (yet quite unknown) commands: npm home and npm repo. These commands open respectively the project home page and project repository in our favorite web browser. This way, no need to google cumbersome GitHub + [repository name] anymore.
These two URLs are extracted from the package.json file. For instance, considering the admin-on-rest repository:
{
"homepage": "https://github.com/marmelab/admin-on-rest#readme",
"repository": "marmelab/admin-on-rest",
}
Fixing Versions During Installation
As you may have noticed during a failed production deployment, npm doesn’t strictly fix the versions of dependencies by default. It just fixes the major version, and accepts minor version upgrades. It sometimes leads to headaches, as the JavaScript ecosystem is not really rigorous about semver.
We noticed it again when letting jquery-ui upgrade itself on one of our customer project:
[

](images/blog/jquery-version-issue.png)
To better understand how to prevent this kind of issue, let’s look under the hood of the npm versioning system.
Tildes and carets
There are three ways to define a dependency version in our package.json file:
-
Caret (
^) is the most relaxed. It matches all major versions and prefers the latest:^2.2.3means at least2.2.3up to2.*.*. So it will install2.2.12,2.10, but not3.0. -
Tilde (
~) is stricter, matching the most recent minor version:~2.2.3means at least2.2.3up to2.2.*. So it will install2.2.12, but not2.3.0. -
Finally, no prefix means the exact version (
2.2.3). This may fix other dependent libraries. For instance, if you depend onreact@15.3.0andmaterial-ui@~0.16.1, you may be surprised not to get the latest material-ui minor version (0.16.4) because it depends onreact@15.4.0.
Using fixed versions avoids regressions in production, but you won’t benefit from minor bugfixes automatically.
Fixing Versions for New Dependencies
If we add new dependencies to our project and we want to fix version directly during installation, we’ll use the --save-exact flag:
npm install --save --save-exact <package_name>
Or, if we want to automatically save exact version without specifying this flag, we can configure our npm globally:
npm config set save-exact true
But how about existing dependencies?
Fixing Versions on an Existing Project
We may first think to simply remove carets and tildes from our package.json file. It may work, but it also may break. Indeed, as explained above, loose dependencies update themselves automatically without updating the package.json file. Hence, a dependency may be at version 1.2.7 while our package.json still refers to ~1.2.0 version.
We need a more precise method to use real installed version. Getting the version of an installed dependency is as simple as:
cat node_modules/<package_name>/package.json | grep version
Bash gurus may use esoteric command to retrieve version number. But, as I always forget how to write a simple for loop in Bash, let’s use simple JavaScript instead:
const fs = require("fs");
// let's read our package.json file
const package = require("./package.json");
const dependencies = package.dependencies;
// do not mutate original dependencies
const updatedPackage = Object.assign({}, package);
["dependencies", "devDependencies"].forEach((depType) => {
Object.keys(updatedPackage[depType]).forEach((dep) => {
// read package.json file to extract current version
const fileContent = fs.readFileSync(`./node_modules/${dep}/package.json`, {
encoding: "utf-8",
});
const depPackage = JSON.parse(fileContent);
// update version in memory dependencies tree
updatedPackage[depType][dep] = depPackage.version;
});
});
// update package.json content with updated versions
fs.writeFileSync(`./package.json`, JSON.stringify(updatedPackage, null, 4));
We take advantage of the JSON format of package.json to parse and manipulate it in pure JavaScript. The code should be self-explainatory. Note however the third argument of JSON.stringify() on the last line, which allows us to beautify the outputted JSON, using 4 spaces for indentation.
This script will transform your package.json from:
{
"dependencies": {
"babel-runtime": "~6.16.0",
"inflection": "~1.9.0",
"lodash.debounce": "^4.0.6",
"lodash.get": "~4.4.2",
"material-ui": "~0.16.4",
"quill": "~1.1.5",
"react": "^15.4.0",
"react-dom": "~15.4.0",
"react-redux": "~4.4.5",
"react-router": "~2.8.1",
"react-router-redux": "~4.0.6",
"react-tap-event-plugin": "~2.0.0",
"redux": "^3.6.0",
"redux-form": "~6.2.0",
"redux-saga": "~0.13.0"
}
}
to:
{
"dependencies": {
"babel-runtime": "6.18.0",
"inflection": "1.10.0",
"lodash.debounce": "4.0.8",
"lodash.get": "4.4.2",
"material-ui": "0.16.4",
"quill": "1.1.5",
"react": "15.4.0",
"react-dom": "15.4.0",
"react-redux": "4.4.5",
"react-router": "2.8.1",
"react-router-redux": "4.0.6",
"react-tap-event-plugin": "2.0.0",
"redux": "3.6.0",
"redux-form": "6.2.0",
"redux-saga": "0.13.0"
}
}
Shrinkwrapping Dependencies
We handled our dependencies the home-made way. However, there is a dedicated command to deal with them correctly:
npm shrinkwrap
This command creates a npm-shrinkwrap.json file, which is the equivalent of composer.lock in PHP. It contains a snapshot of currently installed dependencies. npm install takes this file in priority when installing dependencies.
It sounds like the perfect solution for our versionning issues. Yet, it has several drawbacks, making mostly unusable in production. The main issues are:
- the lack of Git dependencies support,
- the fact that you can’t have one shrinkwrap for development and another for production - only one. If you fix dependencies for development and commit the shrinkwrap files, the development dependencies will be installed in production, too.
- it may be lossy and create not reproducible output.
We encountered several issues using shrinkwrap and so decided to not use it anymore.
I didn’t take a look on Yarn, the npm alternative for now. But it promises to fix all these issues.
Checking Outdated Dependencies
Packages regularly update, fixing some bugs, improving performances, or adding great new features. Yet, following every package evolution is an inhumane task. That’s why npm offers the outdated feature:
npm outdated
Package Current Wanted Latest Location
babel-cli 6.14.0 6.16.0 6.18.0 admin-on-rest
babel-core MISSING 6.17.0 6.18.2 admin-on-rest
babel-eslint 6.1.2 7.0.0 7.1.0 admin-on-rest
babel-preset-es2015 6.14.0 6.16.0 6.18.0 admin-on-rest
[...]
In a single glance, we have a map of all the efforts we need to bring to be up-to-date. Generally, minor version are painless to upgrade. Updating all our dependencies on a regular basis (twice a month?) is a good practice. It may cost a few hours per month, but it’s far cheapest than being forced to update a major version because of a production blocking issue.
Automate Npm Init Parameters
Even if we don’t initialize a new project every day, we can configure some of the parameters asked during npm init, using the following commands:
npm config set init.author.name "Jonathan Petitcolas"
npm config set init.author.url "https://www.jonathan-petitcolas.com"
npm config set init.license "MIT"
Updating Contributors List Automatically
We should always be grateful to all project contributors. They spend some time to contribute to our project, either by creating an issue or, even better, by submitting a pull request. On open-source projects, we can thank them at least by respectively solving their issue, or adding them in the contributors list. Automating the first case is far beyond the scope of this post, so let’s focus on the second one.
The package.json file contains a contributors property as an array of developer names (and possibly email addresses). We may fill this field manually, gathering all commit authors from a git log list. But, we are developers, and prefer automatize cumbersome tasks. And so does doowb thanks to his update-contributors script (working only with GitHub).
Installing it is as simple as:
npm install --save-dev update-contributors
Finally, we just have to call this freshly installed script to update our package.json file:
./node_modules/.bin/update-contrib
And we are done! :)
More npm Tips
If you have other npm tips and tricks, please share them in the comments!
Authors
Full-stack web developer at marmelab - Node.js, React, Angular, Symfony, Go, Arduino, Docker. Can fit a huge number of puns into a single sentence.