15. Guides

15.1. Package Managers

There are two major package managers currently used for JavaScript / TypeScript projects which leverage node.js as a build platform.

Our recommendation is to use yarn but both package managers are fine.

Important
When using npm it is important to use a version greater 5.0 as npm 3 has major drawbacks compared to yarn. The following guide assumes that you are using npm >= 5 or yarn.

Before you start reading further, please take a look at the docs:

The following guide will describe best practices for working with yarn / npm.

15.1.1. Semantic Versioning

When working with package managers it is very important to understand the concept of semantic versioning.

Table 21. Version example 1.2.3
Version 1. 2. 3

Version name when incrementing

Major (2.0.0)

Minor (1.3.0)

Patch (1.2.4)

Has breaking changes

yes

no

no

Has features

yes

yes

no

Has bugfixes

yes

yes

yes

The table gives an overview of the most important parts of semantic versioning. In the header version 1.2.3 is displayed. The first row shows the name and the resulting version when incrementing a part of the version. The next rows show specifics of the resulting version - e.g. a major version can have breaking changes, features and bugfixes.

Packages from npm and yarn leverage semantic versioning and instead of selecting a fixed version one can specify a selector. The most common selectors are:

  • ^1.2.3 At least 1.2.3 - 1.2.4 or 1.3.0 can be used, 2.0.0 can not be used

  • ~1.2.3 At lease 1.2.3 - 1.2.4 can be used, 2.0.0 and 1.3.0 can not be used

  • >=1.2.3 At least 1.2.3 - every version greater can also be used

This achieves a lower number of duplicates. To give an example:

If package A needs version 1.3.0 of package C and package B needs version 1.4.0 of package C one would end up with 4 packages.

If package A needs version ^1.3.0 of package C and package B needs version 1.4.0 of package C one would end up with 3 packages. A would use the same version of C as B - 1.4.0.

15.1.2. Do not modify package.json and lock files by hand

Dependencies are always added using a yarn or npm command. Altering the package.json, package-json.lock or yarn.lock file by hand is not recommended.

Always use a yarn or npm command to add a new dependency.

Adding the package express with yarn to dependencies.

yarn add express

Adding the package express with npm to dependencies.

npm install express

15.1.3. What does the lock file do

The purpose of files yarn.lock and package-json.lock is to freeze versions for a short time.

The following problem is solved:

  • Developer A upgrades the dependency express to fixed version 4.16.3.

  • express has sub-dependency accepts with version selector ~1.3.5

  • His local node_modules folder receives accepts in version 1.3.5

  • On his machine everything is working fine

  • Afterward version 1.3.6 of accepts is published - it contains a major bug

  • Developer B now clones the repo and loads the dependencies.

  • He receives version 1.3.6 of accepts and blames developer A for upgrading to a broken version.

Both yarn.lock and package-json.lock freeze all the dependencies. For example in yarn lock you will find.

Listing 12. yarn.lock example (excerp)
accepts@~1.3.5:
  version "1.3.5"
  resolved "[...URL to registry]"
  dependencies:
    mime-types "~2.1.18"
    negotiator "0.6.1"

mime-db@~1.33.0:
  version "1.33.0"
  resolved "[...URL to registry]"

mime-types@~2.1.18:
  version "2.1.18"
  resolved "[...URL to registry]"
  dependencies:
    mime-db "~1.33.0"

negotiator@0.6.1:
  version "0.6.1"
  resolved "[...URL to registry]"

The described problem is solved by the example yarn.lock file.

  • accepts is frozen at version ~1.3.5

  • All of its sub-dependencies are also frozen. It needs mime-types at version ~2.1.18 which is frozen at 2.1.18. mime-types needs mime-db at ~1.33.0 which is frozen at 1.33.0

Every developer will receive the same versions of every dependency.

Important
You have to make sure all your developers are using the same npm/yarn version - this includes the CI build.

15.2. Package Managers Workflow

15.2.1. Introduction

