We've all been there: we created our first react project and was introduced to the lock file. Some took the time to understand it whereas others was scared of it and instinctively added it to the .gitignore
file. This post will give a short description of the lock file, how we should treat it and use it correctly.
5 min read
·
By Joakim Sjøhaug
·
December 15, 2021
While the package.json
👇 file of your project contains dependencies. The lock file describes the exact version of packages to be installed, including the dependencies of the dependencies. Wait what? Do dependencies have dependencies? 😅
{
...,
"dependencies": {
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"@types/jest": "^26.0.15",
"@types/node": "^12.0.0",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "4.0.3",
"typescript": "^4.1.2",
"web-vitals": "^1.0.1"
},
...
}
Yes! Whenever you add a dependency to your project through npm, yarn, or append it to the package.json
file, the new dependency for the project almost certainly has dependencies. The exact versions of the dependencies of the dependency are described in the lock file. This ensures that the lock file is the single source of truth in terms of package versions.
{
...,
"node_modules/react-dom": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
"integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==",
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"scheduler": "^0.20.2"
},
"peerDependencies": {
"react": "17.0.2"
}
},
...
}
The block above 👆is how the dependency react-dom
is described in the lock file. The entry contain different properties about the package, such as the dependencies of the package along with:
The lock file for a project is generated automatically by your package manager when a fresh install of all dependencies of the project is performed. If yarn is your preferred package manager the lock file will be named yarn.lock
, and contrary, if npm is your preferred package manager the lock file will be named package-lock.json
.
As mentioned, the lock file is generated when running the install command with your preferred package manager. Even if the package.json
file hasn't changed, a subsequent install may update the lock file. This happens because of what you may have prefixed the dependencies with. Take a look at the dependencies from a package.json
file below 👇 and note the characters ^
and ~
.
"react": "^17.0.2",
"react-dom": "~17.0.2",
"react-scripts": "4.0.3",
These characters are used to specify the latest compatible version of the dependency the package manager should install. The ~
character specifies that it should install the latest possible version with fixes. The ^
character tells the package manager that it should install the latest non-breaking version of the dependency. Therefore an install command without changing the package.json
file may result in an updated lock file. Listing a dependency without any of these characters means that the version is pinned to the exact version and any later install won't install a later version of the package.
The lock file is intended to be included in repositories. As described above, the lock file gives a single source of truth of the exact version of all dependencies for a project. Including the lock file in repositories ensures that any other fresh installation of the packages for the project uses the same versions of all dependencies.
Another benefit of including the lock file in the repository is that it gives the possibility to "time-travel". Time-travel in this context means that the lock file makes it possible to install all packages at various states of the project.
There aren’t many things that could go wrong in terms of the lock file, but as with any process where humans are involved, accidents happen 🤷♂️
Imagine that a package is added to or updated in the project. As we learned in the first section, this will modify the lock file accordingly. If the updated lock file is forgotten to be committed along with the package.json
file to the repository, a situation where different dependencies are installed could occur. This is usually not a huge problem, but it means that the versions of the dependencies installed locally could differ from the versions used when e.g. building the project for production in a continuous integration process. NPM and yarn have different approaches to avoid such a situation.
NPM have a special command to avoid such a situation:
npm ci
The command is meant to be used in continuous integration environments. It ensures that the project has an existing lock file and that the versions of the dependencies specified in the lock file does match the ones specified in package.json
, otherwise an error will be thrown. Another benefit of this command is that it will automatically delete the node_modules
folder if it exists which will ensure that a clean install of the dependencies is performed.
Yarn avoids the described situation by offering an flag to the install command:
yarn install --immutable
The flag make the install process abort with an exit code if the lock file was to be modified because of a clean install. In continuous integration environments this flag is on by default.
"💡 If you are using Yarn v1 --frozen-lockfile is the equivalent to --immutable"
Hopefully you learned something new or refreshed your knowledge about lock files. You probably want to double check your continuous integration pipelines to ensure that they use the correct commands for installing the dependencies for your project 👀 If you want to dive even deeper into lock files you should checkout (depending on your package manager) NPMs or Yarns documentation.
Thanks for reading - Happy holidays! 🎅🎄⛄️