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:

note: in this case, this file lived in a third-package, @datadog/browser-core, which is the package I patched directly, after elevating it to a top-level dependency.

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!

THIS IS ESM!

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.

And if we don't add a 'DATADOG_REQUEST_PROXY' function to the window, our logger falls back gracefully. This is useful, because we'll be using this dependency in our background script as well, where we want to use default behavior!

Amazing! Last step, proxy the messages and execute them on the background script.

in the content script, it looks like this

And in the background script, we execute the messages as regular network requests:

That HttpRequest? Imported from the same file we patched, which thankfully already exported it.

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!

Dealing with bugs is 💩, but not with Jam.

Capture bugs fast, in a format that thousands of developers love.
Get Jam for free