Patching dependencies is easier than forking
How to use patch-package to patch dependencies in javascript apps.
Originally posted on cyrusroshan.com. 😁
Oftentimes, it feels like we over-engineer the solutions we build. Patching packages never feels this way.
When I use patch-tool
, it's often the best solution.
It may not be the best long-term solution, but it's so darn fast. And at a startup, speed of development directly translates into product velocity.
So let me tell you how to use it. With an example: Patching Datadog.
What patch-package does.
When working with javascript, you naturally have file access to the contents of your imported NPM packages. So you can modify them locally. If your code needs a dependency changed, you can make that change, rebuild, and try it out.
What patch-package does then, is it lets you check those patches into version control. So when your teammates or CI systems pull the repo, as part of running yarn
to update deps, they reapply those patches. That way you can modify your dependencies, without having to take on the weighty burden of forking them.
Why make a patch?
So here's the context. At Jam, we care about our users' experience, so we want to keep track of unexpected errors that users hit. We use Datadog for this.
The problem is that a portion of our Chrome extension is a content-script, which gets loaded alongside pages that users report bugs on. When our content script sends updates to Datadog, these updates live in the context of the host page. So they pollute the user's network requests tab in devtools. And when a user closes the page, requests that were buffered could be lost!
So that's the issue. We don't want Datadog requests to go be sent by the pages that our users browse. But Datadog's browser-logs
and browser-rum
packages don't let you proxy messages with a proxy function! You can set a URL to proxy them through, but that doesn't help in our case. What do? Fork it?
No! Patch-package to the rescue!
The patch itself
The Datadog packages we use have all of their requests go through one file:
What we need to do now is modify this file to allow us to proxy these requests. After that, we'll be able to proxy these requests over postMessage, from our content script to our background script.
But first, we need to figure out what version of the file we're modifying! For this file, there's a cjs
and an esm
version. Easiest way to figure out which one we're importing? Just sprinkle some log statements in, clear webpack's cache, and reload!
We've found our file! Next step: modify it!
Since Chrome extension content scripts live in a separate context from their host page (window
objects aren't shared), we can simply have our patched package read from the window
object, to figure out where should send the message data, without worrying about interference with/from the host page.
Amazing! Last step, proxy the messages and execute them on the background script.
And in the background script, we execute the messages as regular network requests:
The end result: Datadog logs are smoothly sent through our background script!
Now that we're satsified with our changes, we run yarn patch-package
to generate a patch file, and commit it to version control:
Try it at home!
Next time you consider forking a package to change a few lines, don't forget you have another option!
Patch-package even makes it easy to contribute back, by providing an easy way to submit your patch as a PR!