Henk Verlinde

Introduction to npm

· 4 min read

This is the first post of the series Using npm with Hugo. In a series of three posts I will introduce you to npm, show you how to manage dependencies, and show you how to customize build scripts.

npm

npm is the default dependency manager for Node.js used by millions of developers. Your first question is most likely “what is a dependency manager and why do I need one?".

Almost any code you write probably ends up depending on 3rd party libraries. All of these libraries (projects, frameworks, files, etc) become dependencies of your project. npm lets you declare the dependencies for a project and it will install and manage them.

If you’ve used Composer for PHP, Bundler for Ruby, or pip for Python, then you’ve already used a dependency manager.

All these tools deal with packages. Every dependency is also a package. What constitutes a package? It can be a local file, local folder, remote zip, local Git repository, remote Git repository, GitHub repository, etc.

Most dependency managers also include a global registry of available packages. For npm, this is the npm Registry. To get an idea of what kind of packages are available, just browse through the most depended upon packages.

I’ll do my best to explain some npm concepts throughout the series, but the official npm Docs are quite accessible as well. It’s recommended to read them to get a full understanding of npm.

Installation

Installing npm is pretty simple. Download and install Node.js (it includes npm) for your platform. I recommend installing the current release.

Usage

Seeing npm in action usually solves most of the confusion you might have after reading about dependencies and packages. Let’s go through the most basic example of installing a single dependency to an empty project.

In this case our project is just an empty folder to start with:

mkdir npm-hugo && cd npm-hugo

For npm, you declare your dependencies in a package.json file. Create one without having npm ask any questions:

npm init -y

For this example we’re only installing one dependency: Bootstrap, the popular HTML, CSS, and JS library:

npm install bootstrap@next --save-dev

You’ll then get the following output:

added 2 packages, and audited 2 packages in 2s

2 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

And your package.json will look like:

{
  "name": "npm-hugo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "bootstrap": "^5.0.0-beta1"
  }
}

Let’s list out some folders to see what actually happened:

$ ls

    Directory: .\npm-hugo

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----           1/14/2021  7:44 PM                node_modules
-a---           1/14/2021  7:44 PM           1693 package-lock.json
-a---           1/14/2021  7:44 PM            281 package.json
$ ls node_modules

    Directory: .\npm-hugo\node_modules

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----           1/14/2021  7:44 PM                @popperjs
d----           1/14/2021  7:44 PM                bootstrap
-a---           1/14/2021  7:44 PM            977 .package-lock.json

We can see that npm created a node_modules directory that contains the bootstrap and @popperjs packages.

You might be wondering about the new package-lock.json file that was generated. The official npm docs explain it nicely:

package-lock.json is automatically generated for any operations where npm modifies either the node_modules tree, or package.json. It describes the exact tree that was generated, such that subsequent installs are able to generate identical trees, regardless of intermediate dependency updates.

Advantages

You might still be wondering what’s the point in using npm and going through all the work above. npm (and any dependency manager for that matter) has many advantages:

  • your dependencies are explicitly declared in a single place
  • installing and updating is handled by the tool
  • your project is locked onto specific versions
  • you don’t need to include the actual 3rd party code in your version control repository

The last one is huge. Without a dependency manager, you’re stuck doing one of two things:

  • adding the entirety of a 3rd party library into your VCS repo
  • using something like Git submodules

This means that when you’re using npm, you check the following files into your repository:

  • package.json
  • package-lock.json

That’s it. Add node_modules to your .gitignore and let npm handle it. Now whoever wants to setup your project, they just run the standard git clone followed by npm install.

Wrapping up

You’re now familiar with the basic concepts of npm. We also went through the most basic example of installing a single dependency to an empty project. You can find the example in the npm-hugo repo I’ve set up.

In the next post we’re going to talk about how to manage dependencies.

Henk Verlinde