Stelligent

npm vs Yarn: Part 2

Previously, we covered the release of Yarn, a new package management system designed to fix some of the shortfalls associated with npm.  With improvements in speed, efficiency, readability, and dependency management, Yarn has shown itself to be a worthwhile tool.  In this post, we will put that tool to use and show just how powerful Yarn can be.  Before we can demonstrate its potential, we have to first get Yarn installed and configured, so let’s do that quickly. 

Installing Yarn 

Depending on your OS, there are various ways to install Yarn.  Firstly, Node.js is required and must be installed separately; if you already have this installed, you must make sure that your version of Node corresponds with the proper paired version of Yarn or you will receive corresponding errors.  Proper Node versions are listed at the top of the Yarn install pageBe aware that Yarn should not be installed with npm.  In our example, we installed Yarn on an Ubuntu 14.04 box using the following commands:

#!/bin/bash
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update
sudo apt-get install yarn

Command Comparisons 

People working on the command line learn to value utilities that allow them to do more with less keystrokes and intuitive commands. This is where Yarn excels.  It’s important to note the distinctions between how Yarn and npm are used.  Both npm install and yarn install will install dependencies from a package.json file, but unlike npm, yarn install will look for a yarn.lock file first before trying install dependences for the package.json.  Knowing the dependencies already exist on the file system greatly speeds up installation, giving Yarn a key edge over npm.  Normally you would execute npm install  to also add more packages, but for Yarn, you would instead use yarn add ; this yarn add command will also automatically save it as a dependency in package.json, whereas npm install  requires adding the –save flag.  For upgrading packages, npm update  is equivalent to yarn upgrade .  That’s just a small selection of the options available, for the full list check the Yarn documentation.    Configuring Yarn  After installation, we could then run tests against an existing git repo for an application called my-app that we pulled down containing a package.json file.  The package.json file contained the following:   

{
 "name": "my-app",
 "version": "0.0.1",
 "private": true,
 "dependencies": {
   "express": "~3.4.7",
   "json-lint": "~0.1.0"
 },
 "devDependencies": {
   "grunt": "~0.4.2",
   "load-grunt-tasks": "~0.2.0"
 }
}

 Speed Tests 

Now, if we run time npm install against this package.json, we get a runtime of:

real 0m38.466s 
user 0m29.244s 
sys 0m32.964s

Whereas with time yarn install we get a much faster runtime of:

real 0m2.934s 
user 0m2.261s 
sys 0m1.566s 

The distinction here is astounding, dropping from 38 to approximately 3 seconds of real time.  As the number of dependencies scale up, so too do the runtime improvements by Yarn.  Reducing feedback loop time is crucial to making efficient code and pipelines, so speed gains like this that Yarn can offer over npm provide just the kind of boost we strive to achieve. 

Comparing Lock Files 

The lock file is a key ingredient to Yarn and while npm does offer a similar option in shrinkwrap, the comparisons between their readability and clarity put Yarn far ahead.  Developers may often find an npm’s lock file useful but hardly comprehensive, whereas Yarn’s lock file lists every single dependency installed including their source location.  Committing the yarn.lock to source control helps ensure that dependencies installed on any system will be the same as the ones set up on the original, maintaining consistency for all developers.  Here’s a snippet of the generated lock file of the package.json from our earlier example in Yarn (when running yarn install): 

# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
abbrev@1:
  version "1.1.1"
  resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
"argparse@~ 0.1.11":
  version "0.1.16"
  resolved "https://registry.yarnpkg.com/argparse/-/argparse-0.1.16.tgz#cfd01e0fbba3d6caed049fbd758d40f65196f57c"
  dependencies:
    underscore "~1.7.0"
    underscore.string "~2.4.0"
async@~0.1.22:
  version "0.1.22"
  resolved "https://registry.yarnpkg.com/async/-/async-0.1.22.tgz#0fc1aaa088a0e3ef0ebe2d8831bab0dcf8845061"

This same lock file for npm would start like this (after doing an npm install and then executing npm shrinkwrap):

{
  "name": "my-app",
  "version": "0.0.1",
  "dependencies": {
    "batch": {
      "version": "0.5.0"
    },
    "buffer-crc32": {
      "version": "0.2.1"
    },
    "bytes": {
      "version": "0.2.1"
    },
    "commander": {
      "version": "1.3.2"
    },

While npm will list out hoisting and locations of dependencies, Yarn will not.  However, it still guarantees nearly identical hoisting positions of node_modules folders, provided the same version of Yarn is used across instances; functionally, Yarn will treat these node_modules folders exactly the same.  Yarn is able to achieve determinism with both a yarn.lock and package.json file, while npm requires the package-lock (as of npm 5) or shrinkwrap file as the source of truth. 

Gotchas And Other Important Notes 

Yarn, like any tool, is not without its idiosyncrasies you should know about.  This includes the fact the command of yarn upgrade will update a package to the latest release unless you specify a range, and will do so accordingly for the package.json reference.  It’s also very important that when working with Yarn, the yarn.lock file be committed to version control rather than a package-lock or shrinkwrap file.  Remember that Yarn will automatically generate this file for you upon executing yarn install and will become the source of knowledge for all future installs, guaranteeing that all those who pull down this repo will get the same versions of dependencies.  You may also want to add the –check-files flag to a yarn install command, to make sure that none of the files in the node_modules folder have been removed. 

Conclusion 

The previous examples demonstrate how Yarn can both be used in practice and just how effective it can be as a package management system.  The speed advantages over npm simply cannot be overstated.  Yarn lowers the feedback loop time drastically, enabling developers to iterate more quickly both locally and in Continuous Integration and Delivery pipelines.  What may take npm several minutes to churn through, Yarn could accomplish 2 to 3 times faster.  Impressively, even if npm was already cached and Yarn was not, Yarn still wins the race.  The dependency management system that automatically creates a lock file helps keep the source of truth exactly the same for a particular version of Yarn.  While similar tools for determinism exist in npm, the ease of the built-in system for Yarn helps put it over as the new standard for package management. 
Did you find this post interesting? Are you passionate about working with the latest AWS technologies? If so, Stelligent is hiring and we would love to hear from you!

Stelligent Amazon Pollycast