Java developers are no strangers to build tools such as Maven, Gradle, and Ant. Stemming from a need for better and/or more automated management of project dependencies, access control, custom widgets, plugins, and transport of files between environments, tools such as these help smooth out the development process. As HTML5 web apps gain traction, so too has developers’ need for similar tools in the realm of web development. In today’s article, we’ll explore one category of build tools called Package Managers.
Packages and Modules
Even before talking about Package Managers, let’s take a step back and talk about packages and modules. A module typically contains some related functionality, for instance dom.js, dates.js, or geolocation.js. There may not be a one-to-one correspondence between files and modules although there often is.
A package is one or more modules (libraries) grouped (or packaged) together. Packages are then combined to form a project. Here’s an example of a package:
Shapes <- Package name - Circle.js <- - Rectangle.js <- } Modules that belong to the Shapes package - Square.js <-
Popular Javascript Package Managers at this time include npm, bower, JSPM, Duo, and Yarn. We won’t be going in to the details of individual Package Managers, but you are encouraged to visit their sites for more information.
Roles and Responsibilities of the Package Manager
Now that we’ve established what a package is and how it fits into a project, let’s examine some of the ways that a Package Manager might help you work with project packages.
Project Dependencies
A package may depend on other packages. These may be part of the same library, while others may be part of third-party libraries. One way that a Package Manager can be of benefit is by keeping track of the dependencies that your project requires in order to function properly. It makes sure that dependency code has not changed during the lifetime of your application, and is always accessible by your project in memory.
Version Control
When you install a package using a Package Manager, it fetches the package from a version control system like Git and downloads it to your device. During subsequent installs, the local package will be used instead of sending additional HTTP requests to get the repository. That way, only new (i.e. missing) packages are downloaded.
It should be noted that Package Managers employ a Lock file to store all the information needed to reproduce the full dependency source tree. for instance, Yarn uses a lockfile, while npm doesn’t.
Metadata
The Package Manager typically maintains a special file called the Manifest for keeping track of all your dependencies. It also contains other metadata about your project. In the JavaScript world, this file is named “package.json”. Here is a very basic package.json file for a small node.js application:
{ "name": "json-lint-express", "preferGlobal": true, "version": "0.0.1", "author": "Cody Bonney <me@codybonney.com>", "description": "a simple web server that lints json strings", "license": "MIT", "engines": { "node": ">=0.10" }, "scripts": { "start": "node ./app.js" }, "dependencies": { "express": "~3.4.7", "json-lint": "~0.1.0" } }
Flat vs. Nested Dependency Structure
Different Package Managers differ in their Out of the box, yarn offers flat dependency structure as compared to npm’s nested structure.
While flat dependency design is the simpler of the two designs, it can lead to dependency conflicts (AKA dependency hell!). Meanwhile, nested dependency design avoids dependency conflicts, it’s more complicated because it will install multiple copies of dependencies when necessary, the result of which may confuse/aggravate developers upon seeing multiple versions of the same resource.
Deterministic vs. Non-deterministic Dependency Installation
Package Managers may employ one of two dependency installation strategies. Deterministic dependency installation means that all computers with a given package.json file will all have the exact same source tree of dependencies installed on them. However, with a non-deterministic package manager, even if you have the exact same package.json on two different computers, the structure of nested dependencies may differ between them. In the case of the latter, it is not uncommon to have situations where a projects runs perfectly on one developer’s workstation, but blows up on a co-worker’s or when deployed to another environment.
Npm v3, by default has non-deterministic installs, but offers a “shrinkwrap” feature to make installs deterministic. It writes all the packages on the disk to a lockfile, along with their respective versions. Yarn offers deterministic installs by employing a lockfile to freeze all the dependencies recursively at the application level.
Conclusion
In an upcoming article, we’ll compare some of the popular JS Package Managers and explore some of the pros and cons of each.