How cool patch-package is?
It’s one of those libraries, it’s love at first sight and you don’t want to cheat on it with anything else.
Working on modern web applications means, fortunately or not, using hundreds or thousands of open-source NPM libraries. For sure many of them have bugs. What then? Well, we can report an issue on Github and... wait, hoping that the author will respond in the end. If we’re determined enough, we can prepare a Merge Request and... wait, because without the author’s approval we cannot merge it and not to mention publish it. What else can we do? In the act of desperation we can fork the repository, but it produces so many potential problems (mainly with maintenance and update) that it is almost never a good idea. Situation seems to be hopeless, doesn’t it?
Now, let’s reverse the situation. We are the authors of the library and we would like to test it. We can build it locally, copy and paste it manually to the node_modules
directory in the project, and verify it there. It’s highly inefficient, that’s why NPM exposes a command called link
, thanks to which we can create a symlink between build and usage directories. But what if I would like to test the changes on an environment different than local one, let’s say on CI? Well, we can try to create a convention and publish the package with an appropriate tag, e.g. by running npm publish --tag=beta
. Later, all we need to do is run npm install <package-name>@beta
. That could resolve the issue, but is there any other, perhaps better way of doing it? For sure there are many, and one of them is using patch-package
.
The above mentioned package allows everyone to resolve the discussed issues efficiently. How does it work? Let’s go over an example.
To demonstrate, let’s try to solve the issue reported some time ago to one of the most popular libraries which is called @angular/core
. The bug has been resolved in this Merge Request. For the sake of this example, the characteristic of this bug isn’t important. What’s worth mentioning though, is that it is a relatively easy fix. We just need to swap:
return Zone.current.get('isAngularZone') === true;
the above line of code with the following one:
return typeof Zone !== 'undefined' && Zone.current.get('isAngularZone') === true;
Even without knowing the library itself, you can notice it is just typical Runtime Error called Something is not defined
, whatever Something
is.
Let’s install the package version before the fix has been introduced. I’m going to use v13.1.3
which is the last version where bug occurs. Let’s run npm install --save angular/core@13.1.3
and open the node_modules/@angular/core/esm2020/src/zone/ng_zone.mjs
file.
// ...
static isInAngularZone() {
return Zone.current.get('isAngularZone') === true;
}
// ...
Before making any changes, let’s also install patch-package
by running npm install --save-dev patch-package
.
Next, we are going to add necessary changes directly to node_modules/@angular/core/esm2020/src/zone/ng_zone.mjs
file:
// ...
static isInAngularZone() {
return typeof Zone !== 'undefined' && Zone.current.get('isAngularZone') === true;
}
// ...
Be aware it is a dangerous phase where single npm install
command might overwrite our local changes. This is where patch-package
comes in. Let’s run npx patch-package @angular/core
. If everything goes well, you should see a message in your console similar to:
* Creating temporary folder
* Installing @angular/core@13.1.3 with npm
* Diffing your files with clean files
* Created file patches/@angular+core+13.1.3.patch
As we can observe, the library has generated a new patches
directory. Let’s have a look inside:
diff --git a/node_modules/@angular/core/esm2020/src/zone/ng_zone.mjs b/node_modules/@angular/core/esm2020/src/zone/ng_zone.mjs
index 5202a28..ad59567 100755
--- a/node_modules/@angular/core/esm2020/src/zone/ng_zone.mjs
+++ b/node_modules/@angular/core/esm2020/src/zone/ng_zone.mjs
@@ -134,7 +134,7 @@ export class NgZone {
forkInnerZoneWithAngularBehavior(self);
}
static isInAngularZone() {
- return Zone.current.get('isAngularZone') === true;
+ return typeof Zone !== 'undefined' && Zone.current.get('isAngularZone') === true;
}
static assertInAngularZone() {
if (!NgZone.isInAngularZone()) {
//...
Do you see where this is going? Clever, right? The library compared the local version of @angular/core
with the original one. Thanks to that, it found the difference.
What’s next?
In order to make sure that our changes are going to be applied:
- remember to install fixed version of
@angular/core
- we wouldn’t like to create a version conflict
{
"dependencies": {
"@angular/core": "13.1.3"
}
}
- add
patch-package
topostinstall
hook inside thepackage.json
file - thanks to that, our changes will be noticeable even on remote environment like CI or your colleague’s machine!
{
"dependencies": {
"@angular/core": "13.1.3"
},
"scripts": {
"postinstall": "patch-package"
}
}
- make sure the
patches
directory is being followed by Git - if we are resolving an issue doing all of that, remember to report a problem to the library’s author
The last bullet point is crucial! patch-package
is supposed to be a temporary solution which doesn’t block any work in your project and at the same time gives the library’s author enough time to resolve the issue on their side. In addition, patch-package
allows us to test the changes in all kind of environments - it’ll work wherever npm install
is allowed. The library isn’t limited to just one-line fixes, that way we can even overwrite whole files!
Here I would like to thank the author of patch-package
David Sheldrick known as ds300
in NPM. Great job mate! You have no idea how many times you have helped me with this package!
Enjoy!
P.S. If you’re trying to run patch-package
on CI environment, make sure node_modules
aren’t being cached. If that’s the case, npm install
most probably isn’t being triggered and neither is postinstall
hook, this is why you may not see the changes.