This document aims to provide you the necessary documentation and sources in order to help you understand the importance of dependencies between packages.

Projects in node.js make use of modules, chunks of reusable code made by other people or teams. These small chunks of reusable code are called packages [1]. Packages are used to solve specific problems or tasks. These relations between your project and the external packages are called dependencies.

For example, imagine we are doing a small program that takes your birthday as an input and tells you how many days are left until your birthday. We search in the repository if someone has published a package to retrieve the actual date and manage date types, and maybe we could search for another package to show a calendar, because we want to optimize our time, and we wish the user to click a calendar button and choose the day in the calendar instead of typing it.

As you can see, packages are convenient. In some cases, they may be even needed, as they can manage aspects of your program you may not be proficient in, or provide an easier use of them.

For more comprehensive information visit npm definition

Package.json

Dependencies in your project are stored in a file called package.json. Every package.json must contain, at least, the name and version of your project.

Package.json is located in the root of your project.

Important
If package.json is not on your root directory refer to Problems you may encounter section

If you wish to learn more information about package.json, click on the following links:

Content of package.json

As you noticed, package.json is a really important file in your project. It contains essential information about our project, therefore you need to understand what’s inside.

The structure of package.json is divided in blocks, inside the first one you can find essential information of your project such as the name, version, license and optionally some Scripts.

{
  "name": "exampleproject",
  "version": "0.0.0",
  "license": "MIT",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  }

The next block is called dependencies and contains the packages that project needs in order to be developed, compiled and executed.

"private": true,
  "dependencies": {
    "@angular/animations": "^4.2.4",
    "@angular/common": "^4.2.4",
    "@angular/forms": "^4.2.4",
    ...
    "zone.js": "^0.8.14"
  }

After dependencies we find devDependencies, another kind of dependencies present in the development of the application but unnecessary for its execution. One example is typescript. Code is written in typescript, and then, transpiled to javascript. This means the application is not using typescript in execution and consequently not included in the deployment of our application.

"devDependencies": {
    "@angular/cli": "1.4.9",
    "@angular/compiler-cli": "^4.2.4",
    ...
    "@types/node": "~6.0.60",
    "typescript": "~2.3.3"
  }

Having a peer dependency means that your package needs a dependency that is the same exact dependency as the person installing your package

"peerDependencies": {
    "package-123": "^2.7.18"
  }

Optional dependencies are just that: optional. If they fail to install, Yarn will still say the install process was successful.

"optionalDependencies": {
    "package-321": "^2.7.18"
  }

Finally you can have bundled dependencies which are packages bundled together when publishing your package in a repository.

{
  "bundledDependencies": [
    "package-4"
  ]
}

Here is the link to an in-depth explanation of dependency types​.

Scripts

Scripts are a great way of automating tasks related to your package, such as simple build processes or development tools.

For example:

