https://kettanaito.com/

ID da verificação
aabaef84-6cff-48a2-bdfe-c0cff99088c8Concluído
URL enviado:
https://kettanaito.com/
Relatório concluído:

Ligações · 16 encontradas

HiperligaçãoTexto
https://www.epicweb.dev/testingRead more
https://twitter.com/kettanaitoTwitter
https://github.com/kettanaitoView more on GitHub
https://www.youtube.com/@kettanaitoYouTube
https://epicweb.devEpicWeb
https://github.com/mswjs/mswGitHub
https://mswjs.ioWebsite
https://github.com/open-draft/deferred-promiseGitHub
https://github.com/kettanaito/naming-cheatsheetNaming CheatsheetLanguage-agnostic variable naming guidelines.
https://github.com/ossjs/releaseReleasePredictable release automation.

Variáveis JavaScript · 14 encontradas

NomeTipo
onbeforetoggleobject
documentPictureInPictureobject
onscrollendobject
webpackChunk_N_Eobject
__next_require__function
nextobject
__NEXT_DATA__object
__SSG_MANIFEST_CBfunction
__NEXT_Pobject
_N_Eundefined

Mensagens de registo da consola · 0 encontradas

HTML

<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><link rel="icon" href="/favicon.png" sizes="any"><link rel="icon" href="/favicon.svg" type="image/svg+xml"><link rel="apple-touch-icon" href="/favicon-apple-touch.png"><link rel="manifest" href="/manifest.webmanifest"><title>kettanaito.com</title><meta name="language" content="en"><meta name="description" content="Artem Zakharchenko's personal blog."><meta name="keywords" content="kettanaito, blog, engineering, articles, learning"><meta property="og:title" content="kettanaito.com"><meta property="og:description" content="Artem Zakharchenko's personal blog."><meta property="og:image" content="https://kettanaito.com/og-image.jpg"><meta property="og:type" content="website"><meta property="og:site_name" content="kettanaito.com"><meta name="twitter:card" content="summary_large_image"><meta name="twitter:title" content="kettanaito.com"><meta name="twitter:description" content="Artem Zakharchenko's personal blog."><meta name="twitter:image" content="https://kettanaito.com/og-image.jpg"><meta name="twitter:creator" content="@kettanaito"><meta name="next-head-count" content="20"><link rel="preload" href="/_next/static/css/75ca33d4a139b310.css" as="style"><link rel="stylesheet" href="/_next/static/css/75ca33d4a139b310.css" data-n-g=""><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/_next/static/chunks/polyfills-c67a75d1b6f99dc8.js"></script><script src="/_next/static/chunks/webpack-ee7e63bc15b31913.js" defer=""></script><script src="/_next/static/chunks/framework-8d78bf989db74c8f.js" defer=""></script><script src="/_next/static/chunks/main-4495d4f2747ce246.js" defer=""></script><script src="/_next/static/chunks/pages/_app-5f6a8b47bf6e1193.js" defer=""></script><script src="/_next/static/chunks/f444cf54-48b94c91cb4f175b.js" defer=""></script><script src="/_next/static/chunks/6dcea1b8-e42cde363c2a469f.js" defer=""></script><script src="/_next/static/chunks/950-26e0b85cdab40e06.js" defer=""></script><script src="/_next/static/chunks/pages/index-d168ccbbcf72394d.js" defer=""></script><script src="/_next/static/oo96ePibyKlOO0pCGCn3K/_buildManifest.js" defer=""></script><script src="/_next/static/oo96ePibyKlOO0pCGCn3K/_ssgManifest.js" defer=""></script><link as="script" rel="prefetch" href="/_next/static/chunks/pages/teaching-a4c73ffdc870bf00.js"><link as="script" rel="prefetch" href="/_next/static/chunks/572-7a0badd3a48c7b1e.js"><link as="script" rel="prefetch" href="/_next/static/chunks/pages/blog-5cc819db625e760f.js"></head><body><div id="__next"><aside class="bg-gray-900 text-gray-100 text-sm font-medium py-1 md:text-center"><div class="px-5 max-w-6xl mx-auto"><p>Level up your testing skills by learning directly from me!<!-- --> <a href="https://www.epicweb.dev/testing" class="inline-block text-red-400 hover:underline break-words" target="_blank" rel="noopener noreferrer">Read more</a></p></div></aside><header class="sticky h-18 top-0 border-b border-gray-200 py-4 bg-gray-100 bg-opacity-80 backdrop-blur-md font-medium z-10"><div class="px-5 max-w-6xl mx-auto flex items-center justify-between gap-5 md:gap-10"><a href="/"><img src="/favicon.svg" alt="kettanaito.com" class="inline-block h-10 rounded-sm hover:opacity-80"></a><nav class="-mr-3"><ul class="flex items-center gap-3"><li><a class="p-3 text-gray-500 hover:text-black text-gray-900" href="/">About <span class="hidden md:inline">me</span></a></li><li><a class="p-3 text-gray-500 hover:text-black " href="/teaching">Teaching</a></li><li><a class="p-3 text-gray-500 hover:text-black " href="/blog">Blog</a></li></ul></nav></div></header><div class="px-5 max-w-6xl mx-auto my-20 lg:my-32"><div class="grid sm:grid-cols-2 lg:grid-cols-6 gap-10 overflow-hidden my-20 lg:my-32 items-center"><div class="col-span-full lg:col-span-3"><h1 class="text-5xl font-extrabold leading-[1.14em]">Hi! 👋 My name is <span class="text-[#F04444]">Artem</span> <!-- -->and I am a software engineer.</h1></div><div class="col-span-full lg:col-span-3 lg:justify-self-end text-gray-500 font-medium"><ul class="-mr-5 flex items-center gap-2 text-4xl"><li><a href="https://kettanaito.com/discord" target="_blank" rel="noreferrer" class="inline-block p-5 hover:text-gray-800"><svg stroke="currentColor" fill="currentColor" stroke-width="0" role="img" viewBox="0 0 24 24" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><title></title><path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"></path></svg></a></li><li><a href="https://twitter.com/kettanaito" target="_blank" rel="noreferrer" class="inline-block p-5 hover:text-gray-800"><svg stroke="currentColor" fill="currentColor" stroke-width="0" role="img" viewBox="0 0 24 24" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><title></title><path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"></path></svg></a></li><li><a href="https://github.com/kettanaito" target="_blank" rel="noreferrer" class="inline-block p-5 hover:text-gray-800"><svg stroke="currentColor" fill="currentColor" stroke-width="0" role="img" viewBox="0 0 24 24" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><title></title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"></path></svg></a></li><li><a href="https://www.youtube.com/@kettanaito" target="_blank" rel="noreferrer" class="inline-block p-5 hover:text-gray-800"><svg stroke="currentColor" fill="currentColor" stroke-width="0" role="img" viewBox="0 0 24 24" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><title></title><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"></path></svg></a></li></ul></div></div><main><div class="grid sm:grid-cols-2 lg:grid-cols-6 gap-10 overflow-hidden my-20 lg:my-32 gap-y-20 text-xl"><div class="col-span-3"><h2 class="mt-8">How it started</h2><p class="leading-8">I was born in 1994 in a small town in eastern Ukraine. Unable to decide whom I wanted to be more—an astronaut or a drummer—I've slowly found myself in the grasp of graphic design. But the main reason I began learning it was so I would have a nice visual presentation for the "awesome" HTML sites I was building at the time.</p><p class="leading-8">I graduated from a medical university and moved to the Czech Republic to pursue a doctor's career. But life had different plans for me, and so it led me down the path of software engineering so I could affect the lives of thousands of other developers through the projects I would build.</p></div><div class="col-span-3"><div class="bg-gradient-to-t from-transparent to-gray-100 p-8 rounded-xl"><h2 class="mt-0">How it's going</h2><div class="leading-8"><p>Over the past decade I've been working as a software engineer in digital agencies, large corporations, and startups of various stages of acquisition. I've learned and taught, broken and fixed, but most importantly, had a chance to meet so many wonderful people along the way.</p><p>Now, I've joined<!-- --> <a href="https://epicweb.dev" target="_blank" rel="noreferrer" class="text-black hover:underline"><span class="-mt-1 p-1 inline-block align-middle bg-[#0D111A] rounded-md"><img src="/logo/epicweb.svg" alt="EpicWeb" class="h-4 w-4"></span> <strong>EpicWeb</strong></a> <!-- -->to teach the world about automated testing!</p></div></div></div></div><hr><section class="my-20 lg:my-32"><header class="text-xl"><div class="grid sm:grid-cols-2 lg:grid-cols-6 gap-10 overflow-hidden"><div class="col-span-3"><h2 class="mt-0">Writing</h2><p class="leading-8">I created this website to be a personal space where I could write about the things I would like to read myself. I draw topics from my experiences and my struggles so we could learn together.</p></div></div></header><div class="grid sm:grid-cols-2 lg:grid-cols-6 gap-10 overflow-hidden my-16 gap-y-20 justify-center sm:justify-start"><article class="group max-w-md col-span-1 lg:col-span-2"><a href="/blog/why-patching-globals-is-harmful"><figure class="aspect-[3/4] flex items-center justify-center overflow-hidden rounded-xl from-transparent to-gray-100 bg-gradient-to-t"><div class="w-48 drop-shadow-xl transition group-hover:scale-110 group-hover:drop-shadow-2xl group-hover:-translate-y-3"><!--?xml version="1.0" encoding="UTF-8"?--><svg viewBox="0 0 384 472" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path d="M82,0 C108.509668,-4.86974701e-15 130,21.490332 130,48 L130,290 C130,316.509668 108.509668,338 82,338 L48,338 C21.490332,338 1.623249e-15,316.509668 0,290 L0,48 C-3.24649801e-15,21.490332 21.490332,3.24649801e-15 48,0 L82,0 Z M49,295 C44.581722,295 41,298.581722 41,303 C41,307.418278 44.581722,311 49,311 C53.418278,311 57,307.418278 57,303 C57,298.581722 53.418278,295 49,295 Z M81,295 C76.581722,295 73,298.581722 73,303 C73,307.418278 76.581722,311 81,311 C85.418278,311 89,307.418278 89,303 C89,298.581722 85.418278,295 81,295 Z M97,271 C92.581722,271 89,274.581722 89,279 C89,283.418278 92.581722,287 97,287 C101.418278,287 105,283.418278 105,279 C105,274.581722 101.418278,271 97,271 Z M65,271 C60.581722,271 57,274.581722 57,279 C57,283.418278 60.581722,287 65,287 C69.418278,287 73,283.418278 73,279 C73,274.581722 69.418278,271 65,271 Z M33,271 C28.581722,271 25,274.581722 25,279 C25,283.418278 28.581722,287 33,287 C37.418278,287 41,283.418278 41,279 C41,274.581722 37.418278,271 33,271 Z M97,51 C92.581722,51 89,54.581722 89,59 C89,63.418278 92.581722,67 97,67 C101.418278,67 105,63.418278 105,59 C105,54.581722 101.418278,51 97,51 Z M65,51 C60.581722,51 57,54.581722 57,59 C57,63.418278 60.581722,67 65,67 C69.418278,67 73,63.418278 73,59 C73,54.581722 69.418278,51 65,51 Z M33,51 C28.581722,51 25,54.581722 25,59 C25,63.418278 28.581722,67 33,67 C37.418278,67 41,63.418278 41,59 C41,54.581722 37.418278,51 33,51 Z M49,27 C44.581722,27 41,30.581722 41,35 C41,39.418278 44.581722,43 49,43 C53.418278,43 57,39.418278 57,35 C57,30.581722 53.418278,27 49,27 Z M81,27 C76.581722,27 73,30.581722 73,35 C73,39.418278 76.581722,43 81,43 C85.418278,43 89,39.418278 89,35 C89,30.581722 85.418278,27 81,27 Z" id="mrd6vsn__path-1"></path></defs><g id="mrd6vsn__thumbnail" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="mrd6vsn__bandage" transform="translate(192, 236) rotate(45) translate(-192, -236)translate(127, 67)"><mask id="mrd6vsn__mask-2" fill="white"><use xlink:href="#mrd6vsn__path-1"></use></mask><use id="mrd6vsn__body" fill="#FFAC4D" xlink:href="#mrd6vsn__path-1"></use><path d="M106,108 C117.045695,108 127.045695,112.477153 134.284271,119.715729 C141.522847,126.954305 146,136.954305 146,148 L146,169 L-16,169 L-16,148 C-16,136.954305 -11.5228475,126.954305 -4.28427125,119.715729 C2.954305,112.477153 12.954305,108 24,108 L106,108 Z" id="mrd6vsn__shadow-top" fill="#FFF5C2" mask="url(#mrd6vsn__mask-2)"></path><path d="M106,169 C117.045695,169 127.045695,173.477153 134.284271,180.715729 C141.522847,187.954305 146,197.954305 146,209 L146,230 L-16,230 L-16,209 C-16,197.954305 -11.5228475,187.954305 -4.28427125,180.715729 C2.954305,173.477153 12.954305,169 24,169 L106,169 Z" id="mrd6vsn__shadow-bottom" fill="#CF772E" mask="url(#mrd6vsn__mask-2)" transform="translate(65, 199.5) scale(1, -1) translate(-65, -199.5)"></path><path d="M24,124 L106,124 C119.254834,124 130,134.745166 130,148 L130,190 C130,203.254834 119.254834,214 106,214 L24,214 C10.745166,214 0,203.254834 0,190 L0,148 C0,134.745166 10.745166,124 24,124 Z" id="mrd6vsn__middle" fill="#FFAC4D" mask="url(#mrd6vsn__mask-2)"></path><g id="mrd6vsn__DO-NOT-USE" mask="url(#mrd6vsn__mask-2)" fill="#79320E" fill-rule="nonzero"><g transform="translate(28.315, 145.5)"><path d="M-9.09882781e-14,16.5 L-9.09882781e-14,6.3159836e-14 L7.348,6.3159836e-14 C8.34533333,6.3159836e-14 9.20333333,0.363 9.922,1.089 C10.6406667,1.815 11,2.67666667 11,3.674 L11,12.826 C11,13.8233333 10.6406667,14.685 9.922,15.411 C9.20333333,16.137 8.34533333,16.5 7.348,16.5 L-9.09882781e-14,16.5 Z M3.674,14.674 L5.5,14.674 C6.01333333,14.674 6.44966667,14.4906667 6.809,14.124 C7.16833333,13.7573333 7.348,13.3246667 7.348,12.826 L7.348,3.674 C7.348,3.16066667 7.16833333,2.72433333 6.809,2.365 C6.44966667,2.00566667 6.01333333,1.826 5.5,1.826 L3.674,1.826 L3.674,14.674 Z" id="mrd6vsn__Shape"></path><path d="M18.348,5.55290509e-14 L22.022,5.55290509e-14 C23.0193333,5.55290509e-14 23.8773333,0.363 24.596,1.089 C25.3146667,1.815 25.674,2.67666667 25.674,3.674 L25.674,12.826 C25.674,13.8233333 25.3146667,14.685 24.596,15.411 C23.8773333,16.137 23.0193333,16.5 22.022,16.5 L18.348,16.5 C17.3506667,16.5 16.489,16.1406667 15.763,15.422 C15.037,14.7033333 14.674,13.838 14.674,12.826 L14.674,3.674 C14.674,2.662 15.037,1.79666667 15.763,1.078 C16.489,0.359333333 17.3506667,5.55290509e-14 18.348,5.55290509e-14 Z M18.348,3.674 L18.348,12.826 C18.348,13.3246667 18.5276667,13.7573333 18.887,14.124 C19.2463333,14.4906667 19.6753333,14.674 20.174,14.674 C20.6873333,14.674 21.1236667,14.4906667 21.483,14.124 C21.8423333,13.7573333 22.022,13.3246667 22.022,12.826 L22.022,3.674 C22.022,3.16066667 21.8423333,2.72433333 21.483,2.365 C21.1236667,2.00566667 20.6873333,1.826 20.174,1.826 C19.6753333,1.826 19.2463333,2.00566667 18.887,2.365 C18.5276667,2.72433333 18.348,3.16066667 18.348,3.674 Z" id="mrd6vsn__Shape"></path><polygon id="mrd6vsn__Path" points="34.848 16.5 34.848 7.0294782e-14 35.706 7.0294782e-14 45.848 9.174 45.848 7.0294782e-14 47.696 7.0294782e-14 47.696 16.5 46.86 16.5 36.696 7.326 36.696 16.5"></polygon><path d="M55.022,4.04465448e-14 L58.696,4.04465448e-14 C59.6933333,4.04465448e-14 60.5513333,0.363 61.27,1.089 C61.9886667,1.815 62.348,2.67666667 62.348,3.674 L62.348,12.826 C62.348,13.8233333 61.9886667,14.685 61.27,15.411 C60.5513333,16.137 59.6933333,16.5 58.696,16.5 L55.022,16.5 C54.0246667,16.5 53.163,16.1406667 52.437,15.422 C51.711,14.7033333 51.348,13.838 51.348,12.826 L51.348,3.674 C51.348,2.662 51.711,1.79666667 52.437,1.078 C53.163,0.359333333 54.0246667,4.04465448e-14 55.022,4.04465448e-14 Z M55.022,3.674 L55.022,12.826 C55.022,13.3246667 55.2016667,13.7573333 55.561,14.124 C55.9203333,14.4906667 56.3493333,14.674 56.848,14.674 C57.3613333,14.674 57.7976667,14.4906667 58.157,14.124 C58.5163333,13.7573333 58.696,13.3246667 58.696,12.826 L58.696,3.674 C58.696,3.16066667 58.5163333,2.72433333 58.157,2.365 C57.7976667,2.00566667 57.3613333,1.826 56.848,1.826 C56.3493333,1.826 55.9203333,2.00566667 55.561,2.365 C55.2016667,2.72433333 55.022,3.16066667 55.022,3.674 Z" id="mrd6vsn__Shape"></path><polygon id="mrd6vsn__Path" points="67.87 16.5 67.87 1.826 64.196 1.826 64.196 3.83841598e-14 75.196 3.83841598e-14 75.196 1.826 71.522 1.826 71.522 16.5"></polygon><path d="M25.696,28 L29.348,28 L29.348,40.826 C29.348,41.8233333 28.9886667,42.685 28.27,43.411 C27.5513333,44.137 26.6933333,44.5 25.696,44.5 L22.022,44.5 C21.0246667,44.5 20.163,44.1406667 19.437,43.422 C18.711,42.7033333 18.348,41.838 18.348,40.826 L18.348,28 L22.022,28 L22.022,40.826 C22.022,41.3246667 22.2016667,41.7573333 22.561,42.124 C22.9203333,42.4906667 23.3493333,42.674 23.848,42.674 C24.3613333,42.674 24.7976667,42.4906667 25.157,42.124 C25.5163333,41.7573333 25.696,41.3246667 25.696,40.826 L25.696,28 Z" id="mrd6vsn__Path"></path><path d="M42.196,31.674 L40.37,31.674 C40.2086667,30.442 39.6293333,29.826 38.632,29.826 C37.9866667,29.826 37.5026667,29.98 37.18,30.288 C36.8573333,30.596 36.696,31.058 36.696,31.674 C36.696,32.1286667 36.872,32.5613333 37.224,32.972 L41.118,37.328 C41.8366667,38.1346667 42.196,39 42.196,39.924 L42.196,40.826 C42.196,41.8233333 41.8366667,42.685 41.118,43.411 C40.3993333,44.137 39.534,44.5 38.522,44.5 L36.696,44.5 C35.4786667,44.5 34.562,44.1956667 33.946,43.587 C33.33,42.9783333 33.022,42.058 33.022,40.826 L34.87,40.826 C34.87,40.958 34.87,41.0606667 34.87,41.134 C34.87,41.6326667 35.0606667,42.014 35.442,42.278 C35.8233333,42.542 36.2413333,42.674 36.696,42.674 C37.3413333,42.674 37.807,42.5163333 38.093,42.201 C38.379,41.8856667 38.522,41.4273333 38.522,40.826 C38.522,40.3566667 38.346,39.924 37.994,39.528 L34.1,35.172 C33.3813333,34.38 33.022,33.5146667 33.022,32.576 L33.022,31.674 C33.022,30.6766667 33.3813333,29.815 34.1,29.089 C34.8186667,28.363 35.684,28 36.696,28 L38.522,28 C40.8246667,28 42.0493333,29.2246667 42.196,31.674 Z" id="mrd6vsn__Path"></path><polygon id="mrd6vsn__Path" points="45.848 44.5 45.848 28 55.022 28 55.022 29.826 49.522 29.826 49.522 35.326 53.196 35.326 53.196 37.174 49.522 37.174 49.522 42.674 55.022 42.674 55.022 44.5"></polygon></g></g></g></g></svg></div></figure></a><div class="mt-4"><p class="mb-4 lg:mb-6 text-sm font-mono text-gray-500"><span class="uppercase tracking-wide">Engineering</span><span class="mx-3 text-gray-300">—</span><span>07/05/2024</span></p><a class="hover:underline" href="/blog/why-patching-globals-is-harmful"><p class="font-bold text-2xl text-pretty">Why Patching Globals Is Harmful</p></a></div></article><article class="group max-w-md col-span-1 lg:col-span-2"><a href="/blog/my-struggle-with-remix"><figure class="aspect-[3/4] flex items-center justify-center overflow-hidden rounded-xl from-transparent to-gray-100 bg-gradient-to-t"><div class="w-48 drop-shadow-xl transition group-hover:scale-110 group-hover:drop-shadow-2xl group-hover:-translate-y-3"><!--?xml version="1.0" encoding="UTF-8"?--><svg viewBox="0 0 499 665" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="dcrhnpq__thumbnail" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="dcrhnpq__remix" transform="translate(122.000000, 184.000000)" fill="#1E293B" fill-rule="nonzero"><path d="M141.674538,0 C218.04743,0 256,36.3493031 256,94.4136694 C256,137.843796 229.292875,166.16709 193.214546,170.888177 C223.670152,177.025429 241.473826,194.491998 244.754554,228.952544 L245.229325,235.289856 L245.643706,241.214203 L246.00181,246.756109 L246.250531,250.934795 L246.517683,255.865245 L246.656217,258.679019 L246.853693,263.148984 L247.012965,267.370833 L247.091895,269.797544 L247.198581,273.685906 L247.290626,278.131883 L247.324005,280.280236 L247.384197,286.505871 L247.403543,293.002292 L247.40462,296.886512 L168.646185,296.886512 L168.650135,295.266478 L168.650135,295.266478 L168.678181,292.120279 L168.678181,292.120279 L168.725186,289.055223 L168.861417,281.631321 L168.895871,279.142491 L168.922852,275.239899 L168.922852,275.239899 L168.919162,272.744266 L168.896218,270.127045 L168.864335,268.072886 L168.799537,265.197081 L168.706158,262.147348 L168.580806,258.904651 L168.42009,255.449957 L168.325411,253.637163 L168.164297,250.804473 L167.978998,247.828446 L167.691838,243.623566 L167.444542,240.281862 C167.373519,239.25114 167.291291,238.24473 167.19786,237.262104 L166.996058,235.328408 C164.395177,212.50087 155.340815,203.170989 139.832974,200.059114 L138.525715,199.814028 C137.64425,199.660026 136.742867,199.52459 135.821566,199.406474 L134.424675,199.242133 C134.189371,199.216855 133.952821,199.192621 133.715026,199.169411 L132.27332,199.042283 L132.27332,199.042283 L130.801736,198.938792 L130.801736,198.938792 L129.300276,198.858003 L129.300276,198.858003 L127.785563,198.799503 L126.241612,198.761396 L124.668422,198.742777 L124.668422,198.742777 L0,198.740492 L0,136.900224 L127.619345,136.900224 C129.706029,136.900224 131.728173,136.860653 133.685777,136.779928 L135.621869,136.685425 L135.621869,136.685425 L137.514935,136.563134 L137.514935,136.563134 L139.364974,136.412701 C139.669729,136.385264 139.97269,136.35664 140.273859,136.326822 L142.05936,136.133518 C143.235352,135.995014 144.382659,135.837162 145.501284,135.659493 L147.157707,135.378069 C167.866574,131.62361 178.22062,120.630459 178.22062,99.1783057 C178.22062,75.1035054 161.354066,60.5128152 127.619345,60.5128152 L0,60.5128152 L0,0 L141.674538,0 Z M83.2762921,250.785352 C93.6094556,250.785352 97.9327877,256.522818 99.4729615,262.01452 L99.6761617,262.804225 L99.6761617,262.804225 L99.8429155,263.58653 L99.9515227,264.204367 L99.9979397,264.509915 L100.075689,265.112992 L100.134243,265.703672 L100.156667,265.993728 L100.188494,266.561991 L100.198173,266.839685 L100.205751,267.380932 L100.205751,296.886512 L0,296.886512 L0,250.785352 L83.2762921,250.785352 Z"></path></g></g></svg></div></figure></a><div class="mt-4"><p class="mb-4 lg:mb-6 text-sm font-mono text-gray-500"><span class="uppercase tracking-wide">Engineering</span><span class="mx-3 text-gray-300">—</span><span>10/05/2023</span></p><a class="hover:underline" href="/blog/my-struggle-with-remix"><p class="font-bold text-2xl text-pretty">My Struggle With Remix</p></a></div></article><article class="group max-w-md col-span-1 lg:col-span-2"><a href="/blog/debounce-vs-throttle"><figure class="aspect-[3/4] flex items-center justify-center overflow-hidden rounded-xl from-transparent to-gray-100 bg-gradient-to-t"><div class="w-48 drop-shadow-xl transition group-hover:scale-110 group-hover:drop-shadow-2xl group-hover:-translate-y-3"><!--?xml version="1.0" encoding="UTF-8"?--><svg viewBox="0 0 499 665" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><linearGradient x1="100%" y1="50%" x2="0%" y2="50%" id="m8oi1nt__linearGradient-1"><stop stop-color="#FFFFFF" stop-opacity="0" offset="0%"></stop><stop stop-color="#95A2B8" offset="100%"></stop></linearGradient><linearGradient x1="93.4036474%" y1="50%" x2="0%" y2="50%" id="m8oi1nt__linearGradient-2"><stop stop-color="#FFFFFF" stop-opacity="0" offset="0%"></stop><stop stop-color="#485569" offset="100%"></stop></linearGradient></defs><g id="m8oi1nt__thumbnail" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="m8oi1nt__Group-5" transform="translate(249.750000, 332.250000) rotate(90.000000) translate(-249.750000, -332.250000) translate(21.000000, 240.000000)"><path d="M92.1747965,0.5 C119.538983,0.5 144.118126,12.4015715 160.999613,31.3014158 C146.473831,47.5617385 137.647696,69.0020968 137.647696,92.5 C137.647696,115.997903 146.473831,137.438261 160.997676,153.698584 C144.118126,172.598429 119.538983,184.5 92.1747965,184.5 C41.2680621,184.5 0,143.310197 0,92.5 C0,41.689803 41.2680621,0.5 92.1747965,0.5 Z" id="m8oi1nt__Combined-Shape" fill="url(#m8oi1nt__linearGradient-1)"></path><circle id="m8oi1nt__Oval-Copy-2" fill="url(#m8oi1nt__linearGradient-2)" cx="228.5" cy="92" r="92"></circle><circle id="m8oi1nt__Oval-Copy-3" fill="#1D293B" cx="365.5" cy="92" r="92"></circle></g></g></svg></div></figure></a><div class="mt-4"><p class="mb-4 lg:mb-6 text-sm font-mono text-gray-500"><span class="uppercase tracking-wide">Engineering</span><span class="mx-3 text-gray-300">—</span><span>23/12/2019</span></p><a class="hover:underline" href="/blog/debounce-vs-throttle"><p class="font-bold text-2xl text-pretty">Debounce vs Throttle: Definitive Visual Guide</p></a></div></article></div><footer class="text-center mt-16"><a class="button px-14 py-2" href="/blog">See more posts</a></footer></section><hr><section class="my-20 lg:my-32"><header class="text-xl"><div class="grid sm:grid-cols-2 lg:grid-cols-6 gap-10 overflow-hidden"><div class="col-span-3"><h2 class="mt-0">Open source</h2><p class="leading-8">Open source plays a tremendous part in my engineering journey. This is my primary way of learning and I'm truly humbled to have influenced so many people with my projects. Here are just a few of them.</p></div></div></header><div class="grid sm:grid-cols-2 lg:grid-cols-6 gap-10 overflow-hidden my-20 text-lg"><div class="col-span-3"><article class="flex flex-col sm:flex-row gap-7 bg-gradient-to-t from-transparent to-orange-50 rounded-xl p-10"><img src="/logo/msw.svg" alt="Mock Service Worker" class="w-14 h-14 rounded-lg shadow-xl shadow-orange-300 self-center sm:self-start"><div><h3 class="mt-0">Mock Service Worker</h3><p class="text-yellow-900 text-opacity-80">Seamless API mocking library for browser and Node.js.</p><p class="space-x-5 font-medium text-gray-500"><a href="https://github.com/mswjs/msw" target="_blank" rel="noreferrer" class="button px-8 py-1.5"><svg stroke="currentColor" fill="currentColor" stroke-width="0" role="img" viewBox="0 0 24 24" class="inline -mt-1 mr-2" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><title></title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"></path></svg>GitHub</a><a href="https://mswjs.io" target="_blank" rel="noreferrer" class="text-black hover:underline">Website</a></p></div></article></div><div class="col-span-3"><article class="flex gap-7 flex-col sm:flex-row bg-gradient-to-t from-transparent to-green-50 rounded-xl p-10"><img src="/logo/package.svg" alt="Deferred Promise" class="w-14 h-14 rounded-lg shadow-xl shadow-green-200 self-center sm:self-start"><div><h3 class="mt-0">Deferred Promise</h3><p class="text-green-900 text-opacity-80">Type-safe A+ Promise implementation with deferred resolution.</p><p class="space-x-5 font-medium text-gray-500"><a href="https://github.com/open-draft/deferred-promise" target="_blank" rel="noreferrer" class="button px-8 py-1.5"><svg stroke="currentColor" fill="currentColor" stroke-width="0" role="img" viewBox="0 0 24 24" class="inline -mt-1 mr-2" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><title></title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"></path></svg>GitHub</a></p></div></article></div></div><div class="grid sm:grid-cols-2 lg:grid-cols-4 gap-10 my-20"><article><a href="https://github.com/kettanaito/naming-cheatsheet" target="_blank" rel="noreferrer" class="block bg-gradient-to-t from-transparent to-gray-100 px-5 py-4 rounded-xl hover:to-gray-200"><h3 class="my-0 mb-2 text-lg">Naming Cheatsheet</h3><p class="text-gray-500">Language-agnostic variable naming guidelines.</p></a></article><article><a href="https://github.com/ossjs/release" target="_blank" rel="noreferrer" class="block bg-gradient-to-t from-transparent to-gray-100 px-5 py-4 rounded-xl hover:to-gray-200"><h3 class="my-0 mb-2 text-lg">Release</h3><p class="text-gray-500">Predictable release automation.</p></a></article><article><a href="https://github.com/open-draft/dotalias" target="_blank" rel="noreferrer" class="block bg-gradient-to-t from-transparent to-gray-100 px-5 py-4 rounded-xl hover:to-gray-200"><h3 class="my-0 mb-2 text-lg">Dotalias</h3><p class="text-gray-500">Single source of truth for path alias configuration.</p></a></article><article><a href="https://github.com/open-draft/until" target="_blank" rel="noreferrer" class="block bg-gradient-to-t from-transparent to-gray-100 px-5 py-4 rounded-xl hover:to-gray-200"><h3 class="my-0 mb-2 text-lg">Until</h3><p class="text-gray-500">Error-first handling of async/await without try/catch.</p></a></article></div><footer class="text-center"><a href="https://github.com/kettanaito" target="_blank" rel="noreferrer" class="button px-14 py-2">View more on GitHub</a></footer></section><hr><section class="my-20 lg:my-32"><header class="text-xl"><div class="grid sm:grid-cols-2 lg:grid-cols-6 gap-10 overflow-hidden"><div class="col-span-3"><h2 class="mt-0">Speaking</h2><p class="leading-8">I've been priveleged to speak at a number of events around the globe. From cozy meetups to large international conferences, I speak about the topics that interest me, sharing my knowledge with others.</p></div></div></header><div class="grid sm:grid-cols-2 lg:grid-cols-6 gap-10 overflow-hidden my-16"><div class="col-span-full lg:col-span-4"><article><div class="relative rounded-tr-xl rounded-tl-xl overflow-hidden"><img alt="Mock Service Worker 2.0" srcset="/_next/image?url=%2Ftalk-msw-2.0.jpg&amp;w=1920&amp;q=75 1x, /_next/image?url=%2Ftalk-msw-2.0.jpg&amp;w=3840&amp;q=75 2x" src="/_next/image?url=%2Ftalk-msw-2.0.jpg&amp;w=3840&amp;q=75" width="1280" height="720" decoding="async" data-nimg="1" class="my-0" loading="lazy" style="color:transparent"><a href="https://www.youtube.com/watch?v=DKvccL1WSeU" target="_blank" rel="noreferrer" class="absolute group top-0 left-0 h-full w-full bg-slate-500 bg-opacity-70 flex items-center justify-center"><span class="inline-block rounded-full bg-white px-4 py-2 font-bold shadow-xl group-hover:bg-slate-200"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 20 20" class="inline -mb-1 mr-1 align-baseline" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clip-rule="evenodd"></path></svg>Watch</span></a></div><div class="p-8 bg-slate-100 rounded-bl-xl rounded-br-xl"><h3 class="mt-0 mb-1">Mock Service Worker 2.0</h3><p class="inline-block font-mono font-medium text-gray-500">TestJS Summit 2023</p><p class="mt-5 text-lg font-medium">I talk API mocking, web standards, and how Mock Service Worker merries the two together.</p></div></article></div><div class="col-span-full lg:col-span-2"><p class="my-10 mb-0 uppercase text-sm font-bold text-gray-500 tracking-widest">Past talks</p><article class="block group py-8 border-b border-slate-200"><h3 class="my-0 text-xl font-semibold">Dissecting Complexity in Tests</h3><p class="inline-block font-mono text-sm font-medium text-gray-500">TestJS Summit 2022</p><p class="mt-5 text-lg font-medium">The most common sources of complexity in tests and practical tips on how to approach them.</p><p><a href="https://www.youtube.com/watch?v=nUozijHdgMM" target="_blank" rel="noreferrer" class="inline-block font-bold bg-slate-100 rounded-full px-4 py-2 hover:bg-slate-200"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 20 20" class="inline -mb-1 mr-1 align-baseline" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clip-rule="evenodd"></path></svg>Watch</a></p></article><article class="block group py-8 "><h3 class="my-0 text-xl font-semibold">Beyond API Mocking</h3><p class="inline-block font-mono text-sm font-medium text-gray-500">TestJS Summit 2021</p><p class="mt-5 text-lg font-medium">Using Service Worker API to mock responses and why it's incredible.</p><p><a href="https://youtube.com/watch?v=76yEzm07Kfk" target="_blank" rel="noreferrer" class="inline-block font-bold bg-slate-100 rounded-full px-4 py-2 hover:bg-slate-200"><svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 20 20" class="inline -mb-1 mr-1 align-baseline" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clip-rule="evenodd"></path></svg>Watch</a></p></article></div></div><footer class="flex flex-col lg:flex-row items-center justify-center gap-4"><p class="text-xl text-gray-500 font-medium">Would like for me to speak at your event?</p><a href="https://twitter.com/kettanaito" target="_blank" rel="noreferrer" class="button px-14 py-2">Let's talk!</a></footer></section><div class="bg-slate-100 rounded-xl p-10 text-lg my-20 lg:my-32"><div class="grid sm:grid-cols-2 lg:grid-cols-6 gap-10 overflow-hidden"><div class="col-span-full lg:col-span-4"><h2 class="mt-0">Stay in touch</h2><p>Never miss a single post or a project announcement I make. Follow me on Twitter to stay in touch, ask a question, or just discuss different engineering topics together.</p></div><div class="col-span-full lg:col-span-2 self-center lg:justify-self-center"><a href="https://twitter.com/intent/follow?original_referer=https%3A%2F%2Fkettanaito.com&amp;twgr=kettanaito&amp;screen_name=kettanaito&amp;twterm=follow" target="_blank" rel="noreferrer" class="button text-center w-full md:w-auto px-12 py-2 whitespace-nowrap"><svg stroke="currentColor" fill="currentColor" stroke-width="0" role="img" viewBox="0 0 24 24" class="inline-block -mb-[0.25ch] mr-2 align-baseline" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><title></title><path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"></path></svg><span>Follow me</span></a></div></div></div></main></div><footer class="py-20 lg:py-32 font-medium text-gray-500 border-t"><div class="px-5 max-w-6xl mx-auto"><div class="grid sm:grid-cols-2 lg:grid-cols-6 gap-10 overflow-hidden"><div class="col-span-2 lg:col-span-3"><p>© <!-- -->2024<!-- --> Artem Zakharchenko.</p><p class="mt-1 text-sm">All content of this website is distributed under the<!-- --> <a href="https://creativecommons.org/licenses/by-nc/4.0/" target="_blank" rel="noreferrer" class="text-black underline hover:text-gray-500">CC BY-NC license</a>.</p></div><ul class="lg:col-start-5 space-y-1 lg:text-right"><li><a class="py-1 inline-block hover:text-black hover:underline" href="/">Home</a></li><li><a class="py-1 inline-block hover:text-black hover:underline" href="/blog">Blog</a></li><li><a href="/blog/rss.xml" target="_blank" rel="noreferrer" class="py-1 inline-block hover:text-black hover:underline">RSS</a></li></ul><ul class="space-y-1 lg:text-right"><li><a href="https://kettanaito.com/discord" target="_blank" rel="noreferrer" class="py-1 inline-block hover:text-black hover:underline">Discord</a></li><li><a href="https://twitter.com/kettanaito" target="_blank" rel="noreferrer" class="py-1 inline-block hover:text-black hover:underline">Twitter</a></li><li><a href="https://github.com/kettanaito" target="_blank" rel="noreferrer" class="py-1 inline-block hover:text-black hover:underline">GitHub</a></li><li><a href="https://www.youtube.com/@kettanaito" target="_blank" rel="noreferrer" class="py-1 inline-block hover:text-black hover:underline">YouTube</a></li></ul></div></div></footer></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"featuredPosts":[{"id":"mrd6vsn","code":"var Component=(()=\u003e{var p=Object.create;var r=Object.defineProperty;var u=Object.getOwnPropertyDescriptor;var m=Object.getOwnPropertyNames;var g=Object.getPrototypeOf,f=Object.prototype.hasOwnProperty;var y=(n,e)=\u003e()=\u003e(n\u0026\u0026(e=n(n=0)),e);var w=(n,e)=\u003e()=\u003e(e||n((e={exports:{}}).exports,e),e.exports),b=(n,e)=\u003e{for(var o in e)r(n,o,{get:e[o],enumerable:!0})},h=(n,e,o,s)=\u003e{if(e\u0026\u0026typeof e==\"object\"||typeof e==\"function\")for(let i of m(e))!f.call(n,i)\u0026\u0026i!==o\u0026\u0026r(n,i,{get:()=\u003ee[i],enumerable:!(s=u(e,i))||s.enumerable});return n};var v=(n,e,o)=\u003e(o=n!=null?p(g(n)):{},h(e||!n||!n.__esModule?r(o,\"default\",{value:n,enumerable:!0}):o,n)),k=n=\u003eh(r({},\"__esModule\",{value:!0}),n);var a=y(()=\u003e{});var l=w((B,c)=\u003e{a();c.exports=_jsx_runtime});var q={};b(q,{default:()=\u003ej,frontmatter:()=\u003ex});a();var t=v(l()),x={title:\"Why Patching Globals Is Harmful\",description:\"Why I believe patching globals is a bad API design.\",category:\"Engineering\",date:new Date(171504e7),keywords:[\"patch\",\"monkey-patch\",\"globals\",\"fetch\",\"harm\",\"mootools\"]};function d(n){let e=Object.assign({p:\"p\",em:\"em\",h2:\"h2\",a:\"a\",code:\"code\",pre:\"pre\",strong:\"strong\",blockquote:\"blockquote\",h3:\"h3\",hr:\"hr\",ul:\"ul\",li:\"li\"},n.components),{Quote:o}=e;return o||T(\"Quote\",!0),(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(e.p,{children:\"Honestly, I'd never thought I'd be writing about this, but the matter of patching globals turned out to be one of those topics that a lot of engineers seem to misunderstand. Most of us don't do monkey-patching, and even fewer stay around those implementations long enough to witness their impact. All the more reason to talk about it.\"}),`\n`,(0,t.jsxs)(e.p,{children:[`Throughout the next few thousand words, I will be referring to modifications of global APIs as \"patching\". You take the code you don't own, you modify its behavior, you `,(0,t.jsx)(e.em,{children:\"patch\"}),\" it. I hope we are clear on semantics.\"]}),`\n`,(0,t.jsx)(e.p,{children:\"I am using React, Next.js, and Bun as examples in this article because they have introduced features built on top of patched globals in the past months, which was met with a sizeable backlash from the community. This piece is not a part of that backlash. If you came here to bolster your dislike toward those projects or the companies behind them, I kindly encourage you to close this tab and go on your way. I have voiced my concerns about the matter publicly and saw firsthand how deserved criticism is muddied with blind hatred, and I wish to contribute to that kind of discourse no longer.\"}),`\n`,(0,t.jsx)(e.p,{children:\"I love API design. All of the said companies have featured excellent design choices in the past. Today, you will learn why patching globals is never such a choice, despite its reasoning and appeal, which we will also discuss.\"}),`\n`,(0,t.jsx)(e.h2,{children:\"Background\"}),`\n`,(0,t.jsxs)(e.p,{children:['I first came upon the \"patching issue\" when I heard the announcement of the new ',(0,t.jsx)(e.a,{href:\"https://nextjs.org/docs/app/building-your-application/caching\",children:\"Caching API\"}),\" in Next.js. While the feature itself is a great addition to the developer and user experience, the proposed public API was rather . . . unusual.\"]}),`\n`,(0,t.jsxs)(e.p,{children:[\"The team has decided to provide a Next.js-specific feature by extending a standard global API\\u2014\",(0,t.jsx)(e.code,{children:\"fetch\"}),\".\"]}),`\n`,(0,t.jsx)(e.pre,{children:(0,t.jsx)(e.code,{className:\"language-js\",children:`fetch('https://kettanaito.com', {\n  // This is not a valid option of RequestInit,\n  // and it doesn't exist outside of Next.js.\n  next: { revalidate: 10 },\n})\n`})}),`\n`,(0,t.jsxs)(e.p,{children:[\"After looking more into this, I've soon discovered that React itself was \",(0,t.jsxs)(e.a,{href:\"https://github.com/facebook/react/issues/25573\",children:[\"patching the global \",(0,t.jsx)(e.code,{children:\"fetch\"}),\" function\"]}),\" to cache server-side responses in their in-progress React Server Component model. This was one of the most used JavaScript frameworks and one of the most used JavaScript meta-frameworks introducing what I find to be a rather harmful design pattern.\"]}),`\n`,(0,t.jsxs)(e.p,{children:[\"A few days ago, I stumbled upon a \",(0,t.jsx)(e.a,{href:\"https://twitter.com/bunjavascript/status/1785615815838175658\",children:\"tweet from Bun\"}),\", reminding its users about a convenient way to proxy HTTP requests. All it takes is to give any \",(0,t.jsx)(e.code,{children:\"fetch\"}),\" request a \",(0,t.jsx)(e.code,{children:\"proxy\"}),\" option:\"]}),`\n`,(0,t.jsx)(e.pre,{children:(0,t.jsx)(e.code,{className:\"language-js\",children:`fetch('https://redd.one', {\n  // This is not a valid option of RequestInit,\n  // and it doesn't exist outside of Bun.\n  proxy: 'https://kettanaito.com',\n})\n`})}),`\n`,(0,t.jsx)(e.p,{children:\"At this point, it was the third time a major framework (or, in this case, a runtime) introduced a feature that relied on modifying an existing global API. I did (and still do) assumed the good intention from the authors, yet I couldn't help but worry that it was an oversight, an accidental spark of a flint that, if not doused, would shroud our whole dear forest in fire.\"}),`\n`,(0,t.jsx)(e.p,{children:(0,t.jsx)(e.strong,{children:\"But why do I think those APIs are bad? What is so harmful about them?\"})}),`\n`,(0,t.jsx)(e.p,{children:\"Answering these questions without understanding the reason behind those design decisions first would be simply improper.\"}),`\n`,(0,t.jsx)(e.h2,{children:\"Appeal of patching globals\"}),`\n`,(0,t.jsx)(e.p,{children:\"It's not an easy thing to come up with a good API. Even more so, with an API that would be used by thousands of people. It has to be intuitive, concise, solve the problem it sets off to solve all while being efficient about it. There's a lot of boxes to check, with some of them pulling your judgment in opposite directions.\"}),`\n`,(0,t.jsxs)(e.p,{children:[\"Luckily, all three examples we have today happened to concern \",(0,t.jsx)(e.code,{children:\"fetch\"}),\". It's a global function to make HTTP requests on the web, and it's known and used by a lot of people out there. You call \",(0,t.jsx)(e.code,{children:\"fetch\"}),\" when you want a request to happen. Isn't that the perfect place to say you want that request cached too? Maybe proxied elsewhere? And because it's a global, it doesn't even have to be imported! We are checking off those boxes so fast we are grinding pencils to stumps here.\"]}),`\n`,(0,t.jsx)(e.p,{children:\"In the end, why should anyone go through the trouble of inventing custom functions and methods for something the platform should have had, to begin with?\"}),`\n`,(0,t.jsx)(e.p,{children:\"And I do get this appeal, I truly do. There is, however, a cost to every decision, and patching globals doesn't just have a cost that affects both users and maintainers, but also actively harms otherwise great APIs and even the language itself. Now, to the specifics.\"}),`\n`,(0,t.jsx)(e.h2,{children:\"Harm #1: Maintenance\"}),`\n`,(0,t.jsx)(e.p,{children:\"When you patch a global, you modify something you do not own. Maintaining the things you do not own is hard. That is why you should always strive toward making the experiences you ship apparent and explicit. Like so:\"}),`\n`,(0,t.jsx)(e.pre,{children:(0,t.jsx)(e.code,{className:\"language-js\",children:`import { experience } from 'your-thing'\n`})}),`\n`,(0,t.jsx)(e.p,{children:\"That will also help you assess the blast radius of any change you decide to make to your framework in the future.\"}),`\n`,(0,t.jsxs)(e.p,{children:[\"See, the globals are precisely that\\u2014\",(0,t.jsx)(e.em,{children:\"global\"}),\". The only thing worse than thinking you can predict all the possible ways the global is used in the wild both by your users \",(0,t.jsx)(e.em,{children:\"and\"}),\" other tools, is to assume you can accommodate for it.\"]}),`\n`,(0,t.jsxs)(e.blockquote,{children:[`\n`,(0,t.jsxs)(e.p,{children:[\"Context: Hi \\u{1F44B} I patch globals \",(0,t.jsx)(e.a,{href:\"https://github.com/mswjs/interceptors\",children:\"for a living\"}),\". I don't enjoy that. I actually hate that, and I'd move away from that instantly had I had a choice (and I did, moving the network interception to a \",(0,t.jsx)(e.a,{href:\"https://mswjs.io\",children:\"Service Worker\"}),\" the moment it clicked). Sadly, the nature of what I do doesn't give me much choice. Luckily, all the frameworks I bring up as an example today \",(0,t.jsx)(e.em,{children:\"do\"}),\" have that choice, and I will even show an alternative design to their APIs later in this article.\"]}),`\n`,(0,t.jsx)(e.p,{children:\"But not before we are done discussing the harms of patching globals.\"}),`\n`]}),`\n`,(0,t.jsx)(e.p,{children:\"You can take my word for it, or you can wait for your users to tell you how meddling with globals breaks their applications and what an excruciating pain they had to suffer to discover that.\"}),`\n`,(0,t.jsx)(e.p,{children:\"You have thrown a bear trap into the source code and someone's got caught. At least, it won't cost them hours of their lives and sanity to fix the issue. But it will cost you.\"}),`\n`,(0,t.jsxs)(e.p,{children:[\"Using a more concrete example, imagine your framework patches the global \",(0,t.jsx)(e.code,{children:\"fetch\"}),\" function to dedupe requests. That works really well for the immediate consumers of \",(0,t.jsx)(e.code,{children:\"fetch\"}),\"\\u2014your users. Now, what would happen if they rely on a third-party SDK that \",(0,t.jsx)(e.em,{children:\"also has a deduplication mechanism built-in\"}),\"? The two behaviors would clash, causing all sorts of weird issues that are virtually impossible to debug.\"]}),`\n`,(0,t.jsxs)(e.p,{children:[\"That is your best case scenario. The worst case scenario is that the third-party was also patching \",(0,t.jsx)(e.code,{children:\"fetch\"}),\" and relying on that patch to exist, which your framework successfully undoes by introducing its own patch. A magnificent manifestation of hack-driven development at its finest.\"]}),`\n`,(0,t.jsx)(e.h2,{children:\"Harm #2: Predictability\"}),`\n`,(0,t.jsxs)(e.p,{children:[\"There's hardly anything more satisfying in engineering than knowing how a thing behaves. If you use \",(0,t.jsx)(e.code,{children:\"console.log()\"}),\", you know it will print a message to the console. Now, it may be the browser's DevTools protocol that does it, or Node.js writing to \",(0,t.jsx)(e.code,{children:\"process.stdout\"}),\", but you \",(0,t.jsx)(e.em,{children:\"know\"}),\" what's going to happen. You can predict the behavior.\"]}),`\n`,(0,t.jsx)(o,{children:(0,t.jsx)(e.p,{children:`There's hardly anything more irritating in engineering than having the same\nthing behave differently.`})}),`\n`,(0,t.jsxs)(e.p,{children:[\"If you've been building for web long enough, I'm sure you have your own barrage of oddities, quirks, and behaviors of questionable sanity corked in memories you never wish to revisit. Remember how \",(0,t.jsx)(e.code,{children:\"display: flex\"}),\" used to work only in some browsers? What about negative lookbehind in Safari? And all those joyful hours spent begging IE to render your page correctly, if at all?\"]}),`\n`,(0,t.jsx)(e.p,{children:\"That's the chaos that happens when multiple agents implement the same thing differently.\"}),`\n`,(0,t.jsxs)(e.p,{children:[\"It makes me appreciate the present even more. Cross-browser compatibility has never been higher. Even cross-environment compatibility is showing incredible progress, with Node.js shipping \",(0,t.jsx)(e.code,{children:\"fetch\"}),\", \",(0,t.jsx)(e.code,{children:\"ReadableStream\"}),\", \",(0,t.jsx)(e.code,{children:\"WebSocket\"}),\", and a dozen of other browser APIs. That didn't happen by accident. We, as an industry, came to the conclusion that nobody wants to waste money and time on compatibility issues. We invested into specifications, we deprecated old and gave way to the new.\"]}),`\n`,(0,t.jsx)(e.p,{children:\"And now we have come a full circle. We are back at the stage where multiple agents implement the same thing differently.\"}),`\n`,(0,t.jsx)(e.p,{children:\"Here's a request that will be cached and then revalidated after 10 seconds in Next.js:\"}),`\n`,(0,t.jsx)(e.pre,{children:(0,t.jsx)(e.code,{className:\"language-js\",children:`fetch('https://kettanaito.com', {\n  cache: 'force-cache',\n  next: { revalidate: 10 },\n})\n`})}),`\n`,(0,t.jsxs)(e.p,{children:[\"Will this request behave the same in Svelte? In Remix? In plain JavaScript, maybe? Not really. The revalidating part happened to be a Next.js-specific API leaking through the global \",(0,t.jsx)(e.code,{children:\"fetch\"}),\". It's a custom API disguised as a global, which drives its predictability to the ground.\"]}),`\n`,(0,t.jsxs)(e.blockquote,{children:[`\n`,(0,t.jsxs)(e.p,{children:[\"I'm intentionally not giving examples of the standard behaviors of \",(0,t.jsx)(e.code,{children:\"fetch\"}),\" working differently across runtimes. Those exist too, but there is also an effort to bridge the gaps and improve specification-compliance. Patching, on the other hand, pulls the rope in the opposite direction entirely.\"]}),`\n`]}),`\n`,(0,t.jsx)(e.h2,{children:\"Harm #3: Learning\"}),`\n`,(0,t.jsx)(e.p,{children:\"A software that disregards the learning experience it creates is a tool destined to fail.\"}),`\n`,(0,t.jsx)(e.p,{children:\"As a library author, I know firsthand how impactful the decisions I make can be for my users. There's already a lot of things a good API must accomplish: achieve the task at hand, provide an intuitive, versatile means to do so, align with the established philosophy and best practices, be extendable and maintainable and transferrable. But there's one more thing, the most important thing it must do.\"}),`\n`,(0,t.jsxs)(e.p,{children:[(0,t.jsx)(e.strong,{children:\"An API must educate\"}),\".\"]}),`\n`,(0,t.jsx)(o,{children:(0,t.jsxs)(e.p,{children:[`A good tool teaches you the concept.\n`,(0,t.jsx)(\"br\",{}),\"A bad tool teaches you itself.\"]})}),`\n`,(0,t.jsx)(e.p,{children:\"Every time a user reaches for your tool, they have a problem to solve. And the solution you provide them is always twofold: the immediate one and the lasting one.\"}),`\n`,(0,t.jsxs)(e.p,{children:[\"For the lack of a better example, I will shamelessly use one from my experience. I spent the last year making MSW more \",(0,t.jsx)(e.a,{href:\"https://mswjs.io/blog/introducing-msw-2.0\",children:\"specification-compliant\"}),\". The goal was to facilitate the standard Fetch API so the developer would interface with it more often. It was a challenge and a limitation I imposed on myself, knowing that the gains would be tremendous.\"]}),`\n`,(0,t.jsx)(e.p,{children:\"See, when developers come to MSW, they want to intercept requests and mock responses. They could do all of that before, but the API was entirely contrived and specific to the library:\"}),`\n`,(0,t.jsx)(e.pre,{children:(0,t.jsx)(e.code,{className:\"language-js\",children:`rest.get('/user', (req, res, ctx) =\u003e {\n  return res(ctx.json({ name: 'John' }))\n})\n`})}),`\n`,(0,t.jsx)(e.p,{children:'This does achieve the goal. This does intercept the request and mock its response. It has that \"immediate\" solution right here, ready to be used. But boy did it have no lasting impact whatsoever because it fundamentally failed to educate.'}),`\n`,(0,t.jsx)(e.p,{children:\"Does this API teach you how requests are handled on the web? Does it let you explore what responses are, how to construct them? No, not really. Does it utilize the knowledge you already have to make your experience better? Nae.\"}),`\n`,(0,t.jsx)(e.p,{children:(0,t.jsx)(e.strong,{children:\"Does it make you a better engineer?\"})}),`\n`,(0,t.jsx)(e.p,{children:\"Anyway, let's return to our muttons. What does this example teach me about caching?\"}),`\n`,(0,t.jsx)(e.pre,{children:(0,t.jsx)(e.code,{className:\"language-js\",children:`fetch('https://kettanaito', {\n  cache: 'force-cache',\n  next: { revalidate: 10 },\n})\n`})}),`\n`,(0,t.jsxs)(e.p,{children:[\"Well, for once, that requests \",(0,t.jsx)(e.em,{children:\"can\"}),\" be cached and that there's a standard \",(0,t.jsx)(e.a,{href:\"https://fetch.spec.whatwg.org/#ref-for-dom-request-cache%E2%91%A1\",children:(0,t.jsx)(e.code,{children:\"cache\"})}),\" property that controls how the browser caches that request. So far so good! But that's the standard teaching me, not Next.js.\"]}),`\n`,(0,t.jsxs)(e.p,{children:[\"The \",(0,t.jsx)(e.code,{children:\"next.revalidate\"}),\" part is entirely contrived. Even if I looked it up in the Next.js docs, I wouldn't know what exactly it abstracts over. Does it control cache revalidation or does it do something else entirely? It certainly doesn't teach me about the \",(0,t.jsx)(e.a,{href:\"https://httpwg.org/specs/rfc9111.html#cache-request-directive.max-age\",children:(0,t.jsx)(e.code,{children:\"max-age\"})}),\" request directive of the standard \",(0,t.jsx)(e.code,{children:\"Cache-Control\"}),\" HTTP header.\"]}),`\n`,(0,t.jsxs)(e.p,{children:[\"But most importantly, can I write the same logic using plain \",(0,t.jsx)(e.code,{children:\"fetch\"}),\"?\"]}),`\n`,(0,t.jsx)(e.pre,{children:(0,t.jsx)(e.code,{className:\"language-js\",children:`fetch('https://kettanaito', {\n  cache: 'force-cache',\n  headers: {\n    'Cache-Control': 'max-age=10',\n  },\n})\n`})}),`\n`,(0,t.jsxs)(e.blockquote,{children:[`\n`,(0,t.jsxs)(e.p,{children:[\"I genuinely didn't know. That's why I've reached out to \",(0,t.jsx)(e.a,{href:\"https://twitter.com/leeerob\",children:\"Lee\"}),\" for help. He kindly explained to me that \",(0,t.jsx)(e.code,{children:\"next.revalidate\"}),\" focuses on \",(0,t.jsx)(e.a,{href:\"https://nextjs.org/docs/pages/building-your-application/data-fetching/incremental-static-regeneration\",children:\"Incremental Static Regeneration\"}),\" (ISR), not just caching. So no, I cannot translate it to a plain \",(0,t.jsx)(e.code,{children:\"fetch\"}),\" call. Seeing how tightly it integrates with Next.js, I believe it belongs to \",(0,t.jsx)(e.code,{children:\"RequestInit\"}),\" even less.\"]}),`\n`]}),`\n`,(0,t.jsxs)(e.p,{children:[\"As powerful as \",(0,t.jsx)(e.code,{children:\"next.revalidate\"}),\" is, I wish it taught me more about what exactly it does so I would get better at understanding caching and ISR. Ideally, while also relying on the existing standards that I can apply outside of Next.js too.\"]}),`\n`,(0,t.jsx)(e.h2,{children:\"Harm #4: Lock-in\"}),`\n`,(0,t.jsx)(e.p,{children:\"The more sugar your framework sprinkles on top of standard APIs, the harder it is to move away from that framework.\"}),`\n`,(0,t.jsx)(e.p,{children:\"I would hate to be an engineer who has to migrate this from Bun:\"}),`\n`,(0,t.jsx)(e.pre,{children:(0,t.jsx)(e.code,{className:\"language-js\",children:`fetch('https://redd.one', {\n  proxy: 'https://kettanaito.com',\n})\n`})}),`\n`,(0,t.jsxs)(e.p,{children:[(0,t.jsx)(e.strong,{children:\"Because I have no idea what Bun is doing here\"}),\". I know what it's supposed to do from the docs but the reality is often such that there are dozens of things that are happening under the hood. There's no specification for how \",(0,t.jsx)(e.code,{children:\"proxy\"}),\" behaves. Now I have to match all of its behaviors while migrating because each oddity missed is a new bug in my app waiting to happen.\"]}),`\n`,(0,t.jsx)(e.p,{children:\"You took a thing you don't own, you modified it on the premise of convenience, and now it doesn't work anywhere else but in your own little universe. If that is not the definition of vendor lock-in, I don't know what is.\"}),`\n`,(0,t.jsx)(e.h2,{children:\"Harm #5: Hurting the progress\"}),`\n`,(0,t.jsx)(e.p,{children:\"What appears like a pioneering of innovation ends up being one big spike in the wheel of progress. A really, really big spike.\"}),`\n`,(0,t.jsxs)(e.p,{children:[\"Here's a quick history lesson for you. In the hairy days of the Internet, there was a library called \",(0,t.jsx)(e.a,{href:\"https://mootools.net/\",children:\"MooTools\"}),\". It was a utility library to help developers survive the unfathomable chaos which was webdev some 20 years ago. Its goal was noble, its means, well, not so much.\"]}),`\n`,(0,t.jsxs)(e.p,{children:[\"See, MooTools worked by imbuing the prototypes of global JavaScript objects with extra functionality. In return, you got a ton of useful methods, like \",(0,t.jsx)(e.code,{children:\"Array.prototype.getLast()\"}),\", \",(0,t.jsx)(e.code,{children:\"Function.prototype.attempt()\"}),\", and \",(0,t.jsx)(e.code,{children:\"Number.prototype.limit()\"}),\". None of those really existed in JavaScript but, in the end, it was in such a sorry state, to begin with. Nobody really cared.\"]}),`\n`,(0,t.jsx)(e.p,{children:\"Until more and more sites got built with MooTools. And when the time came to define the next chapter for ECMAScript, to address all those pains and make the language better, the committee was faced with a conundrum. They wanted to introduce new prototype methods on primitives but couldn't because half of the web relied on the same methods from MooTools. Shipping those methods as a part of the language meant breaking half the Internet, which the committee found the wisdom not to do.\"}),`\n`,(0,t.jsxs)(e.p,{children:[\"They had to bend the spec to reality. So we got \",(0,t.jsx)(e.code,{children:\"String.prototype.includes()\"}),\" instead of \",(0,t.jsx)(e.code,{children:\"String.prototype.contains()\"}),\" to stay consistent with \",(0,t.jsx)(e.code,{children:\"Array\"}),\".\"]}),`\n`,(0,t.jsx)(e.p,{children:\"Let me make this abundantly clear:\"}),`\n`,(0,t.jsx)(o,{children:(0,t.jsx)(e.p,{children:`The language itself couldn't become more convenient because developers were\naddicted to a convenience library that modified things it didn't own.`})}),`\n`,(0,t.jsxs)(e.blockquote,{children:[`\n`,(0,t.jsxs)(e.p,{children:[\"MooTools has also spawned a \",(0,t.jsx)(e.a,{href:\"https://developer.chrome.com/blog/smooshgate\",children:\"Smoosh controversy\"}),\", causing a newly added \",(0,t.jsx)(e.code,{children:\"Array.prototype.flatten\"}),\" to break the web and forcing the TC39 folks to rename a perfectly valid method name to \",(0,t.jsx)(e.code,{children:\"flat\"}),\".\"]}),`\n`]}),`\n`,(0,t.jsxs)(e.p,{children:[\"MooTools has passed into history, but its impact on JavaScript remained. So next time you choose to support short-sighted convenience, please do not complain if the WHATWG introduces a \",(0,t.jsx)(e.code,{children:\"proxyTarget\"}),\" and \",(0,t.jsx)(e.code,{children:\"proxyOptions\"}),\" properties to the \",(0,t.jsx)(e.code,{children:\"fetch\"}),\" API instead of a single \",(0,t.jsx)(e.code,{children:\"proxy\"}),\".\"]}),`\n`,(0,t.jsxs)(e.blockquote,{children:[`\n`,(0,t.jsx)(e.p,{children:\"I admit, I don't know the details on why MooTools chose prototype patching as their design direction. I will give them the benefit of the doubt, assuming they thought it the best way for their features to feel like the extension of the language instead of a vendor library. Perhaps they simply didn't know better and there was no other example to learn from. I am thankful they have become one for the rest of us.\"}),`\n`]}),`\n`,(0,t.jsx)(e.p,{children:\"A truly fascinating part here is that none of these costs and harms would even be relevant if one approached convenience with a bit more thought and precaution. It's only proper we looked at alternatives to patching globals (I can't believe I had to write this sentence).\"}),`\n`,(0,t.jsx)(e.h2,{children:\"Alternatives\"}),`\n`,(0,t.jsx)(e.p,{children:\"There's a widespread misbelief that convenience either comes directly from the spec or comes at the cost of violating one. I really don't know where that stance is coming from so let me clarify this:\"}),`\n`,(0,t.jsx)(o,{children:(0,t.jsx)(e.p,{children:\"Being against patching globals doesn't mean being against convenience.\"})}),`\n`,(0,t.jsx)(e.p,{children:\"I am all hands for convenience and yet I hate whenever frameworks patch globals.\"}),`\n`,(0,t.jsx)(e.p,{children:\"But there is one thing I hate even more. Complaining without proposing solutions.\"}),`\n`,(0,t.jsxs)(e.p,{children:[\"There are a ton of solutions to any API problem better than patching globals, and the quintessential part of any of them is \",(0,t.jsx)(e.strong,{children:\"being explicit\"}),\". Abandon the magical, cast aside the pretence that you own \",(0,t.jsx)(e.code,{children:\"fetch\"}),\" and it is only through modifying it that you can achieve that golden DX you dream about. That DX is fundamentally and irreversibly flawed, and I've just given you five extremely detailed reasons above as to why.\"]}),`\n`,(0,t.jsxs)(e.p,{children:[\"Let's take Bun's \",(0,t.jsx)(e.code,{children:\"proxy\"}),\" API as an example and make it explicit:\"]}),`\n`,(0,t.jsx)(e.pre,{children:(0,t.jsx)(e.code,{className:\"language-js\",children:`import { proxy } from 'bun'\n\nconst proxiedRequest = proxy(proxyUrl, new Request(url, options))\nawait fetch(proxiedRequest)\n`})}),`\n`,(0,t.jsxs)(e.p,{children:[\"With this change, the proxying functionality no longer appears from thin air. It has to be imported from \",(0,t.jsx)(e.code,{children:\"bun\"}),\" explicitly. Nobody will confuse it with a standard JavaScript behavior. In fact, developers will be reminded on every import that it is Bun who's making proxying easier. Here's your developer-oriented marketing as a side effect of a good API design!\"]}),`\n`,(0,t.jsxs)(e.p,{children:[\"But we can no longer shove it into \",(0,t.jsx)(e.code,{children:\"fetch\"}),\" so we have to be smarter about our \",(0,t.jsx)(e.code,{children:\"proxy\"}),\" function. We want to proxy a request, right? So, maybe, let's accept \",(0,t.jsx)(e.em,{children:\"any request instance\"}),\" as an argument and return a proxied request? It doesn't interfere with how a \",(0,t.jsx)(e.code,{children:\"Request\"}),\" is constructed or a \",(0,t.jsx)(e.code,{children:\"fetch\"}),\" call is made all the while achieving the same proxying functionality? Get out of here.\"]}),`\n`,(0,t.jsxs)(e.p,{children:[\"As a result of being an explicit utility, our \",(0,t.jsx)(e.code,{children:\"proxy\"}),\" function doesn't suffer any of the drawbacks inherent to patching globals. It's a function owned by the framework, which makes it easy to version, maintain, modify, and deprecate. It's predictable because you clearly see where it's coming from and what it does. Most importantly, it doesn't masquerade as a native JavaScript behavior. It's extremely easy to learn: you go to the Bun's documentation, see the function's call signature, and . . . Wait, what is \",(0,t.jsx)(e.code,{children:\"request: Request\"}),\"? What, I have to learn how to construct requests on the web now? Damn you, Bun, I didn't ask you to make me smarter! There's no lock-in by design, and if you wish to play chaotically good here, you can even publish \",(0,t.jsx)(e.code,{children:\"proxy\"}),\" for other runtimes to use, boosting everybody's awareness of your project and perhaps even incentivising it becoming a standard. And, finally, you start to inspire and incite progress instead of pretending it has happened already, the others just haven't caught up yet.\"]}),`\n`,(0,t.jsx)(e.p,{children:\"Do you see how a simple change made this API better? I'll give it to you, it's likely not production-ready because I had to spent the entirety of 10 seconds coming up with this example. But I bet with a team of smart people and a week or two of discussion, it would have become another small reason to switch over to Bun. Who knows, who knows.\"}),`\n`,(0,t.jsx)(e.p,{children:\"Since the ball is already rolling, let's refactor the APIs from React and Next.js as well. This is going to be mostly boring because we will apply the same pattern to all of them, ridding them of the same problems and reaping the same benefits.\"}),`\n`,(0,t.jsx)(e.pre,{children:(0,t.jsx)(e.code,{className:\"language-js\",children:`import { cache } from 'react'\n\n// There's no reason to couple the data fetching\n// function with React. By leaving it generic, it\n// can be reused and tested with fewer dependencies.\nfunction getUser() {\n  const response = await fetch('https://api.example.com/user')\n  return response.json()\n}\n\nexport async function UserProfile() {\n  // Instead, make caching (request deduplication)\n  // an explicit choice where we actually need it\\u2014\n  // when fetching the data for our component.\n  const profile = await cache(getUser())\n\n  return \u003cp\u003eHello, {profile.name}!\u003c/p\u003e\n}\n`})}),`\n`,(0,t.jsx)(e.pre,{children:(0,t.jsx)(e.code,{className:\"language-js\",children:`import { withCache } from 'next'\n\n// A regular request instance.\nconst request = new Request('https://kettanaito.com')\n\n// A regular fetch call that accepts a modified\n// request instance that instructs Next.js how to\n// cache this request.\nfetch(withCache(request, { revalidate: 10 }))\n`})}),`\n`,(0,t.jsxs)(e.blockquote,{children:[`\n`,(0,t.jsxs)(e.p,{children:[\"It speaks volumes that both React and Next.js are already in a process of migrating away from patching \",(0,t.jsx)(e.code,{children:\"fetch\"}),\" by introducing their \",(0,t.jsx)(e.a,{href:\"https://react.dev/reference/react/cache\",children:(0,t.jsx)(e.code,{children:\"cache\"})}),\" and \",(0,t.jsx)(e.a,{href:\"https://nextjs.org/docs/app/api-reference/functions/unstable_cache\",children:(0,t.jsx)(e.code,{children:\"unstable_cache\"})}),\" APIs, respectivelly.\"]}),`\n`]}),`\n`,(0,t.jsx)(e.p,{children:\"The suggestions I'm proposing are also less invasive, as they operate on existing globals and behaviors as opposed to modifying them.\"}),`\n`,(0,t.jsx)(e.h2,{children:\"Common counter-arguments\"}),`\n`,(0,t.jsx)(e.p,{children:\"The amount of backlash toward the criticism of patching globals online made me realize most people lack the fundamental understanding of the matter. Below, I will address most common counter-arguments against my concerns just in case the article failed to convince you that meddling with globals is a terrible idea.\"}),`\n`,(0,t.jsx)(e.h3,{children:\"But who cares?\"}),`\n`,(0,t.jsx)(e.p,{children:\"Not enough of us, sadly. Putting a jelly sandwich on a bus seat won't affect the entire bus, but does it make it a good thing to do?\"}),`\n`,(0,t.jsx)(e.h3,{children:\"But it's a better developer experience!\"}),`\n`,(0,t.jsx)(e.p,{children:\"If all the extensive explanations of this article failed to convinice you that patching globals is never a good developer experience, I fear nothing will.\"}),`\n`,(0,t.jsx)(e.h3,{children:\"Bun is a custom runtime, there is no patching!\"}),`\n`,(0,t.jsxs)(e.p,{children:[\"Bun is built on a promise of Node.js compatibility. Node.js implements \",(0,t.jsx)(e.code,{children:\"fetch\"}),\" according to the WHATWG Fetch standard, and so Bun does too. The standard defines \",(0,t.jsx)(e.code,{children:\"fetch\"}),\" in its entirety. With addition of custom properties, Bun is patching fetch, deviating from the standard and introducing all the issues I've talked about prior.\"]}),`\n`,(0,t.jsx)(e.h3,{children:\"But Node.js does weird stuff too!\"}),`\n`,(0,t.jsx)(e.p,{children:\"Of course it does! It's a project with 15 years of history behind it. I swim in that weird stuff on a daily basis at work. We should learn from its oddities and lead by example, not use them as an excuse to implement harmful APIs.\"}),`\n`,(0,t.jsxs)(e.h3,{children:[\"But \",(0,t.jsx)(e.code,{children:\"fetch\"}),\" doesn't exist on the server!\"]}),`\n`,(0,t.jsxs)(e.p,{children:[\"Global \",(0,t.jsx)(e.code,{children:\"fetch\"}),\" API exists in Node.js since 2022 (introduced as experimental in v17). \",(0,t.jsx)(e.a,{href:\"https://github.com/nodejs/undici\",children:\"Undici\"}),\" is the official implementer of that API and is a dependency to Node.js. People have been using it on the server for years now.\"]}),`\n`,(0,t.jsx)(e.h3,{children:\"But these frameworks know better!\"}),`\n`,(0,t.jsx)(e.p,{children:'They likely do! But in some cases, it takes them time and the feedback from the community to arrive at that \"better\".'}),`\n`,(0,t.jsxs)(e.p,{children:[\"As I've mentioned before, React and Next.js are moving away from patching \",(0,t.jsx)(e.code,{children:\"fetch\"}),\" in favor of more explicit APIs. When I asked Lee Robinson to provide more context behind that switch, he shared with me the following:\"]}),`\n`,(0,t.jsxs)(e.blockquote,{children:[`\n`,(0,t.jsxs)(e.p,{children:[(0,t.jsxs)(e.em,{children:[\"In theory, community packages could publish their own code that used \",(0,t.jsx)(e.code,{children:\"fetch\"}),\", and set these caching policies on their own. And we started to do that a bit, but it was very opaque and hard to tell why something was cached. So we decided \",(0,t.jsx)(e.strong,{children:\"we want to be more explicit\"})]}),`.\n\\u2014 Lee Robinson, VP of Product at Vercel.`]}),`\n`]}),`\n`,(0,t.jsx)(e.p,{children:\"Perhaps we will even see Bun reconsider their direction of patching globals, which would immediately make it a more mature runtime, at least in my eyes.\"}),`\n`,(0,t.jsx)(e.p,{children:\"The bottom line is, there isn't enough amount of experience, skill, or credibility that would render you immune to making a mistake. The only thing that matters is having the strength to admit one and the wisdom to resolve it.\"}),`\n`,(0,t.jsx)(e.hr,{}),`\n`,(0,t.jsx)(e.h2,{children:\"Conclusion\"}),`\n`,(0,t.jsx)(e.p,{children:`I hope this article was able to shed some light on the dangers of apparently \"controversial\" practice of patching globals. It's a footgun we have already fired, and seems we needed a due reminder that the feet we have are just two.`}),`\n`,(0,t.jsxs)(e.blockquote,{children:[`\n`,(0,t.jsxs)(e.p,{children:[\"Huge thanks to \",(0,t.jsx)(e.a,{href:\"https://twitter.com/leeerob\",children:\"Lee Robinson\"}),\" and \",(0,t.jsx)(e.a,{href:\"https://twitter.com/ljharb\",children:\"Jordan Harband\"}),\" for proofreading this piece! Your feedback helped me ensure my example were accurate and my tone welcoming. I'm truly honored to have had your eyes on this one.\"]}),`\n`]}),`\n`,(0,t.jsx)(e.h2,{children:\"Resources\"}),`\n`,(0,t.jsx)(e.p,{children:\"There are a lot of great writing on this topic. I encourage you to read these:\"}),`\n`,(0,t.jsxs)(e.ul,{children:[`\n`,(0,t.jsxs)(e.li,{children:[(0,t.jsx)(e.a,{href:\"https://www.audero.it/blog/2016/12/05/monkey-patching-javascript\",children:\"Monkey-Patching in JavaScript\"}),\" by Aurelio De Rosa;\"]}),`\n`,(0,t.jsxs)(e.li,{children:[(0,t.jsx)(e.a,{href:\"https://shopify.engineering/the-case-against-monkey-patching\",children:\"The Case Against Monkey-Patching\"}),\" by Eileen Uchitelle;\"]}),`\n`,(0,t.jsxs)(e.li,{children:[(0,t.jsx)(e.a,{href:\"https://swizec.com/blog/that-time-monkey-patching-took-2-days-off-my-life\",children:\"That Time Monkey-Patching Took 2 Days Off My Life\"}),\" by Swizec Teller;\"]}),`\n`,(0,t.jsxs)(e.li,{children:[(0,t.jsx)(e.a,{href:\"https://humanwhocodes.com/blog/2010/03/02/maintainable-javascript-dont-modify-objects-you-down-own\",children:\"Maintainable JavaScript: Don't modify objects you don't own\"}),\" by Nicholas C. Zakas.\"]}),`\n`]}),`\n`,(0,t.jsxs)(e.p,{children:[\"You are also most welcome to share \",(0,t.jsx)(e.em,{children:\"this\"}),\" article any time you notice a framework is reaching toward patching a global. If it helps to prevent that, I know I will be glad.\"]})]})}function A(n={}){let{wrapper:e}=n.components||{};return e?(0,t.jsx)(e,Object.assign({},n,{children:(0,t.jsx)(d,n)})):d(n)}var j=A;function T(n,e){throw new Error(\"Expected \"+(e?\"component\":\"object\")+\" `\"+n+\"` to be defined: you likely forgot to import, pass, or provide it.\")}return k(q);})();\n;return Component;","slug":"why-patching-globals-is-harmful","url":"/blog/why-patching-globals-is-harmful","thumbnailSvg":"\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\u003csvg viewBox=\"0 0 384 472\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\u003e\u003cdefs\u003e\u003cpath d=\"M82,0 C108.509668,-4.86974701e-15 130,21.490332 130,48 L130,290 C130,316.509668 108.509668,338 82,338 L48,338 C21.490332,338 1.623249e-15,316.509668 0,290 L0,48 C-3.24649801e-15,21.490332 21.490332,3.24649801e-15 48,0 L82,0 Z M49,295 C44.581722,295 41,298.581722 41,303 C41,307.418278 44.581722,311 49,311 C53.418278,311 57,307.418278 57,303 C57,298.581722 53.418278,295 49,295 Z M81,295 C76.581722,295 73,298.581722 73,303 C73,307.418278 76.581722,311 81,311 C85.418278,311 89,307.418278 89,303 C89,298.581722 85.418278,295 81,295 Z M97,271 C92.581722,271 89,274.581722 89,279 C89,283.418278 92.581722,287 97,287 C101.418278,287 105,283.418278 105,279 C105,274.581722 101.418278,271 97,271 Z M65,271 C60.581722,271 57,274.581722 57,279 C57,283.418278 60.581722,287 65,287 C69.418278,287 73,283.418278 73,279 C73,274.581722 69.418278,271 65,271 Z M33,271 C28.581722,271 25,274.581722 25,279 C25,283.418278 28.581722,287 33,287 C37.418278,287 41,283.418278 41,279 C41,274.581722 37.418278,271 33,271 Z M97,51 C92.581722,51 89,54.581722 89,59 C89,63.418278 92.581722,67 97,67 C101.418278,67 105,63.418278 105,59 C105,54.581722 101.418278,51 97,51 Z M65,51 C60.581722,51 57,54.581722 57,59 C57,63.418278 60.581722,67 65,67 C69.418278,67 73,63.418278 73,59 C73,54.581722 69.418278,51 65,51 Z M33,51 C28.581722,51 25,54.581722 25,59 C25,63.418278 28.581722,67 33,67 C37.418278,67 41,63.418278 41,59 C41,54.581722 37.418278,51 33,51 Z M49,27 C44.581722,27 41,30.581722 41,35 C41,39.418278 44.581722,43 49,43 C53.418278,43 57,39.418278 57,35 C57,30.581722 53.418278,27 49,27 Z M81,27 C76.581722,27 73,30.581722 73,35 C73,39.418278 76.581722,43 81,43 C85.418278,43 89,39.418278 89,35 C89,30.581722 85.418278,27 81,27 Z\" id=\"mrd6vsn__path-1\"/\u003e\u003c/defs\u003e\u003cg id=\"mrd6vsn__thumbnail\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\"\u003e\u003cg id=\"mrd6vsn__bandage\" transform=\"translate(192, 236) rotate(45) translate(-192, -236)translate(127, 67)\"\u003e\u003cmask id=\"mrd6vsn__mask-2\" fill=\"white\"\u003e\u003cuse xlink:href=\"#mrd6vsn__path-1\"/\u003e\u003c/mask\u003e\u003cuse id=\"mrd6vsn__body\" fill=\"#FFAC4D\" xlink:href=\"#mrd6vsn__path-1\"/\u003e\u003cpath d=\"M106,108 C117.045695,108 127.045695,112.477153 134.284271,119.715729 C141.522847,126.954305 146,136.954305 146,148 L146,169 L-16,169 L-16,148 C-16,136.954305 -11.5228475,126.954305 -4.28427125,119.715729 C2.954305,112.477153 12.954305,108 24,108 L106,108 Z\" id=\"mrd6vsn__shadow-top\" fill=\"#FFF5C2\" mask=\"url(#mrd6vsn__mask-2)\"/\u003e\u003cpath d=\"M106,169 C117.045695,169 127.045695,173.477153 134.284271,180.715729 C141.522847,187.954305 146,197.954305 146,209 L146,230 L-16,230 L-16,209 C-16,197.954305 -11.5228475,187.954305 -4.28427125,180.715729 C2.954305,173.477153 12.954305,169 24,169 L106,169 Z\" id=\"mrd6vsn__shadow-bottom\" fill=\"#CF772E\" mask=\"url(#mrd6vsn__mask-2)\" transform=\"translate(65, 199.5) scale(1, -1) translate(-65, -199.5)\"/\u003e\u003cpath d=\"M24,124 L106,124 C119.254834,124 130,134.745166 130,148 L130,190 C130,203.254834 119.254834,214 106,214 L24,214 C10.745166,214 0,203.254834 0,190 L0,148 C0,134.745166 10.745166,124 24,124 Z\" id=\"mrd6vsn__middle\" fill=\"#FFAC4D\" mask=\"url(#mrd6vsn__mask-2)\"/\u003e\u003cg id=\"mrd6vsn__DO-NOT-USE\" mask=\"url(#mrd6vsn__mask-2)\" fill=\"#79320E\" fill-rule=\"nonzero\"\u003e\u003cg transform=\"translate(28.315, 145.5)\"\u003e\u003cpath d=\"M-9.09882781e-14,16.5 L-9.09882781e-14,6.3159836e-14 L7.348,6.3159836e-14 C8.34533333,6.3159836e-14 9.20333333,0.363 9.922,1.089 C10.6406667,1.815 11,2.67666667 11,3.674 L11,12.826 C11,13.8233333 10.6406667,14.685 9.922,15.411 C9.20333333,16.137 8.34533333,16.5 7.348,16.5 L-9.09882781e-14,16.5 Z M3.674,14.674 L5.5,14.674 C6.01333333,14.674 6.44966667,14.4906667 6.809,14.124 C7.16833333,13.7573333 7.348,13.3246667 7.348,12.826 L7.348,3.674 C7.348,3.16066667 7.16833333,2.72433333 6.809,2.365 C6.44966667,2.00566667 6.01333333,1.826 5.5,1.826 L3.674,1.826 L3.674,14.674 Z\" id=\"mrd6vsn__Shape\"/\u003e\u003cpath d=\"M18.348,5.55290509e-14 L22.022,5.55290509e-14 C23.0193333,5.55290509e-14 23.8773333,0.363 24.596,1.089 C25.3146667,1.815 25.674,2.67666667 25.674,3.674 L25.674,12.826 C25.674,13.8233333 25.3146667,14.685 24.596,15.411 C23.8773333,16.137 23.0193333,16.5 22.022,16.5 L18.348,16.5 C17.3506667,16.5 16.489,16.1406667 15.763,15.422 C15.037,14.7033333 14.674,13.838 14.674,12.826 L14.674,3.674 C14.674,2.662 15.037,1.79666667 15.763,1.078 C16.489,0.359333333 17.3506667,5.55290509e-14 18.348,5.55290509e-14 Z M18.348,3.674 L18.348,12.826 C18.348,13.3246667 18.5276667,13.7573333 18.887,14.124 C19.2463333,14.4906667 19.6753333,14.674 20.174,14.674 C20.6873333,14.674 21.1236667,14.4906667 21.483,14.124 C21.8423333,13.7573333 22.022,13.3246667 22.022,12.826 L22.022,3.674 C22.022,3.16066667 21.8423333,2.72433333 21.483,2.365 C21.1236667,2.00566667 20.6873333,1.826 20.174,1.826 C19.6753333,1.826 19.2463333,2.00566667 18.887,2.365 C18.5276667,2.72433333 18.348,3.16066667 18.348,3.674 Z\" id=\"mrd6vsn__Shape\"/\u003e\u003cpolygon id=\"mrd6vsn__Path\" points=\"34.848 16.5 34.848 7.0294782e-14 35.706 7.0294782e-14 45.848 9.174 45.848 7.0294782e-14 47.696 7.0294782e-14 47.696 16.5 46.86 16.5 36.696 7.326 36.696 16.5\"/\u003e\u003cpath d=\"M55.022,4.04465448e-14 L58.696,4.04465448e-14 C59.6933333,4.04465448e-14 60.5513333,0.363 61.27,1.089 C61.9886667,1.815 62.348,2.67666667 62.348,3.674 L62.348,12.826 C62.348,13.8233333 61.9886667,14.685 61.27,15.411 C60.5513333,16.137 59.6933333,16.5 58.696,16.5 L55.022,16.5 C54.0246667,16.5 53.163,16.1406667 52.437,15.422 C51.711,14.7033333 51.348,13.838 51.348,12.826 L51.348,3.674 C51.348,2.662 51.711,1.79666667 52.437,1.078 C53.163,0.359333333 54.0246667,4.04465448e-14 55.022,4.04465448e-14 Z M55.022,3.674 L55.022,12.826 C55.022,13.3246667 55.2016667,13.7573333 55.561,14.124 C55.9203333,14.4906667 56.3493333,14.674 56.848,14.674 C57.3613333,14.674 57.7976667,14.4906667 58.157,14.124 C58.5163333,13.7573333 58.696,13.3246667 58.696,12.826 L58.696,3.674 C58.696,3.16066667 58.5163333,2.72433333 58.157,2.365 C57.7976667,2.00566667 57.3613333,1.826 56.848,1.826 C56.3493333,1.826 55.9203333,2.00566667 55.561,2.365 C55.2016667,2.72433333 55.022,3.16066667 55.022,3.674 Z\" id=\"mrd6vsn__Shape\"/\u003e\u003cpolygon id=\"mrd6vsn__Path\" points=\"67.87 16.5 67.87 1.826 64.196 1.826 64.196 3.83841598e-14 75.196 3.83841598e-14 75.196 1.826 71.522 1.826 71.522 16.5\"/\u003e\u003cpath d=\"M25.696,28 L29.348,28 L29.348,40.826 C29.348,41.8233333 28.9886667,42.685 28.27,43.411 C27.5513333,44.137 26.6933333,44.5 25.696,44.5 L22.022,44.5 C21.0246667,44.5 20.163,44.1406667 19.437,43.422 C18.711,42.7033333 18.348,41.838 18.348,40.826 L18.348,28 L22.022,28 L22.022,40.826 C22.022,41.3246667 22.2016667,41.7573333 22.561,42.124 C22.9203333,42.4906667 23.3493333,42.674 23.848,42.674 C24.3613333,42.674 24.7976667,42.4906667 25.157,42.124 C25.5163333,41.7573333 25.696,41.3246667 25.696,40.826 L25.696,28 Z\" id=\"mrd6vsn__Path\"/\u003e\u003cpath d=\"M42.196,31.674 L40.37,31.674 C40.2086667,30.442 39.6293333,29.826 38.632,29.826 C37.9866667,29.826 37.5026667,29.98 37.18,30.288 C36.8573333,30.596 36.696,31.058 36.696,31.674 C36.696,32.1286667 36.872,32.5613333 37.224,32.972 L41.118,37.328 C41.8366667,38.1346667 42.196,39 42.196,39.924 L42.196,40.826 C42.196,41.8233333 41.8366667,42.685 41.118,43.411 C40.3993333,44.137 39.534,44.5 38.522,44.5 L36.696,44.5 C35.4786667,44.5 34.562,44.1956667 33.946,43.587 C33.33,42.9783333 33.022,42.058 33.022,40.826 L34.87,40.826 C34.87,40.958 34.87,41.0606667 34.87,41.134 C34.87,41.6326667 35.0606667,42.014 35.442,42.278 C35.8233333,42.542 36.2413333,42.674 36.696,42.674 C37.3413333,42.674 37.807,42.5163333 38.093,42.201 C38.379,41.8856667 38.522,41.4273333 38.522,40.826 C38.522,40.3566667 38.346,39.924 37.994,39.528 L34.1,35.172 C33.3813333,34.38 33.022,33.5146667 33.022,32.576 L33.022,31.674 C33.022,30.6766667 33.3813333,29.815 34.1,29.089 C34.8186667,28.363 35.684,28 36.696,28 L38.522,28 C40.8246667,28 42.0493333,29.2246667 42.196,31.674 Z\" id=\"mrd6vsn__Path\"/\u003e\u003cpolygon id=\"mrd6vsn__Path\" points=\"45.848 44.5 45.848 28 55.022 28 55.022 29.826 49.522 29.826 49.522 35.326 53.196 35.326 53.196 37.174 49.522 37.174 49.522 42.674 55.022 42.674 55.022 44.5\"/\u003e\u003c/g\u003e\u003c/g\u003e\u003c/g\u003e\u003c/g\u003e\u003c/svg\u003e","frontmatter":{"title":"Why Patching Globals Is Harmful","description":"Why I believe patching globals is a bad API design.","category":"Engineering","date":"2024-05-07T00:00:00.000Z","keywords":["patch","monkey-patch","globals","fetch","harm","mootools"]}},{"id":"dcrhnpq","code":"var Component=(()=\u003e{var B=Object.create;var g=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var e=Object.getOwnPropertyNames;var C=Object.getPrototypeOf,w=Object.prototype.hasOwnProperty;var u=(D,_)=\u003e()=\u003e(D\u0026\u0026(_=D(D=0)),_);var x=(D,_)=\u003e()=\u003e(_||D((_={exports:{}}).exports,_),_.exports),N=(D,_)=\u003e{for(var s in _)g(D,s,{get:_[s],enumerable:!0})},v=(D,_,s,c)=\u003e{if(_\u0026\u0026typeof _==\"object\"||typeof _==\"function\")for(let G of e(_))!w.call(D,G)\u0026\u0026G!==s\u0026\u0026g(D,G,{get:()=\u003e_[G],enumerable:!(c=m(_,G))||c.enumerable});return D};var n=(D,_,s)=\u003e(s=D!=null?B(C(D)):{},v(_||!D||!D.__esModule?g(s,\"default\",{value:D,enumerable:!0}):s,D)),z=D=\u003ev(g({},\"__esModule\",{value:!0}),D);var l=u(()=\u003e{});var k=x((K,O)=\u003e{l();O.exports=_jsx_runtime});var Y={};N(Y,{default:()=\u003eb,frontmatter:()=\u003ei});l();var o=n(k());var W=\"/blog/my-struggle-with-remix/remix-error-wall-E3UPEY6L.png\";var i={title:\"My Struggle With Remix\",description:\"Remix is a fantastic framework but it's not without its issues. Here are some of my struggles after building a few different projects with it.\",category:\"Engineering\",date:new Date(16836768e5),keywords:[\"remix\",\"struggle\",\"issues\",\"framework\",\"mdx\"]};function y(D){let _=Object.assign({p:\"p\",strong:\"strong\",ol:\"ol\",li:\"li\",h2:\"h2\",em:\"em\",code:\"code\",blockquote:\"blockquote\",a:\"a\",pre:\"pre\",img:\"img\"},D.components),{Quote:s}=_;return s||d(\"Quote\",!0),(0,o.jsxs)(o.Fragment,{children:[(0,o.jsxs)(_.p,{children:[\"I've been putting off this article for way too long. It's time for me to talk about my experience with Remix, and my struggle with it in particular. I won't lie, part of the reason I was reluctant to write this piece was the community the framework has cultivated around itself. Everybody is \",(0,o.jsx)(_.strong,{children:\"extremely\"}),\" excited about Remix. I get it, I do, the framework is fantastic and brings a ton of innovation that the competitors are catching up to years later. And yet this unanimous praise makes it difficult to criticize the thing and, trust me, there are points to bring up when talking about any framework, library, or tool, and Remix is no exception here.\"]}),`\n`,(0,o.jsx)(_.p,{children:\"In light of all the tomatoes you've picked up to throw at me, let's settle on a few things before you read any further, alright?\"}),`\n`,(0,o.jsxs)(_.ol,{children:[`\n`,(0,o.jsxs)(_.li,{children:[\"I think \",(0,o.jsx)(_.strong,{children:\"Remix is a great framework\"}),\". It's been praised countless times and I find most of that praise well-deserved.\"]}),`\n`,(0,o.jsxs)(_.li,{children:[(0,o.jsx)(_.strong,{children:\"I will not be mentioning Remix's advantages\"}),\". This isn't a comparison article and neither is it a proper critique. If you haven't heard of Remix before, I'd suggest you go do that, build an app or two on your own, and then come back here. I don't wish to make you biased toward what could've been your favorite framework for years to come.\"]}),`\n`,(0,o.jsxs)(_.li,{children:[\"I am going to write about \",(0,o.jsx)(_.strong,{children:\"my subjective experience\"}),\". It may not coincide with yours. It may be the exact opposite of yours. That doesn't invalidate either of them so let's remain civil and respectful.\"]}),`\n`]}),`\n`,(0,o.jsx)(_.p,{children:\"Okay, I hope we are all good here and can continue.\"}),`\n`,(0,o.jsx)(_.h2,{children:\"The Struggle\"}),`\n`,(0,o.jsxs)(_.p,{children:[\"Unless you've been living under a rock, you've likely heard about Remix one way or another. Remix is a full-stack JavaScript framework. In fact, at the moment of writing this, I'd even go as far as to say it is \",(0,o.jsx)(_.em,{children:\"the only\"}),\" truly full-stack JavaScript framework out there. At least, it's been the first one in my memory to give developers actual full control of their routes, unlike magic APIs that describe server-side actions alongside your client-side code.\"]}),`\n`,(0,o.jsxs)(_.p,{children:[\"You've also likely read all the praise threads about Remix and how incredible it is. I won't lie, it \",(0,o.jsx)(_.em,{children:\"is\"}),\" pretty incredible. It made it all the more odd that when I first tried building an actual app with it I felt conflicted. I felt like everybody was utilizing this new thing to its fullest but whenever I reached to solve a problem with Remix I created two new ones. At first, I wrote it off as my lack of experience and moved on. But as I worked with the framework more and more, certain things kept repeating even after I felt quite comfortable when it came to Remix's side of things.\"]}),`\n`,(0,o.jsx)(_.p,{children:\"Once I tentatively (and carefully) posted about some of my struggles on Twitter, I found out that I wasn't alone. My friends also replied that they were struggling with certain things, and that gave me that final nudge to sit down and write about some of those (well, more like coming up with a draft and forgetting about it for a few months in my typical fashion).\"}),`\n`,(0,o.jsx)(_.h2,{children:\"Struggle #1: Developer Experience\"}),`\n`,(0,o.jsx)(_.p,{children:\"Remix nails some of the most complex aspects of development with eye-opening ease. That's why it's so jarring that it seems to ignore a big chunk of developer experience surrounding the usage of the framework.\"}),`\n`,(0,o.jsxs)(_.p,{children:[\"It's been almost ten years since JavaScript applications have stopped being just JavaScript, whether you share that ideology or not. Real-world applications ship CSS and SASS, may use technologies like GraphQL, and can be written in other languages, like TypeScript or Reason. And they do all that \",(0,o.jsx)(_.em,{children:\"as a part of the application\"}),\". A project without a bundler these days is as rare as a tweet not mentioning ChatGPT. And Remix too comes with a bundler, using esbuild to make the magic happen.\"]}),`\n`,(0,o.jsxs)(_.p,{children:[\"The issue is, it abstracts the bundler and the bundling process so much that you, the developer, have no control over it. Now, the last thing I want is to meddle with a framework's build process. Brrgh. But it's still \",(0,o.jsx)(_.em,{children:\"my application\"}),\" and I dislike whenever tools take control away from me. Let me give you an example.\"]}),`\n`,(0,o.jsxs)(_.p,{children:[\"I like writing my GraphQL operations using their SDL syntax (i.e. \",(0,o.jsx)(_.code,{children:\"*.gql\"}),\" files). It feels natural and less verbose than using object-based operation declarations. However, you have to have some processing for those \",(0,o.jsx)(_.code,{children:\"*.gql\"}),\" files to transpile them back to JavaScript. You need a loader. That's not a big deal though, as every modern bundler comes with a way to control how certain modules are processed, including esbuild that's used in Remix. But there's no way to affect module handling \",(0,o.jsx)(_.em,{children:\"from\"}),\" Remix. Its configuration doesn't allow extending esbuild or affecting the build pipeline in any other way. This translates to a simple realization: I can't use a commonly established practice because Remix doesn't allow me to.\"]}),`\n`,(0,o.jsx)(_.p,{children:\"Here's a different example. I like Tailwind, it saves me a ton of time. Tailwind works through PostCSS so, yet again, you need a module processing pipeline (CSS files \\u2192 PostCSS \\u2192 Tailwind plugin). You may start to get what I'm coming at here.\"}),`\n`,(0,o.jsxs)(_.blockquote,{children:[`\n`,(0,o.jsx)(_.p,{children:\"Remix has shipped the (experimental) Tailwind support recently, which is great! Unfortunately, it doesn't automatically negate my point.\"}),`\n`]}),`\n`,(0,o.jsxs)(_.p,{children:[\"It's crucial for you to understand one thing: a framework doesn't own your build, \",(0,o.jsx)(_.em,{children:\"you\"}),\" do. And when that build fails during another deployment, it's not the framework that will be spending its time debugging it, it will be \",(0,o.jsx)(_.em,{children:\"you\"}),\". Moreover, locking away an established pattern of letting the end developer control their builds is a huge limitation that doesn't provide any objective advantages to the developer whatsoever.\"]}),`\n`,(0,o.jsxs)(_.p,{children:[\"So, what does Remix offer for those cases? The only recommendation I found, and the one that's commonly used in the wild, is using tools like \",(0,o.jsx)(_.code,{children:\"pm2\"}),\" to implement the build orchestration manually. I'm extremely skeptical about this recommendation, to say the least. Even if you ignore that \",(0,o.jsx)(_.code,{children:\"pm2\"}),\" is \",(0,o.jsx)(_.em,{children:\"not\"}),\" a build orchestration tool (it's a Node.js process monitoring tool) and provides you with no means to describe parallel, sequential, and inter-dependent build steps, it is simply inefficient to force the developer to dive into Gulp era of build orchestration (Gulp is still a phenomenal task orchestration tool and I have, in fact, used it in some of my Remix projects!).\"]}),`\n`,(0,o.jsx)(_.p,{children:\"It's a no-brainer that Remix will, eventually, give back the rightful control over the build to its users. But it didn't do that back when I was working with it, and it hasn't done that yet.\"}),`\n`,(0,o.jsx)(_.h2,{children:\"Struggle #2: Routing Is (Too) Magical\"}),`\n`,(0,o.jsx)(_.p,{children:\"Routing is what makes Remix stand out. In the end, it's created by folks who have years of experience in shipping industry-standard client-side routing libraries. I didn't expect less from Remix and it certainly didn't disappoint.\"}),`\n`,(0,o.jsxs)(_.p,{children:[\"Until I had to write routes. Lots of them. If I got a nickel every time I had to rummage through their docs to remind me about the correct syntax to group routes I'd be working open-source full-time now (you can still \",(0,o.jsx)(_.a,{href:\"https://github.com/sponsors/kettanaito\",children:\"support me on that goal\"}),\", if you wish). I know that routing is hard but I can't say that Remix's routing syntax is the most obvious one. It simply contains way too much magic for me to remember, especially since I've never used that syntax in any other solutions before (\",(0,o.jsx)(_.code,{children:\"__\"}),\", \",(0,o.jsx)(_.code,{children:\"?index\"}),\", \",(0,o.jsx)(_.code,{children:\"[.png]\"}),\", all these magical friends is not something my brain spits out on command).\"]}),`\n`,(0,o.jsxs)(_.p,{children:[\"To add insult to injury, back in the day there used to be no \\u201CRouting\\u201D section in their documentation! Routing syntax and rules were scattered across data fetching and layout sections, making them hard to find and even harder to put together. Now, Remix has the \",(0,o.jsx)(_.a,{href:\"https://remix.run/docs/en/main/guides/routing\",children:\"Routing page\"}),\", and it's a great improvement.\"]}),`\n`,(0,o.jsx)(_.p,{children:\"I can't say the routing syntax has changed much over this time. I haven't used the flat routes from V2 and don't have any opinion on them as a result yet. Once again, you may find this routing the next most natural thing to breathing but I haven't. I wonder sometimes whether file system-based routing is truly the best thing there is.\"}),`\n`,(0,o.jsx)(_.h2,{children:\"Struggle #3: It's Still Raw\"}),`\n`,(0,o.jsx)(_.p,{children:\"Remix hasn't been around for that long but it positions itself as a production-ready, viable option when it comes to choosing the framework for your next project. While it checks all the boxes to make that statement, a tinge of strange experience slips through here and there.\"}),`\n`,(0,o.jsxs)(_.p,{children:[\"For example, I am terrified of updating Remix. I've had my share of cryptic error messages and things breaking when jumping between \",(0,o.jsx)(_.em,{children:\"minor\"}),\" versions of post-1.0 releases (1.7 \\u2192 1.9 was a story to tell). I would be the last person to blame the team for breaking the consumers. However, as the consumer, I really don't want to spend any time deciphering errors like this when all I want is to ship the next feature in my app:\"]}),`\n`,(0,o.jsx)(_.pre,{children:(0,o.jsx)(_.code,{className:\"language-jsx\",children:`[1] [1] TypeError: Cannot read properties of undefined (reading 'v2_meta')\n`})}),`\n`,(0,o.jsx)(_.p,{children:\"Remind you, this wasn't a breaking release but my application broke nonetheless.\"}),`\n`,(0,o.jsx)(_.p,{children:\"Even when it comes to the default, out-of-the-box experience, Remix adds a grain on salt to the mix. Bootstrapping a fresh Remix app and opening it in the browser gives you a screen-long red wall of hydration errors:\"}),`\n`,(0,o.jsx)(_.p,{children:(0,o.jsx)(_.img,{alt:\"The Remix Wall of Errors\",src:W})}),`\n`,(0,o.jsxs)(_.blockquote,{children:[`\n`,(0,o.jsx)(_.p,{children:\"I've noticed this wall of errors only happens in Brave, and in Chrome you get a single humble error still.\"}),`\n`]}),`\n`,(0,o.jsx)(_.p,{children:\"I wouldn't pick on things like this if this wasn't literally the first thing you see opening a new Remix app. This clearly hints at some internals issues and leaves a sour aftertaste for the user.\"}),`\n`,(0,o.jsx)(_.p,{children:\"Luckily, the time will fix all this. Remix has gone a long way from the moment I first picked it up and until now, and it will undergo even more improvements in the upcoming years. Until then, I see it as a heavily-developing framework.\"}),`\n`,(0,o.jsx)(_.h2,{children:\"Struggle #4: Control not default\"}),`\n`,(0,o.jsx)(_.p,{children:\"I hope you are still with me because I saved the best for last.\"}),`\n`,(0,o.jsxs)(_.p,{children:[\"Remix is the first JavaScript framework where I truly felt in control (even despite it locking away the build process). Any route can render a component. Any route can be a server-side route, handling request and serving data. Any route \",(0,o.jsx)(_.em,{children:\"can be both at the same time\"}),\". That is one of the things that, once you're used to, you can't stop craving for it in any other framework you try.\"]}),`\n`,(0,o.jsxs)(_.p,{children:[\"Control is a double-edged sword. When you're driving a car, you are clearly in control behind the steering wheel\\u2014you decide where the thing would go, whether to speed up or slow down, or if you need to crank up the AC on the back seats or open a window. You are in charge and you control the things that \",(0,o.jsx)(_.em,{children:\"matter to you\"}),\", the driver. Now, imagine if you'd also have to monitor and correct the voltage coming to your car's electronics so it wouldn't short-circuit, or had to manually enter the amount of fuel you wish to burn every time you press on the gas pedal. That is even more control, so why doesn't that feel better?\"]}),`\n`,(0,o.jsx)(_.p,{children:\"Because control is only good when it concerns the things of your immediate interest. If you start managing things you don't care about right now, or at all, control becomes a nuisance that degrades your car driving experience.\"}),`\n`,(0,o.jsxs)(_.p,{children:[\"Remix gives you control. It gives you \",(0,o.jsx)(_.em,{children:\"a lot\"}),\" of control. Much so that I often find myself working on things that are barely related to what I'm trying to build. The most painful memory being my attempts to manage any kind of static assets. Allow me to back this point up with a concrete example.\"]}),`\n`,(0,o.jsx)(_.p,{children:\"I chose Remix to build a portfolio for my friend, who is an artist. Naturally, I had to create a few pages that showcase her work on the site. She didn't have much site management experience and I didn't want to onboard her to WordPress and friends, so I decided to use MDX to describe each individual work. It's plain text, it's easy to understand and edit, and I even wrote some basic instructions on how to create new portfolio entries whenever she needs to. Besides, I've worked a lot with MDX in the past, that was supposed to be a breathe!\"}),`\n`,(0,o.jsx)(_.p,{children:\"Until it wasn't. One particularly painful moment was handling image assets on the work detail page. Roughly, that page translated to the following MDX:\"}),`\n`,(0,o.jsx)(_.pre,{children:(0,o.jsx)(_.code,{className:\"language-jsx\",children:`---\ntitle: Castle In The Sky\nthumbnail: ./castle.jpg\n---\n\nHere's a short description of the work. A neat way for an artist\nto bring more context and life to their creations.\n\n![Concept](./concept.jpg)\n![First draft](./first-draft.jpg)\n![Final painting](./final.jpg)\n`})}),`\n`,(0,o.jsx)(_.p,{children:\"You can imagine the most basic MDX blog setup around this:\"}),`\n`,(0,o.jsxs)(_.ol,{children:[`\n`,(0,o.jsxs)(_.li,{children:[\"Individual works kept under \",(0,o.jsx)(_.code,{children:\"works/{slug}.mdx\"}),\".\"]}),`\n`,(0,o.jsxs)(_.li,{children:[\"A template described under \",(0,o.jsx)(_.code,{children:\"src/routes/work/$slug.jsx\"}),\".\"]}),`\n`,(0,o.jsx)(_.li,{children:\"And the MDX \\u2192 HTML pipeline that locates, converts, and renders the contents.\"}),`\n`]}),`\n`,(0,o.jsx)(_.p,{children:\"And integrating this MDX setup into Remix was largely just that, except for those images.\"}),`\n`,(0,o.jsxs)(_.p,{children:[\"See, Remix won't load those images (in fact, any static assets) from the file system if they are next to your \",(0,o.jsx)(_.code,{children:\"*.mdx\"}),\" files. Why? Because nothing is instructing Remix to \",(0,o.jsx)(_.em,{children:\"serve\"}),\" them from there. To get what is a basic assets imports in Remix you have to create a designated route, describe a \",(0,o.jsx)(_.code,{children:\"loader\"}),\" in there, resolve requested assets from the right path in file-system, and respond with them.\"]}),`\n`,(0,o.jsx)(_.pre,{children:(0,o.jsx)(_.code,{className:\"language-jsx\",children:`// src/routes/blog/[slug]/$asset[.jpg]\nexport const loader = async ({ request }) =\u003e {\n  const url = new URL(request.url)\n  const assetPath = await resolveAsset(url.pathname)\n  const buffer = await fs.readFile(assetPath)\n\n  return new Response(buffer, {\n    headers: { 'Content-Type': 'image/jpg' },\n  })\n}\n`})}),`\n`,(0,o.jsx)(_.p,{children:\"I may be spoiled beyond saving by Gatsby and Astro, but when I reference a relative image in my Markdown I just expect the framework to resolve it by itself. Having to manually write a server-side handler to resolve these static assets felt like I was adding missing bits of the framework instead of working on my application, and, needless to say, it was not an experience I want to relive.\"}),`\n`,(0,o.jsxs)(_.p,{children:[\"But hey, this was my custom MDX setup so there was a decent possibility I just screwed something up. To give the framework a chance, I tried the default, barebones \",(0,o.jsx)(_.a,{href:\"https://remix.run/docs/en/main/guides/mdx\",children:\"MDX example\"}),\" as described in their docs to find the behavior exactly the same. Sadly, Remix will not pick up static assets relatively to your routes, and the official recommendation is to either write a \",(0,o.jsx)(_.code,{children:\"loader\"}),\" for them or devise a custom copy script to flush all the assets to the \",(0,o.jsx)(_.code,{children:\"public\"}),\" directory during the build. This reminds me of the Gulp era yet again with all the manual assets copying, which is also not the time period I want to come back to.\"]}),`\n`,(0,o.jsx)(_.p,{children:\"This point stretches beyond working with MDX. Remix gives you control, it does, but it fails to give you sensible defaults in certain areas, forcing you to fill the gaps by yourself. To this, I have a simple yet true rule:\"}),`\n`,(0,o.jsx)(s,{children:(0,o.jsx)(_.p,{children:`Every time you're writing code for your framework and not your product, the\nframework fails.`})}),`\n`,(0,o.jsx)(_.h2,{children:\"Closing thoughts\"}),`\n`,(0,o.jsx)(_.p,{children:\"Looking at all this struggle you may think I dislike Remix. But I don't. On the contrary, I find Remix to be a great tool, and I've experienced it myself how much it shines when developing data-heavy, dynamic applications. I believe the main cause for my struggle was simply choosing the wrong tool for the job.\"}),`\n`,(0,o.jsx)(_.p,{children:\"I would pick Remix to build my next startup. I would not pick Remix to build my blog, portfolio, or other kind of, effectively, static websites. This doesn't make the framework bad in any way, it just reminds me that Remix is a tool and certain tools are better than others for certain needs.\"})]})}function r(D={}){let{wrapper:_}=D.components||{};return _?(0,o.jsx)(_,Object.assign({},D,{children:(0,o.jsx)(y,D)})):y(D)}var b=r;function d(D,_){throw new Error(\"Expected \"+(_?\"component\":\"object\")+\" `\"+D+\"` to be defined: you likely forgot to import, pass, or provide it.\")}return z(Y);})();\n;return Component;","slug":"my-struggle-with-remix","url":"/blog/my-struggle-with-remix","thumbnailSvg":"\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\u003csvg viewBox=\"0 0 499 665\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\u003e\u003cg id=\"dcrhnpq__thumbnail\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\"\u003e\u003cg id=\"dcrhnpq__remix\" transform=\"translate(122.000000, 184.000000)\" fill=\"#1E293B\" fill-rule=\"nonzero\"\u003e\u003cpath d=\"M141.674538,0 C218.04743,0 256,36.3493031 256,94.4136694 C256,137.843796 229.292875,166.16709 193.214546,170.888177 C223.670152,177.025429 241.473826,194.491998 244.754554,228.952544 L245.229325,235.289856 L245.643706,241.214203 L246.00181,246.756109 L246.250531,250.934795 L246.517683,255.865245 L246.656217,258.679019 L246.853693,263.148984 L247.012965,267.370833 L247.091895,269.797544 L247.198581,273.685906 L247.290626,278.131883 L247.324005,280.280236 L247.384197,286.505871 L247.403543,293.002292 L247.40462,296.886512 L168.646185,296.886512 L168.650135,295.266478 L168.650135,295.266478 L168.678181,292.120279 L168.678181,292.120279 L168.725186,289.055223 L168.861417,281.631321 L168.895871,279.142491 L168.922852,275.239899 L168.922852,275.239899 L168.919162,272.744266 L168.896218,270.127045 L168.864335,268.072886 L168.799537,265.197081 L168.706158,262.147348 L168.580806,258.904651 L168.42009,255.449957 L168.325411,253.637163 L168.164297,250.804473 L167.978998,247.828446 L167.691838,243.623566 L167.444542,240.281862 C167.373519,239.25114 167.291291,238.24473 167.19786,237.262104 L166.996058,235.328408 C164.395177,212.50087 155.340815,203.170989 139.832974,200.059114 L138.525715,199.814028 C137.64425,199.660026 136.742867,199.52459 135.821566,199.406474 L134.424675,199.242133 C134.189371,199.216855 133.952821,199.192621 133.715026,199.169411 L132.27332,199.042283 L132.27332,199.042283 L130.801736,198.938792 L130.801736,198.938792 L129.300276,198.858003 L129.300276,198.858003 L127.785563,198.799503 L126.241612,198.761396 L124.668422,198.742777 L124.668422,198.742777 L0,198.740492 L0,136.900224 L127.619345,136.900224 C129.706029,136.900224 131.728173,136.860653 133.685777,136.779928 L135.621869,136.685425 L135.621869,136.685425 L137.514935,136.563134 L137.514935,136.563134 L139.364974,136.412701 C139.669729,136.385264 139.97269,136.35664 140.273859,136.326822 L142.05936,136.133518 C143.235352,135.995014 144.382659,135.837162 145.501284,135.659493 L147.157707,135.378069 C167.866574,131.62361 178.22062,120.630459 178.22062,99.1783057 C178.22062,75.1035054 161.354066,60.5128152 127.619345,60.5128152 L0,60.5128152 L0,0 L141.674538,0 Z M83.2762921,250.785352 C93.6094556,250.785352 97.9327877,256.522818 99.4729615,262.01452 L99.6761617,262.804225 L99.6761617,262.804225 L99.8429155,263.58653 L99.9515227,264.204367 L99.9979397,264.509915 L100.075689,265.112992 L100.134243,265.703672 L100.156667,265.993728 L100.188494,266.561991 L100.198173,266.839685 L100.205751,267.380932 L100.205751,296.886512 L0,296.886512 L0,250.785352 L83.2762921,250.785352 Z\"/\u003e\u003c/g\u003e\u003c/g\u003e\u003c/svg\u003e","frontmatter":{"title":"My Struggle With Remix","description":"Remix is a fantastic framework but it's not without its issues. Here are some of my struggles after building a few different projects with it.","category":"Engineering","date":"2023-05-10T00:00:00.000Z","keywords":["remix","struggle","issues","framework","mdx"]}},{"id":"m8oi1nt","code":"var Component=(()=\u003e{var ie=Object.create;var S=Object.defineProperty;var re=Object.getOwnPropertyDescriptor;var se=Object.getOwnPropertyNames;var ue=Object.getPrototypeOf,le=Object.prototype.hasOwnProperty;var ae=(o,e)=\u003e()=\u003e(o\u0026\u0026(e=o(o=0)),e);var M=(o,e)=\u003e()=\u003e(e||o((e={exports:{}}).exports,e),e.exports),_e=(o,e)=\u003e{for(var i in e)S(o,i,{get:e[i],enumerable:!0})},U=(o,e,i,l)=\u003e{if(e\u0026\u0026typeof e==\"object\"||typeof e==\"function\")for(let u of se(e))!le.call(o,u)\u0026\u0026u!==i\u0026\u0026S(o,u,{get:()=\u003ee[u],enumerable:!(l=re(e,u))||l.enumerable});return o};var k=(o,e,i)=\u003e(i=o!=null?ie(ue(o)):{},U(e||!o||!o.__esModule?S(i,\"default\",{value:o,enumerable:!0}):i,o)),de=o=\u003eU(S({},\"__esModule\",{value:!0}),o);var d=ae(()=\u003e{});var R=M((Se,j)=\u003e{d();j.exports=_jsx_runtime});var N=M((fe,A)=\u003e{d();A.exports=React});var Ne={};_e(Ne,{default:()=\u003eWe,frontmatter:()=\u003eGe});d();var t=k(R());d();var p=k(N());d();var O=k(N());var X=\"/blog/debounce-vs-throttle/vending-machine-CIUDEBW5.png\";var L=\"/blog/debounce-vs-throttle/debounce-vs-throttle-AUBAQPS3.css\";var Oe=1,Z=1.22,T=.47,b=1/60,xe=b*1e3,ye=9.81;function q(o,e){return Math.floor(Math.random()*(e-o+1))+o}function ge(o,e,i,l,u,x){return{position:{x:o,y:e},velocity:{x:0,y:0},e:-l,area:Math.PI*i*2/1e4,radius:i,mass:u,color:x}}function H({ballRadius:o,maxBalls:e,onButtonClick:i,onReset:l}){let u=(0,O.useRef)(null),x=(0,O.useRef)(null),w=(0,O.useRef)(null),y=(0,O.useRef)([]),h={x:0,y:0,isDown:!1},P=(0,O.useMemo)(()=\u003ey.current.length\u003ce,[e,y]),K=()=\u003e{let{current:n}=u;x.current=n==null?void 0:n.getContext(\"2d\")},G=n=\u003e{let{current:s}=u;!s||(h.x=n.pageX-s.offsetLeft,h.y=n.pageY-s.offsetTop)},J=n=\u003e{if(n.nativeEvent.which===1){G(n.nativeEvent),h.isDown=!0;let{current:s}=y,_=q(0,50),m=q(85,95),a=q(50,70);s.push(ge(h.x,h.y,o,.7,10,`hsl(${_}, ${m}%, ${a}%)`))}},ee=n=\u003e{if(n.nativeEvent.which===1){h.isDown=!1;let{current:s}=y,_=s[s.length-1];s[s.length-1].velocity={x:(_.position.x-h.x)/10,y:(_.position.y-h.y)/10}}},te=n=\u003e{let{current:s}=u;if(!s)return;let{height:_,width:m}=s;n.position.x\u003em-n.radius\u0026\u0026(n.velocity.x*=n.e,n.position.x=m-n.radius),n.position.y\u003e_-n.radius\u0026\u0026(n.velocity.y*=n.e,n.position.y=_-n.radius),n.position.x\u003cn.radius\u0026\u0026(n.velocity.x*=n.e,n.position.x=n.radius),n.position.y\u003cn.radius\u0026\u0026(n.velocity.y*=n.e,n.position.y=n.radius)},ne=n=\u003e{let{current:s}=y;for(let E=0;E\u003cs.length;E++){let c=s[E];if(n.position.x!==c.position.x\u0026\u0026n.position.y!==c.position.y\u0026\u0026n.position.x+n.radius+c.radius\u003ec.position.x\u0026\u0026n.position.x\u003cc.position.x+n.radius+c.radius\u0026\u0026n.position.y+n.radius+c.radius\u003ec.position.y\u0026\u0026n.position.y\u003cc.position.y+n.radius+c.radius){var _=n.position.x-c.position.x,m=n.position.y-c.position.y,a=Math.sqrt(_*_+m*m);if(a\u003cn.radius+c.radius){var r=(c.position.x-n.position.x)/a,g=(c.position.y-n.position.y)/a,v=2*(n.velocity.x*r+n.velocity.y*g-c.velocity.x*r-c.velocity.y*g)/(n.mass+c.mass),C=(n.position.x*c.radius+c.position.x*n.radius)/(n.radius+c.radius),I=(n.position.y*c.radius+c.position.y*n.radius)/(n.radius+c.radius);n.position.x=C+n.radius*(n.position.x-c.position.x)/a,n.position.y=I+n.radius*(n.position.y-c.position.y)/a,c.position.x=C+c.radius*(c.position.x-n.position.x)/a,c.position.y=I+c.radius*(c.position.y-n.position.y)/a,n.velocity.x-=v*n.mass*r,n.velocity.y-=v*n.mass*g,c.velocity.x+=v*c.mass*r,c.velocity.y+=v*c.mass*g}}}},V=(0,O.useCallback)(()=\u003e{let{current:n}=x,{current:s}=u,{current:_}=y;if(!n||!s)return;n.clearRect(0,0,s.width,s.height);let m=_.length;for(let a=0;a\u003cm;a++){let r=_[a];if(!h.isDown||a!==m-1){let g=-T*Z*r.area*r.velocity.x*r.velocity.x*(r.velocity.x/Math.abs(r.velocity.x)),v=-T*Z*r.area*r.velocity.y*r.velocity.y*(r.velocity.y/Math.abs(r.velocity.y));g=isNaN(g)?0:g,v=isNaN(v)?0:v;let C=g/r.mass,I=ye*Oe+v/r.mass;r.velocity.x+=C*b,r.velocity.y+=I*b,r.position.x+=r.velocity.x*b*100,r.position.y+=r.velocity.y*b*100}n.beginPath(),n.fillStyle=r.color,n.arc(r.position.x,r.position.y,r.radius,0,2*Math.PI,!0),n.fill(),n.closePath(),ne(r),te(r)}},[u,x,y,h.isDown]),oe=()=\u003e{let{current:n}=u;if(!n)return;let s={x:n.width/2+n.offsetLeft,y:n.offsetTop+10},_={x:s.x+q(-20,20),y:s.y+q(-20,20)},m=new MouseEvent(\"mousedown\",{bubbles:!0,clientX:s.x,clientY:s.y,relatedTarget:n}),a=new MouseEvent(\"mouseup\",{bubbles:!0,clientX:_.x,clientY:_.y,relatedTarget:n});Object.defineProperty(m,\"nativeEvent\",{value:m}),Object.defineProperty(a,\"nativeEvent\",{value:a}),J(m),G(a.nativeEvent),ee(a)},ce=()=\u003e{w.current\u0026\u0026clearInterval(w.current)},qe=()=\u003e{y.current=[],l()};return(0,O.useEffect)(()=\u003e(K(),()=\u003e{ce()}),[]),(0,O.useEffect)(()=\u003e{(()=\u003e{w.current=setInterval(V,xe)})()},[V]),React.createElement(React.Fragment,null,React.createElement(\"link\",{rel:\"stylesheet\",href:L}),React.createElement(\"div\",{className:\"relative mx-auto shrink-0 touch-manipulation w-full max-w-[280px] md:max-w-[350px]\"},React.createElement(\"div\",{className:\"absolute m-auto left-0 right-0 z-[1] w-[85px] bottom-[150px] md:bottom-[210px]\"},React.createElement(\"button\",{className:\"throw-button\",onMouseDown:i.bind(null,oe)})),React.createElement(\"img\",{src:X,alt:\"Ball vending machine\"}),React.createElement(\"canvas\",{ref:u,className:\"absolute block m-auto left-0 right-0 top-[102px] h-[145px] w-[115px] md:top-[170px] md:h-[185px] md:w-[145px]\",width:145,height:185})))}function W({action:o=()=\u003el=\u003el(),maxBalls:e=30,children:i}){let[l,u]=(0,p.useState)(0),[x,w]=(0,p.useState)(0),y=(0,p.useMemo)(()=\u003ex\u003ce,[e,x]),h=(0,p.useCallback)(o(G=\u003eG()),[o]);return React.createElement(\"div\",{className:\"my-16 md:flex gap-10 items-center\"},React.createElement(H,{ballRadius:10,maxBalls:e,onButtonClick:G=\u003e{!y||(u(l+1),h(()=\u003e{G(),w(x+1)}))},onReset:()=\u003e{u(0),w(0)}}),React.createElement(\"div\",{className:\"space-y-10 w-full\"},React.createElement(\"table\",{className:\"text-base w-full\"},React.createElement(\"tbody\",null,React.createElement(\"tr\",null,React.createElement(\"td\",{className:\"text-gray-500\"},\"Button clicked:\"),React.createElement(\"td\",{className:\"text-right\"},React.createElement(\"strong\",null,React.createElement(\"span\",{style:{fontVariantNumeric:\"ordinal\"}},l),\" \",\"time(s)\"))),React.createElement(\"tr\",null,React.createElement(\"td\",{className:\"text-gray-500\"},\"Event handler called:\"),React.createElement(\"td\",{className:\"text-right\"},React.createElement(\"strong\",null,React.createElement(\"span\",{style:{fontVariantNumeric:\"ordinal\"}},x),\" \",\"time(s)\"))))),i?React.createElement(\"div\",{className:\"my-5 mb-16 md:mb-0\"},i):null))}d();var Y=k(N());var Q=500;function ve(o,e){let i=!1;return function(...l){i||(o.apply(this,l),i=!0,setTimeout(function(){i=!1},e))}}function B(){let[o,e]=(0,Y.useState)(Q),i=(0,Y.useCallback)(function(u){return ve(u,o)},[o]);return React.createElement(W,{action:i},React.createElement(\"fieldset\",{className:\"p-5 border shadow-lg rounded-lg\"},React.createElement(\"legend\",{className:\"px-3 font-bold\"},\"Throttle options\"),React.createElement(\"label\",{htmlFor:\"throttleDuration\"},\"Duration:\",\" \",React.createElement(\"span\",{className:\"font-mono\",style:{fontVariantNumeric:\"ordinal\"}},o,\"ms\")),React.createElement(\"input\",{id:\"throttleDuration\",className:\"block mt-1 w-full\",type:\"range\",min:\"0\",max:\"2000\",step:\"100\",value:o,onChange:l=\u003ee(l.target.valueAsNumber),onDoubleClick:()=\u003ee(Q)}),o===0\u0026\u0026React.createElement(\"p\",{className:\"mt-2 font-semibold text-yellow-600\"},\"No throttling applied.\")))}d();var f=k(N());function pe(o,e){let i;return function(...l){let u=()=\u003e(i=void 0,o.apply(this,l));clearTimeout(i),i=setTimeout(u,e)}}var F=500;function $(){let[o,e]=(0,f.useState)(F),i=(0,f.useCallback)(function(u){return pe(u,o)},[o]);return React.createElement(W,{action:i},React.createElement(\"fieldset\",{className:\"p-5 border shadow-lg rounded-lg\"},React.createElement(\"legend\",{className:\"px-3 font-bold\"},\"Debounce options\"),React.createElement(\"label\",{htmlFor:\"debounceDuration\"},\"Duration: \",o,\"ms\"),React.createElement(\"input\",{id:\"debounceDuration\",className:\"block mt-1 w-full\",type:\"range\",min:\"0\",max:\"2000\",step:\"100\",value:o,onChange:l=\u003ee(l.target.valueAsNumber),onDoubleClick:()=\u003ee(F)}),o\u003c=200\u0026\u0026React.createElement(\"p\",{className:\"text-yellow-600 font-semibold\"},we(o))))}function we(o){return o===0?\"No debounce applied.\":\"Debounce duration is too fast to notice any effect. Please choose a higher number.\"}var Ge={title:\"Debounce vs Throttle: Definitive Visual Guide\",description:\"A complete guide to learn the difference between debounce and throttle using visual examples. Never confuse the two again.\",category:\"Engineering\",date:new Date(15770592e5),keywords:[\"debounce\",\"throttle\",\"javascript\",\"event\",\"events handling\",\"response rate\",\"ball machine\",\"ball vending machine\",\"interactive example\",\"definitive guide\"],hashtags:[\"javascript\",\"debounce\",\"tips\"]};function D(o){let e=Object.assign({h2:\"h2\",p:\"p\",code:\"code\",em:\"em\",a:\"a\",pre:\"pre\",strong:\"strong\",hr:\"hr\",h3:\"h3\",blockquote:\"blockquote\",ul:\"ul\",li:\"li\",h4:\"h4\"},o.components),{Quote:i}=e;return i||ke(\"Quote\",!0),(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(e.h2,{children:\"Introduction\"}),`\n`,(0,t.jsxs)(e.p,{children:[\"When it comes to debounce and throttle developers often confuse the two. Choosing the right one is, however, crucial, as they bear a different effect. If you are a visual learner as myself, you will find this interactive guide useful to differentiate between \",(0,t.jsx)(e.code,{children:\"throttle\"}),\" and \",(0,t.jsx)(e.code,{children:\"debounce\"}),\" and better understand when to use each.\"]}),`\n`,(0,t.jsx)(e.h2,{children:\"The basics\"}),`\n`,(0,t.jsx)(e.p,{children:\"Throttling and debouncing are two ways to optimize event handling. Before we begin, let's take a moment to briefly revise the basics of events. In this article I'm going to use JavaScript in all examples, yet the concepts they illustrate are not bound to any specific language.\"}),`\n`,(0,t.jsx)(e.p,{children:'Event is an action that occurs in the system. In front-end development that system is usually a browser. For example, when you resize a browser window the \"resize\" event is fired, and when you click on a button the \"click\" event is. We are interested in events to attach our own logic to them. That logic is represented as a function that is called a handler function (because it handles the event). Such handler functions may handle a UI element update on resize, display a modal window upon a button click, or execute an arbitrary logic in response to any event.'}),`\n`,(0,t.jsxs)(e.p,{children:[\"In JavaScript you can react to events using event listeners. \",(0,t.jsx)(e.em,{children:\"Event listener\"}),\" is a function that listens to the given event on a DOM element and executes a handler function whenever that event occurs. To add an event listener to an element (target) you should use the \",(0,t.jsx)(e.a,{href:\"https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener\",children:(0,t.jsx)(e.code,{children:\"addEventListener\"})}),\" function:\"]}),`\n`,(0,t.jsx)(e.pre,{children:(0,t.jsx)(e.code,{className:\"language-js\",children:`element.addEventListener(eventName, listener, options)\n`})}),`\n`,(0,t.jsx)(e.h2,{children:\"Let's throw a ball!\"}),`\n`,(0,t.jsxs)(e.p,{children:[\"Let's build a ball throwing machine. Our machine would have a button that, when pushed, throws a ball. To describe this cause-and-effect relation between the button click and a ball throw we can use \",(0,t.jsx)(e.code,{children:\"addEventListener\"}),\" on our button element:\"]}),`\n`,(0,t.jsx)(e.pre,{children:(0,t.jsx)(e.code,{className:\"language-js\",children:`// Find a button element on the page.\nconst button = document.getElementById('button')\n\n// And react to its click event.\nbutton.addEventListener('click', function () {\n  throwBall()\n})\n`})}),`\n`,(0,t.jsxs)(e.p,{children:[\"This reads as: \",(0,t.jsxs)(e.em,{children:[\"whenever the button is clicked, execute the \",(0,t.jsx)(e.code,{children:\"throwBall()\"}),\" function\"]}),\". The details of \",(0,t.jsx)(e.code,{children:\"throwBall\"}),\" function are not important, as it represents \",(0,t.jsx)(e.em,{children:\"any logic\"}),\" bound to an event.\"]}),`\n`,(0,t.jsx)(e.p,{children:\"Hinges are tightened and the screws are steady, let's put our ingenious invention to test!\"}),`\n`,`\n`,(0,t.jsx)(W,{}),`\n`,(0,t.jsxs)(e.p,{children:['Whenever we press the button we produce the \"click\" event, to which the event listener reacts by calling our ',(0,t.jsx)(e.code,{children:\"throwBall()\"}),\" function. In other words, one button click results into one handler function call and one ball being thrown.\"]}),`\n`,(0,t.jsx)(i,{children:(0,t.jsx)(e.p,{children:\"By default, event listener executes with 1-1 ratio to the event call.\"})}),`\n`,(0,t.jsx)(e.p,{children:\"There are cases, however, when such a direct proportion may become undesired. For instance, what if throwing a ball was an expensive operation, or we couldn't afford to throw more than 1 ball in half a second? In those cases we would have to limit the amount of times our listener is being called.\"}),`\n`,(0,t.jsxs)(e.p,{children:[(0,t.jsx)(e.strong,{children:\"Throttling\"}),\" and \",(0,t.jsx)(e.strong,{children:\"debouncing\"}),\" are two most common ways to control a listener response rate to an event. Let's analyze each of them more closely by tweaking our ball machine.\"]}),`\n`,(0,t.jsx)(e.hr,{}),`\n`,(0,t.jsx)(e.h2,{children:\"Throttle\"}),`\n`,(0,t.jsxs)(e.p,{children:[(0,t.jsx)(e.em,{children:\"Throttling\"}),\" is the action of reducing the number of times a function can be called over time to exactly \",(0,t.jsx)(e.em,{children:\"one\"}),\".\"]}),`\n`,(0,t.jsx)(e.p,{children:\"For example, if we throttle a function by 500ms, it means that it cannot be called more than once per 500ms time frame. Any additional function calls within the specified time interval are simply ignored.\"}),`\n`,(0,t.jsx)(e.h3,{children:\"Implementing throttle\"}),`\n`,(0,t.jsx)(e.pre,{children:(0,t.jsx)(e.code,{className:\"language-js\",children:`function throttle(func, duration) {\n  let shouldWait = false\n\n  return function (...args) {\n    if (!shouldWait) {\n      func.apply(this, args)\n      shouldWait = true\n\n      setTimeout(function () {\n        shouldWait = false\n      }, duration)\n    }\n  }\n}\n`})}),`\n`,(0,t.jsxs)(e.blockquote,{children:[`\n`,(0,t.jsxs)(e.p,{children:[\"Depending on the use case, this simplified implementation may not be enough. I highly recommend looking into \",(0,t.jsx)(e.a,{href:\"https://www.npmjs.com/package/lodash.throttle\",children:(0,t.jsx)(e.code,{children:\"lodash.throttle\"})}),\" and \",(0,t.jsx)(e.a,{href:\"https://underscorejs.org/#throttle\",children:(0,t.jsx)(e.code,{children:\"_.throttle\"})}),\" packages then.\"]}),`\n`]}),`\n`,(0,t.jsxs)(e.p,{children:[\"The \",(0,t.jsx)(e.code,{children:\"throttle\"}),\" function accepts two arguments: \",(0,t.jsx)(e.code,{children:\"func\"}),\", which is a function to throttle, and \",(0,t.jsx)(e.code,{children:\"duration\"}),\", which is the duration (in ms) of the throttling interval. It returns a \",(0,t.jsx)(e.em,{children:\"throttled function\"}),\". There are implementations that also accept the \",(0,t.jsx)(e.code,{children:\"leading\"}),\" and \",(0,t.jsx)(e.code,{children:\"trailing\"}),\" parameters that control the first (leading) and the last (trailing) function calls, but I'm going to skip those to keep the example simple.\"]}),`\n`,(0,t.jsxs)(e.p,{children:[\"To throttle our machine's button click we need to pass the event handler function as the first argument to \",(0,t.jsx)(e.code,{children:\"throttle\"}),\", and specify a throttling interval as the second argument:\"]}),`\n`,(0,t.jsx)(e.pre,{children:(0,t.jsx)(e.code,{className:\"language-js\",children:`button.addEventListener(\n  'click',\n  throttle(function () {\n    throwBall()\n  }, 500)\n)\n`})}),`\n`,(0,t.jsx)(e.p,{children:\"Here's how our patched ball machine would work with the throttling applied:\"}),`\n`,`\n`,(0,t.jsx)(B,{}),`\n`,(0,t.jsx)(e.p,{children:\"No matter how often we press the button a ball won't be thrown more than once per throttled interval (500ms in our case). That's a great way to keep our ball machine from overheating during the busy hours!\"}),`\n`,(0,t.jsx)(i,{children:(0,t.jsx)(e.p,{children:`Throttle is a spring that throws balls: after a ball flies out, it needs some\ntime to shrink back, so it cannot throw any more balls unless it's ready.`})}),`\n`,(0,t.jsx)(e.h3,{children:\"When to use throttle?\"}),`\n`,(0,t.jsxs)(e.p,{children:[\"Use throttling to \",(0,t.jsx)(e.em,{children:\"consistently\"}),\" react to a frequent event.\"]}),`\n`,(0,t.jsx)(e.p,{children:\"This technique ensures consistent function execution within a given time interval. Since throttle is bound to a fixed time frame, the event listener should be ready to accept an intermediate state of the event.\"}),`\n`,(0,t.jsx)(e.p,{children:(0,t.jsx)(e.strong,{children:\"Common use cases for throttling include:\"})}),`\n`,(0,t.jsxs)(e.ul,{children:[`\n`,(0,t.jsxs)(e.li,{children:[\"Any consistent UI update after window \",(0,t.jsx)(e.code,{children:\"resize\"}),\";\"]}),`\n`,(0,t.jsx)(e.li,{children:\"Performance-heavy operations on the server or client.\"}),`\n`]}),`\n`,(0,t.jsx)(e.hr,{}),`\n`,(0,t.jsx)(e.h2,{children:\"Debounce\"}),`\n`,(0,t.jsxs)(e.p,{children:[\"A debounced function is called after \",(0,t.jsx)(e.em,{children:\"N\"}),\" amount of time passes since its last call. It reacts to a seemingly resolved state and implies a delay between the event and the handler function call.\"]}),`\n`,(0,t.jsx)(e.h3,{children:\"Implementing debounce\"}),`\n`,(0,t.jsx)(e.pre,{children:(0,t.jsx)(e.code,{className:\"language-js\",children:`function debounce(func, duration) {\n  let timeout\n\n  return function (...args) {\n    const effect = () =\u003e {\n      timeout = null\n      return func.apply(this, args)\n    }\n\n    clearTimeout(timeout)\n    timeout = setTimeout(effect, duration)\n  }\n}\n`})}),`\n`,(0,t.jsxs)(e.blockquote,{children:[`\n`,(0,t.jsxs)(e.p,{children:[\"For more complicated scenarios consider \",(0,t.jsx)(e.a,{href:\"https://www.npmjs.com/package/lodash.debounce\",children:(0,t.jsx)(e.code,{children:\"lodash.debounce\"})}),\" and \",(0,t.jsxs)(e.a,{href:\"https://underscorejs.org/#debounce\",children:[(0,t.jsx)(e.code,{children:\"_.debounce\"}),\" \"]}),\" packages then.\"]}),`\n`]}),`\n`,(0,t.jsxs)(e.p,{children:[\"The \",(0,t.jsx)(e.code,{children:\"debounce\"}),\" function accepts two arguments: \",(0,t.jsx)(e.code,{children:\"func\"}),\", which is a function to debounce, and \",(0,t.jsx)(e.code,{children:\"duration\"}),\", which is the amount of time (in ms) to pass from the last function call. It returns a \",(0,t.jsx)(e.em,{children:\"debounced function\"}),\".\"]}),`\n`,(0,t.jsxs)(e.p,{children:[\"To apply debouncing to our example we would have to wrap the button click handler in the \",(0,t.jsx)(e.code,{children:\"debounce\"}),\":\"]}),`\n`,(0,t.jsx)(e.pre,{children:(0,t.jsx)(e.code,{className:\"language-js\",children:`button.addEventListener(\n  'click',\n  debounce(function () {\n    throwBall()\n  }, 500)\n)\n`})}),`\n`,(0,t.jsxs)(e.p,{children:[\"While the call signature of \",(0,t.jsx)(e.code,{children:\"debounce\"}),\" is often similar to the one in \",(0,t.jsx)(e.code,{children:\"throttle\"}),\", it produces a much different effect when applied. Let's see how our machine will behave if its button clicks are debounced:\"]}),`\n`,`\n`,(0,t.jsx)($,{}),`\n`,(0,t.jsxs)(e.p,{children:[\"If we keep pressing the button fast enough no balls will be thrown at all, unless a debounce duration (500ms) passes since the last click. It is if our machine treats any amount of button clicks within a defined time period as \",(0,t.jsx)(e.em,{children:\"a single event\"}),\" and handles it respectively.\"]}),`\n`,(0,t.jsx)(i,{children:(0,t.jsx)(e.p,{children:`Debounce is an overloaded waiter: if you keep asking him, your requests will\nbe ignored until you stop and give him some time to think about your latest\ninquiry.`})}),`\n`,(0,t.jsx)(e.h3,{children:\"When to use debounce?\"}),`\n`,(0,t.jsxs)(e.p,{children:[\"Use debounce to \",(0,t.jsx)(e.em,{children:\"eventually\"}),\" react to a frequent event.\"]}),`\n`,(0,t.jsxs)(e.p,{children:[\"Debounce is useful when you don't need an intermediate state and wish to respond to the end state of the event. That being said, you need to take into account an inevitable delay between the event and the response to it when using \",(0,t.jsx)(e.code,{children:\"debounce\"}),\".\"]}),`\n`,(0,t.jsx)(e.p,{children:(0,t.jsx)(e.strong,{children:\"Common use cases for a debounced function:\"})}),`\n`,(0,t.jsxs)(e.ul,{children:[`\n`,(0,t.jsx)(e.li,{children:\"Asynchronous search suggestions;\"}),`\n`,(0,t.jsx)(e.li,{children:\"Updates batching on the server.\"}),`\n`]}),`\n`,(0,t.jsx)(e.hr,{}),`\n`,(0,t.jsx)(e.h2,{children:\"Common problems\"}),`\n`,(0,t.jsx)(e.h3,{children:\"Re-declaring debounced/throttled function\"}),`\n`,(0,t.jsxs)(e.p,{children:[\"One of the most common mistakes when working with these rate limiting functions is \",(0,t.jsx)(e.em,{children:\"repeatedly re-declaring them\"}),\". You see, both \",(0,t.jsx)(e.code,{children:\"debounce\"}),\" and \",(0,t.jsx)(e.code,{children:\"throttle\"}),\" work due to \",(0,t.jsx)(e.em,{children:\"the same (debounced/throttled) function reference\"}),\" being called. It is absolutely necessary to ensure you declare your debounced/throttled function only once.\"]}),`\n`,(0,t.jsx)(e.p,{children:\"Allow me to illustrate this pitfall. Take a look at this click event handler:\"}),`\n`,(0,t.jsx)(e.pre,{children:(0,t.jsx)(e.code,{className:\"language-js\",children:`button.addEventListener('click', function handleButtonClick() {\n  return debounce(throwBall, 500)\n})\n`})}),`\n`,(0,t.jsxs)(e.p,{children:[\"It may look fine at first, but in fact nothing is going to be debounced. That is because the \",(0,t.jsx)(e.code,{children:\"handleButtonClick\"}),\" function is not debounced, but instead we debounce the \",(0,t.jsx)(e.code,{children:\"throwBall\"}),\" function.\"]}),`\n`,(0,t.jsxs)(e.p,{children:[\"Instead, we should debounce an entire \",(0,t.jsx)(e.code,{children:\"handleButtonClick\"}),\" function:\"]}),`\n`,(0,t.jsx)(e.pre,{children:(0,t.jsx)(e.code,{className:\"language-js\",children:`button.addEventListener(\n  'click',\n  debounce(function handleButtonClick() {\n    return throwBall()\n  }, 500)\n)\n`})}),`\n`,(0,t.jsx)(i,{children:(0,t.jsx)(e.p,{children:`Remeber that the event handler function must be debounced/throttled only once.\nThe returned function must be provided to any event listeners.`})}),`\n`,(0,t.jsx)(e.h4,{children:\"React example\"}),`\n`,(0,t.jsxs)(e.p,{children:[\"If you are familiar with \",(0,t.jsx)(e.a,{href:\"https://reactjs.org\",children:\"React\"}),\" you may also recognize the following declaration as being invalid:\"]}),`\n`,(0,t.jsx)(e.pre,{children:(0,t.jsx)(e.code,{className:\"language-jsx\",children:`class MyComponent extends React.Component {\n  handleButtonClick = () =\u003e {\n    console.log('The button was clicked')\n  }\n\n  render() {\n    return (\n      \u003cbutton onClick={debounce(this.handleButtonClick, 500)}\u003e\n        Click the button\n      \u003c/button\u003e\n    )\n  }\n}\n`})}),`\n`,(0,t.jsxs)(e.p,{children:[\"Since \",(0,t.jsx)(e.code,{children:\"debounce\"}),\" is called during the render, each MyComponent's re-render will produce a new instance of a debounced \",(0,t.jsx)(e.code,{children:\"handleButtonClick\"}),\" function, resulting into no effect being applied.\"]}),`\n`,(0,t.jsxs)(e.p,{children:[\"Instead, the \",(0,t.jsx)(e.code,{children:\"handleButtonClick\"}),\" \",(0,t.jsx)(e.em,{children:\"declaration\"}),\" should be debounced:\"]}),`\n`,(0,t.jsx)(e.pre,{children:(0,t.jsx)(e.code,{className:\"language-jsx\",children:`class MyComponent extends React.Component {\n  handleButtonClick = debounce(() =\u003e {\n    console.log('The button was clickeds')\n  }, 500)\n\n  render() {\n    return \u003cbutton onClick={this.handleButtonClick}\u003eClick the button\u003c/button\u003e\n  }\n}\n`})}),`\n`,(0,t.jsx)(e.h3,{children:\"Finding optimal duration\"}),`\n`,(0,t.jsxs)(e.p,{children:[\"With both \",(0,t.jsx)(e.code,{children:\"debounce\"}),\" and \",(0,t.jsx)(e.code,{children:\"throttle\"}),\" finding a duration time optimal for UX \",(0,t.jsx)(e.em,{children:\"and\"}),\" performance is important. Choosing a fast interval will not have impact on performance, and picking a too long interval will make your UI feel sluggish.\"]}),`\n`,(0,t.jsx)(e.p,{children:\"The truth is, there is no magical number to this, as time interval will differ for each use case. The best advice I can give you is to not copy any intervals blindly, but test what works the best for your application/users/server. You may want to conduct A/B testing to find that.\"}),`\n`,(0,t.jsx)(e.hr,{}),`\n`,(0,t.jsx)(e.h2,{children:\"Afterword\"}),`\n`,(0,t.jsx)(e.p,{children:\"Thank you for reading through this guide! Of course, there's much more to events handling, and throttling and debouncing are not the only techniques you may use in practice. Let me know if you liked this article by reposting or retweeting it.\"}),`\n`,(0,t.jsxs)(e.blockquote,{children:[`\n`,(0,t.jsxs)(e.p,{children:[\"Special thanks to \",(0,t.jsx)(e.a,{href:\"https://codepen.io/AlexRA96\",children:\"Alexander Fernandes\"}),' for \"Ball Bouncing Physics\" project used for balls physics in the vending machine example.']}),`\n`]})]})}function ze(o={}){let{wrapper:e}=o.components||{};return e?(0,t.jsx)(e,Object.assign({},o,{children:(0,t.jsx)(D,o)})):D(o)}var We=ze;function ke(o,e){throw new Error(\"Expected \"+(e?\"component\":\"object\")+\" `\"+o+\"` to be defined: you likely forgot to import, pass, or provide it.\")}return de(Ne);})();\n;return Component;","slug":"debounce-vs-throttle","url":"/blog/debounce-vs-throttle","thumbnailSvg":"\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\u003csvg viewBox=\"0 0 499 665\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\u003e\u003cdefs\u003e\u003clinearGradient x1=\"100%\" y1=\"50%\" x2=\"0%\" y2=\"50%\" id=\"m8oi1nt__linearGradient-1\"\u003e\u003cstop stop-color=\"#FFFFFF\" stop-opacity=\"0\" offset=\"0%\"/\u003e\u003cstop stop-color=\"#95A2B8\" offset=\"100%\"/\u003e\u003c/linearGradient\u003e\u003clinearGradient x1=\"93.4036474%\" y1=\"50%\" x2=\"0%\" y2=\"50%\" id=\"m8oi1nt__linearGradient-2\"\u003e\u003cstop stop-color=\"#FFFFFF\" stop-opacity=\"0\" offset=\"0%\"/\u003e\u003cstop stop-color=\"#485569\" offset=\"100%\"/\u003e\u003c/linearGradient\u003e\u003c/defs\u003e\u003cg id=\"m8oi1nt__thumbnail\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\"\u003e\u003cg id=\"m8oi1nt__Group-5\" transform=\"translate(249.750000, 332.250000) rotate(90.000000) translate(-249.750000, -332.250000) translate(21.000000, 240.000000)\"\u003e\u003cpath d=\"M92.1747965,0.5 C119.538983,0.5 144.118126,12.4015715 160.999613,31.3014158 C146.473831,47.5617385 137.647696,69.0020968 137.647696,92.5 C137.647696,115.997903 146.473831,137.438261 160.997676,153.698584 C144.118126,172.598429 119.538983,184.5 92.1747965,184.5 C41.2680621,184.5 0,143.310197 0,92.5 C0,41.689803 41.2680621,0.5 92.1747965,0.5 Z\" id=\"m8oi1nt__Combined-Shape\" fill=\"url(#m8oi1nt__linearGradient-1)\"/\u003e\u003ccircle id=\"m8oi1nt__Oval-Copy-2\" fill=\"url(#m8oi1nt__linearGradient-2)\" cx=\"228.5\" cy=\"92\" r=\"92\"/\u003e\u003ccircle id=\"m8oi1nt__Oval-Copy-3\" fill=\"#1D293B\" cx=\"365.5\" cy=\"92\" r=\"92\"/\u003e\u003c/g\u003e\u003c/g\u003e\u003c/svg\u003e","frontmatter":{"title":"Debounce vs Throttle: Definitive Visual Guide","description":"A complete guide to learn the difference between debounce and throttle using visual examples. Never confuse the two again.","category":"Engineering","date":"2019-12-23T00:00:00.000Z","keywords":["debounce","throttle","javascript","event","events handling","response rate","ball machine","ball vending machine","interactive example","definitive guide"],"hashtags":["javascript","debounce","tips"]}}]},"__N_SSG":true},"page":"/","query":{},"buildId":"oo96ePibyKlOO0pCGCn3K","isFallback":false,"gsp":true,"scriptLoader":[]}</script><script defer="" src="https://static.cloudflareinsights.com/beacon.min.js/vcd15cbe7772f49c399c6a5babf22c1241717689176015" integrity="sha512-ZpsOmlRQV6y907TI0dKBHq9Md29nnaEIPlkf84rnaERnq6zvWvPUqr2ft8M1aS28oN72PdrCzSjY4U6VaAw1EQ==" data-cf-beacon="{&quot;rayId&quot;:&quot;8d9b40d79c8c384c&quot;,&quot;version&quot;:&quot;2024.10.4&quot;,&quot;r&quot;:1,&quot;serverTiming&quot;:{&quot;name&quot;:{&quot;cfExtPri&quot;:true,&quot;cfL4&quot;:true,&quot;cfSpeedBrain&quot;:true,&quot;cfCacheStatus&quot;:true}},&quot;token&quot;:&quot;89e4673fdcdd440f8968f334dd2e907a&quot;,&quot;b&quot;:1}" crossorigin="anonymous"></script>
<next-route-announcer><p aria-live="assertive" id="__next-route-announcer__" role="alert" style="border: 0px; clip: rect(0px, 0px, 0px, 0px); height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: absolute; width: 1px; white-space: nowrap; overflow-wrap: normal;"></p></next-route-announcer><script src="/_next/static/chunks/pages/teaching-a4c73ffdc870bf00.js"></script><script src="/_next/static/chunks/572-7a0badd3a48c7b1e.js"></script><script src="/_next/static/chunks/pages/blog-5cc819db625e760f.js"></script></body></html>