Since the release of NPM 5 in 2017, package-lock.json has been introduced to the developer community. I’ve been looking for a concise article on package-locks for a while now, and I have not found one yet, which is why I have decided to write one after trying to figure that out for a little while. Hope this saves you some time!
The problem
- npm install is not deterministic. That is, if I run npm install today, and then I run it again two months from now, I may not end up with the same results.
- What happens is that because of the ~ and the ^ contained in json, npm install will sometimes install updated packages without really raising a flag. Consider the following syntax: 1.0.1 = major.minor.patch.
- ~1.0.1: this will install the most recent patch version for this minor. This can include 1.0.2, 1.0.3 and so on, but not 1.1.0. TL;DR: upgrade the patch as long as you stay at this minor.
- ^1.0.1 : this is pretty much the same thing as the previous one, but for minor patches too . 1.0.1, 1.0.2, 1.1.0, 1.2.0 are all package versions that npm would install if you use that syntax. TL;DR : upgrade patch and minor, but not major.
The solution
- package-lock.json : its goal is to provide an immutable version of package.json, so that you can, for example, pull an older version of your code (which contains the package-lock.json) and end up with the same node_modules folder.
- package-lock.json : this file is generated by npm install. If npm install ends up upgrading packages, it will output a new package-lock.json which contains details about the newer versions of the packages that were installed.
- With package-lock.json comes this new npm command: ci. By running npm ci in a code base containing a package-lock.json, it is now possible to end up with a deterministic node_modules folder.
Conclusion
- npm install is not deterministic, but it generates a package-lock.json.
- package-lock.json makes node_modules deterministic, by using the npm ci command.
- Running npm ci on a package-lock.json will ALWAYS generate the SAME node_modules folder.
Why is it important for you, as a developer?
- You can now cache your node_modules!!!
- We can now generate a cache key with the contents of our package-lock.json
- If there is a cached version of node_modules for this particular package-lock.json, and that you get a cache hit in your build, you may end up saving precious minutes in your build (your mileage may vary).
Annex
Installation time is now slim to none !!!
Here is an example of a task definition for a continuous integration pipeline with YAML notation
- task: Cache@2 inputs: key: '**/package-lock.json, !**/node_modules/**/package-lock.json, !**/.*/**/package-lock.json' path:$PATH_TO_YOUR_FRONTENDClientAppnode_modules' cacheHitVar: 'CacheRestored' - task: Npm@1 displayName: 'npm ci' condition: ne(variables['CacheRestored'], 'true') inputs: command: 'ci' workingDir: '$PATH_TO_YOUR_FRONTENDClientApp'