{
  "name": "exampleproject",
  "version": "0.0.0",
  "license": "MIT",
  "scripts": {
    "build-project": "node hello-world.js",
  }

You can run that script by running the command yarn (run) script or npm run script, check the example below:

$ yarn (run) build-project    # run is optional
$ npm run build-project

There are special reserved words for scripts, like preinstall, which will execute the script automatically before the package you install are installed.

Chech different uses for scripts in the following links:

Or you can go back to Content of package.json​.

Managing dependencies

In order to manage dependencies we recommend using package managers in your projects.

A big reason is their usability. Adding or removing a package is really easy, and by doing so, packet manager update the package.json and copies (or removes) the package in the needed location, with a single comand.

Another reason, closely related to the first one, is reducing human error by automating the package management process.

Two of the package managers you can use in node.js projects are "yarn" and "npm". While you can use both, we encourage you to use only one of them while working on projects. Using both may lead to different dependencies between members of the team.

npm

We’ll start by installing npm following this small guide here.

As stated on the web, npm comes inside of node.js, and must be updated after installing node.js, in the same guide you used earlier are written the instructions to update npm.

How npm works

In order to explain how npms works, let’s take a command as an example:

$ npm install @angular/material @angular/cdk

This command tells npm to look for the packages @angular/material and @angular/cdk in the npm registry, download and decompress them in the folder node_modules along with their own dependencies. Additionally, npm will update package.json and create a new file called package-lock.json.

After initializating and installing the first package there will be a new folder called node_modules in your project. This folder is where your packages are unzipped and stored, following a tree scheme.

Take in consideration both npm and yarn need a package.json in the root of your project in order to work properly. If after creating your project don’t have it, download again the package.json from the repository or you’ll have to start again.

Brief overview of commands

If we need to create a package.json from scratch, we can use the comand init. This command asks the user for basic information about the project and creates a brand new package.json.

$ npm init

Install (or i) installs all modules listed as dependencies in package.json locally. You can also specify a package, and install that package. Install can also be used with the parameter -g, which tells npm to install the Global package.

$ npm install
$ npm i
$ npm install Package
Note
Earlier versions of npm did not add dependencies to package.json unless it was used with the flag --save, so npm install package would be npm install --save package, you have one example below.
$ npm install --save Package

Npm needs flags in order to know what kind of dependency you want in your project, in npm you need to put the flag -D or --save-dev to install devdependencies, for more information consult the links at the end of this section.

$ npm install -D package
$ npm install --save-dev package

The next command uninstalls the module you specified in the command.

$ npm uninstall Package

ls command shows us the dependencies like a nested tree, useful if you have few packages, not so useful when you need a lot of packages.

$ npm ls
npm@@VERSION@ /path/to/npm
└─┬ init-package-json@0.0.4
  └── promzard@0.1.5
example tree

We recommend you to learn more about npm commands in the following link, navigating to the section cli commands.

About Package-lock.json

Package-lock.json describes the dependency tree resulting of using package.json and npm. Whenever you update, add or remove a package, package-lock.json is deleted and redone with the new dependencies.

 "@angular/animations": {
      "version": "4.4.6",
      "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-4.4.6.tgz",
      "integrity": "sha1-+mYYmaik44y3xYPHpcl85l1ZKjU=",
      "requires": {
        "tslib": "1.8.0"
      }

This lock file is checked everytime the command npm i (or npm install) is used without specifying a package, in the case it exists and it’s valid, npm will install the exact tree that was generated, such that subsequent installs are able to generate identical dependency trees.

Warning
It is not recommended to modify this file yourself. It’s better to leave its management to npm.

More information is provided by the npm team at package-lock.json

Yarn

Yarn is an alternative to npm, if you wish to install yarn follow the guide getting started with yarn and download the correct version for your operative system. Node.js is also needed you can find it here.

Working with yarn

Yarn is used like npm, with small differences in syntax, for example npm install module is changed to yarn add module.

$ yarn add @covalent

This command is going to download the required packages, modify package.json, put the package in the folder node_modules and makes a new yarn.lock with the new dependency.

However, unlike npm, yarn maintains a cache with packages you download inside. You don’t need to download every file every time you do a general installation. This means installations faster than npm.

Similarly to npm, yarn creates and maintains his own lock file, called yarn.lock. Yarn.lock gives enough information about the project for dependency tree to be reproduced.

yarn commands

Here we have a brief description of yarn’s most used commands:

$ yarn add Package
$ yarn add --dev Package

Adds a package locally to use in your package. Adding the flags --dev or -D will add them to devDependencies instead of the default dependencies, if you need more information check the links at the end of the section.

$ yarn init

Initializes the development of a package.

$ yarn install

Installs all the dependencies defined in a package.json file, you can also write "yarn" to achieve the same effect.

$ yarn remove Package

You use it when you wish to remove a package from your project.

$ yarn global add Package

Installs the Global package.

Please, refer to the documentation to learn more about yarn commands and their attributes: yarn commands

yarn.lock

This file has the same purpose as Package-lock.json, to guide the packet manager, in this case yarn, to install the dependency tree specified in yarn.lock.

Yarn.lock and package.json are essential files when collaborating in a project more co-workers and may be a source of errors if programmers do not use the same manager.

Yarn.lock follows the same structure as package-lock.json, you can find an example of dependency below:

"@angular/animations@^4.2.4":
  version "4.4.6"
  resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-4.4.6.tgz#fa661899a8a4e38cb7c583c7a5c97ce65d592a35"
  dependencies:
    tslib "^1.7.1"
Warning
As with package-lock.json, it’s strongly not adviced to modify this file. Leave its management to yarn

You can learn more about yarn.lock here: yarn.lock

Global package

Global packages are packages installed in your operative system instead of your local project, global packages useful for developer tooling that is not part of any individual project but instead is used for local commands.

A good example of global package is angular/cli, a command line interface for angular used in our projects. You can install a global package in npm with "npm install -g package" and "yarn global add package" with yarn, you have a npm example below:

Listing 13. npm global package
npm install –g @angular/cli
Package version

Dependencies are critical to the success of a package. You must be extra careful about which version packages are using, one package in a different version may break your code.

Versioning in npm and yarn, follows a semantic called semver, following the logic MAJOR.MINOR.PATCH, like for example, @angular/animations: 4.4.6.

Different versions

Sometimes, packages are installed with a different version from the one initially installed. This happens because package.json also contains the range of versions we allow yarn or npm to install or update to, example:

"@angular/animations": "^4.2.4"

And here the installed one:

 "@angular/animations": {
      "version": "4.4.6",
      "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-4.4.6.tgz",
      "integrity": "sha1-+mYYmaik44y3xYPHpcl85l1ZKjU=",
      "requires": {
        "tslib": "1.8.0"
      }

As you can see, the version we initially added is 4.2.4, and the version finally installed after a global installation of all packages, 4.4.6.

Installing packages without package-lock.json or yarn.lock using their respective packet managers, will always end with npm or yarn installing the latest version allowed by package.json.

"@angular/animations": "^4.2.4" contains not only the version we added, but also the range we allow npm and yarn to update. Here are some examples:

"@angular/animations": "<4.2.4"

The version installed must be lower than 4.2.4 .

"@angular/animations": ">=4.2.4"

The version installed must be greater than or equal to 4.2.4 .

"@angular/animations": "=4.2.4"

the version installed must be equal to 4.2.4 .

"@angular/animations": "^4.2.4"

The version installed cannot modify the first non zero digit, for example in this case it cannot surpass 5.0.0 or be lower than 4.2.4 .

You can learn more about this in Versions

Problems you may encounter

If you can’t find package.json, you may have deleted the one you had previously, which means you have to download the package.json from the repository. In the case you are creating a new project you can create a new package.json. More information in the links below. Click on Package.json if you come from that section.

Important
Using npm install or yarn without package.json in your projects will result in compilation errors. As we mentioned earlier, Package.json contains essential information about your project.

If you have package.json, but you don’t have package-lock.json or yarn.lock the use of command "npm install" or "yarn" may result in a different dependency tree.

If you are trying to import a module and visual code studio is not able to find it, is usually caused by error adding the package to the project, try to add the module again with yarn or npm, and restart Visual Studio Code.

Be careful with the semantic versioning inside your package.json of the packages, or you may find a new update on one of your dependencies breaking your code.

Tip
In the following link there is a solution to a problematic update to one package.

A list of common errors of npm can be found in: npm errors

Recomendations

Use yarn or npm in your project, reach an agreement with your team in order to choose one, this will avoid undesired situations like forgetting to upload an updated yarn.lock or package-lock.json. Be sure to have the latest version of your project when possible.

Tip
Pull your project every time it’s updated. Erase your node_modules folder and reinstall all dependencies. This assures you to be working with the same dependencies your team has.

AD Center recommends the use of yarn.

Last updated 2019-12-11 12:58:44 UTC