<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kristian Ivanov</title>
    <description>The latest articles on DEV Community by Kristian Ivanov (@k_ivanow).</description>
    <link>https://dev.to/k_ivanow</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F12027%2Fjlhav8Ea.jpeg</url>
      <title>DEV Community: Kristian Ivanov</title>
      <link>https://dev.to/k_ivanow</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/k_ivanow"/>
    <language>en</language>
    <item>
      <title>Cognitive Biases in Software Engineering: How They Impact Developers and Managers</title>
      <dc:creator>Kristian Ivanov</dc:creator>
      <pubDate>Tue, 15 Apr 2025 08:10:25 +0000</pubDate>
      <link>https://dev.to/k_ivanow/cognitive-biases-in-software-engineering-how-they-impact-developers-and-managers-5hno</link>
      <guid>https://dev.to/k_ivanow/cognitive-biases-in-software-engineering-how-they-impact-developers-and-managers-5hno</guid>
      <description>&lt;h2&gt;
  
  
  Cognitive Biases in Software Engineering: How They Impact Developers and Managers
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;A practical guide to recognizing and mitigating the mental shortcuts that undermine technical decision-making&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F15904%2F0%2AVSZrcLoihU4jRNde" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F15904%2F0%2AVSZrcLoihU4jRNde" alt="Photo by [Annie Spratt](https://unsplash.com/@anniespratt?utm_source=medium&amp;amp;utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral)" width="600" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Let’s face it
&lt;/h2&gt;

&lt;p&gt;We developers and tech managers love to think we’re the most logical people on the planet. After all, we spend our days working with machines that operate on pure logic. But here’s the uncomfortable truth: our brains are just as messy and biased as everyone else’s, maybe even more so because we don’t expect it.&lt;/p&gt;

&lt;p&gt;I’ve spent many years both writing code and managing teams, and I’ve seen firsthand how cognitive biases can totally derail technical projects. So let me break down the most problematic mental shortcuts I’ve witnessed (&lt;em&gt;and more often than not in myself, but hey the first step is admitting you have a problem&lt;/em&gt;), with a particular focus on how they manifest differently depending on whether you’re the one writing the code or the one managing the people writing the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 10 Cognitive Biases That Are Probably Screwing Up Your Code or Team Right Now
&lt;/h2&gt;

&lt;h2&gt;
  
  
  1. Recency Bias
&lt;/h2&gt;

&lt;p&gt;Ever noticed how your team suddenly becomes obsessed with fixing that one bug a customer complained about yesterday, while ignoring the critical issue affecting hundreds of users for months?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For Developers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You’re ready to rewrite the entire codebase using that shiny framework you read about on Hacker News this morning&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You focus all your energy on that bug Karen from Marketing reported yesterday while ignoring the performance issue affecting thousands of users&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You’re convinced Pattern X is the best approach to every problem because it worked well in the last project (which, by the way, was completely different)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For Engineering Managers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;“Our last retrospective revealed X, so clearly X is our biggest problem company-wide!”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You prioritize whatever feature was mentioned in the last executive meeting&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;That developer who did great in the last sprint must be your star player (never mind the six months of mediocre performance before that)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Confirmation Bias
&lt;/h2&gt;

&lt;p&gt;When you know you’re right and spend three hours googling just to find that one random forum post from 2011 that agrees with you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For Developers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You write tests designed to confirm your code works rather than trying to break it (that would be, you know, the actual point of testing)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;“Users are just using it wrong” is your go-to explanation for negative feedback&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You somehow always find documentation that supports your approach while missing all contradictory evidence&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For Engineering Managers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You hear what you want to hear in status updates, filtering out warning signs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You keep pushing a technical direction despite mounting evidence it’s turning into a disaster&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The team members who agree with you are “insightful” while those who raise concerns are “not seeing the big picture”&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Availability Heuristic
&lt;/h2&gt;

&lt;p&gt;That one time the production database crashed and became the stuff of company legend? Now you’ve got three engineers dedicated to database optimization while your frontend is held together with duct tape and prayers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For Developers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You’re certain SQL injection attacks are the biggest security threat because you read an article about one last week&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You build elaborate protection against edge cases you’ve personally encountered while ignoring more common scenarios&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You keep solving problems from your last job even though your current company faces completely different challenges&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For Engineering Managers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The bug that caused the CEO’s demo to fail gets five engineers assigned to it immediately&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You completely restructure support processes because one customer with a dramatic story complained loudly&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;“We’re never hiring junior developers again” after one bad experience&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Anchoring Bias
&lt;/h2&gt;

&lt;p&gt;“It’s just a simple CRUD app, right? Should take a couple of days!” — Words that have launched a thousand death marches and countless weekend deployments. This ties very well with the Dunning-Kruger effect, and often then leads to the sunk cost fallacy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For Developers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You committed to the “couple of days” estimate and now you’re working nights and weekends to avoid admitting it was wildly unrealistic&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your first solution becomes THE solution because you’ve already mentally committed to it&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You “optimize” code until it matches some arbitrary benchmark you set before understanding the actual requirements&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For Engineering Managers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You promise delivery dates based on when marketing wants the feature, not on what engineering says is possible&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You lowball salary offers based on what candidates made at their previous jobs rather than what they’re worth&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You judge team performance based on their first few sprints, ignoring all evidence of improvement or changing circumstances&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Sunk Cost Fallacy
&lt;/h2&gt;

&lt;p&gt;Sometimes it’s better to cut your losses and start over with an updated approach. This can also be applied for giving up on your startup — if it is failing cut it. Close it down. Don’t stay simply because you’ve already invested X years into it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For Developers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;“I’ve already spent two weeks on this approach, I can’t switch now” (even though a better solution would take days instead of weeks)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You keep using a problematic library because “we’ve already invested time in learning it”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You continue debugging using the same approach for days, despite making zero progress&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For Engineering Managers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You keep funding projects that no longer align with business goals because “we’ve already spent so much”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You maintain ancient systems requiring expensive specialists rather than migrating to something maintainable&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You stick with failing processes because admitting they don’t work feels like admitting failure&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Dunning-Kruger Effect
&lt;/h2&gt;

&lt;p&gt;This effect is especially dangerous because it follows a predictable pattern: beginners with minimal knowledge often have maximum confidence. As engineers gain experience, they realize how much they don’t know (the “valley of despair”). Truly senior engineers tend to have measured confidence — they know what they know, but also recognize the boundaries of their expertise.&lt;/p&gt;

&lt;p&gt;What makes Dunning-Kruger particularly toxic in software teams is that those most affected are least equipped to recognize it in themselves. The same knowledge gaps causing overconfidence prevent accurate self-assessment. This is why peer review processes are essential — they provide external reality checks that help calibrate your internal confidence meter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For Developers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You roll your own solution for everything instead of using external solutions, because ‘how hard can it be’?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You dismiss industry best practices as “overkill” because you don’t understand the problems they solve&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You write “clever” code without comments because it seems obvious to you now (future you and your colleagues will hate you)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For Engineering Managers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You override technical decisions made by your specialists because “how complicated could it be?”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You commit to deadlines without consulting the people who’ll do the work&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You evaluate engineers’ technical skills in areas where your own expertise is limited, often favoring those who speak confidently over those who are actually competent&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. Self-serving Bias
&lt;/h2&gt;

&lt;p&gt;Come on, this is the description of your favorite co-worker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For Developers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;“The deployment succeeded because of my brilliant code, but it failed because ops screwed up the configuration”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You take credit for all the features users love while blaming “unclear requirements” for the parts they hate&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When your code has bugs, it’s because of “edge cases,” but when others’ code has bugs, it’s because of incompetence&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For Engineering Managers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Your team succeeded because of your amazing leadership, but they failed because they didn’t execute properly&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You claim credit for on-time deliveries but blame “unforeseen technical challenges” when deadlines slip&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You view successful quarters as proof of your strategy and unsuccessful ones as market anomalies&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  8. Survivorship Bias
&lt;/h2&gt;

&lt;p&gt;Who hasn’t heard of a failed start up doing the best and most glamorous architecture, instead of something simpler and easier to maintain, because all of the bigger companies are doing it?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For Developers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;“We should use microservices because that’s what Netflix does!” (ignoring that Netflix evolved to that architecture after years of learning and has hundreds of engineers to support it)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You learn programming by following success stories without studying the far more common failure patterns&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You adopt complex architectures used by tech giants without considering whether they solve problems you actually have&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For Engineering Managers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You build development processes based only on successful projects, ignoring what went wrong in failures&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your hiring process favors candidates from successful companies without evaluating their actual contributions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You apply management techniques that worked at your last company without adapting them to your current team’s context&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  9. Status Quo Bias
&lt;/h2&gt;

&lt;p&gt;Who hasn’t joined a company and tried to suggest an improvement only to be cut down with the argument of — we are used to doing it this way?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For Developers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;“We’ve always used this approach” becomes the single most annoying phrase you’ll ever hear&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You resist learning new tools or techniques because your current workflow is comfortable&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You defend problematic legacy code because rewriting it would disrupt your routine&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For Engineering Managers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You maintain inefficient processes because changing them would require effort&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You block organizational improvements because they’d disrupt established team dynamics&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You stick with obsolete technical approaches because everyone already knows them&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  10. Bandwagon Effect
&lt;/h2&gt;

&lt;p&gt;Who hasn’t had a colleague that want’s to use something so cutting edge that has a handful of stars and forks on GitHub instead of something that was tried and tested?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For Developers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You choose technologies based on Twitter hype rather than project requirements&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You implement patterns because “everyone is doing it” without understanding why&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You follow trendy opinions on best practices without critically evaluating them&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For Engineering Managers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;“We need to be doing agile!” (without understanding what that actually means for your specific context)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You let the loudest team members dictate decisions regardless of merit&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You prioritize features because competitors have them, without asking if your users need them&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to Not Be a Walking Cognitive Bias Disaster
&lt;/h2&gt;

&lt;h2&gt;
  
  
  For Developers
&lt;/h2&gt;

&lt;p&gt;**Document your assumptions&lt;br&gt;
 — **Write down what you’re taking for granted before you start coding&lt;br&gt;
 — Future you will thank present you when those assumptions change&lt;/p&gt;

&lt;p&gt;**Actively seek contradiction&lt;br&gt;
 — **Don’t ask colleagues “Does this look good?” Ask “What am I missing?”&lt;br&gt;
 — The goal isn’t validation; it’s finding problems before your users do&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Set time limits for explorations&lt;br&gt;
 — *&lt;/em&gt;“I’ll spend exactly two hours evaluating this new tech” prevents rabbit holes&lt;br&gt;
 — Your excitement about a new framework is a terrible decision-making tool&lt;/p&gt;

&lt;p&gt;**Break down tasks to expose complexity&lt;br&gt;
 — **If you can’t explain exactly how you’ll implement something, you don’t understand it well enough to estimate it&lt;br&gt;
 — Details reveal the dragons hiding in seemingly simple tasks&lt;/p&gt;

&lt;h2&gt;
  
  
  For Engineering Managers
&lt;/h2&gt;

&lt;p&gt;**Require written proposals for major decisions&lt;br&gt;
 — **Force articulation of alternatives and trade-offs&lt;br&gt;
 — Make implicit assumptions explicit where everyone can see (and question) them&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Assign devil’s advocates&lt;br&gt;
*&lt;/em&gt; — Make it someone’s explicit job to argue against the prevailing view&lt;br&gt;
 — This lowers the social cost of dissent and surfaces important counterarguments&lt;/p&gt;

&lt;p&gt;**Track estimate accuracy&lt;br&gt;
 — **Compare estimates vs. actuals as a learning exercise (not a blame game)&lt;br&gt;
 — Look for patterns in what types of tasks consistently fool everyone&lt;/p&gt;

&lt;p&gt;**Diversify your information diet&lt;br&gt;
 — **Seek input from people with different backgrounds and experience levels&lt;br&gt;
 — Build teams with complementary biases to counterbalance each other&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Cognitive biases aren’t character flaws — they’re part of the standard human operating system. The difference between average and exceptional engineers isn’t the absence of bias, but the awareness of it.&lt;/p&gt;

&lt;p&gt;By recognizing these patterns in ourselves and our teams, we can build guardrails that compensate for our predictable irrationality. The most powerful tool against bias isn’t more technical knowledge — it’s metacognition, the practice of thinking about how we think.&lt;/p&gt;

&lt;p&gt;I’d love to hear what biases you’ve seen derail technical projects and what strategies you’ve found to combat them. Drop your experiences in the comments!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://levelup.gitconnected.com/cognitive-biases-in-software-engineering-how-they-impact-developers-and-managers-c55b5db5e781?source=rss-762ab83d8bc8------2" rel="noopener noreferrer"&gt;If you want to help out, read it out and clap at Medium as well&lt;/a&gt;&lt;/p&gt;

</description>
      <category>careeradvice</category>
      <category>lessonslearned</category>
      <category>teamwork</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Chrome’s Offscreen API: The Hidden Powerhouse for Modern Extensions</title>
      <dc:creator>Kristian Ivanov</dc:creator>
      <pubDate>Mon, 24 Feb 2025 14:13:47 +0000</pubDate>
      <link>https://dev.to/k_ivanow/chromes-offscreen-api-the-hidden-powerhouse-for-modern-extensions-10c5</link>
      <guid>https://dev.to/k_ivanow/chromes-offscreen-api-the-hidden-powerhouse-for-modern-extensions-10c5</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AzW9X8WpjWpmVAYqB" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AzW9X8WpjWpmVAYqB" width="1024" height="683"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Photo by Markus Spiske on Unsplash&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;While building &lt;a href="https://chromewebstore.google.com/detail/web-%C3%A0-la-carte/kmabmmhdgfdapmefodmilkncnnommkmm?authuser=0&amp;amp;hl=en-GB" rel="noopener noreferrer"&gt;Web à la Carte&lt;/a&gt;, I discovered that service workers can’t do everything we need in modern extensions. Enter the Offscreen API — Chrome’s solution for running DOM operations in the background. Let’s dive into how this powerful API can transform your extensions.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Background/Info
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Offscreen API&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;— Released: Late 2022&lt;br&gt;&lt;br&gt;
 — Part of Manifest V3’s solutions&lt;br&gt;&lt;br&gt;
 — Designed to replace background page capabilities&lt;br&gt;&lt;br&gt;
 — Currently in stable release&lt;/p&gt;
&lt;h3&gt;
  
  
  Why Do We Need It?
&lt;/h3&gt;

&lt;p&gt;With Manifest V3’s shift to service workers, we lost the ability to:&lt;/p&gt;

&lt;p&gt;— Use DOM APIs in the background&lt;br&gt;&lt;br&gt;
 — Play audio (&lt;em&gt;something I’ve used heavily in an older (&lt;/em&gt; &lt;strong&gt;&lt;em&gt;no longer supported&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;)&lt;/em&gt; &lt;a href="https://chromewebstore.google.com/detail/music-meal-audio-player-p/dnfclojpjpcpkepcobligoibhnjfpllm?authuser=0&amp;amp;hl=en-GB" rel="noopener noreferrer"&gt;&lt;em&gt;extension of mine&lt;/em&gt;&lt;/a&gt;)&lt;br&gt;&lt;br&gt;
 — — Something to keep in mind is that if you are usingthe AUDIO_PLAYBACK reason it will close the offscreen document after 30 seconds without audio playing.&lt;br&gt;&lt;br&gt;
 — Create canvases&lt;br&gt;&lt;br&gt;
 — Make AJAX requests with certain headers&lt;br&gt;&lt;br&gt;
 — Use WebSockets&lt;/p&gt;

&lt;p&gt;The Offscreen API solves these problems by providing a real DOM environment that runs in the background.&lt;/p&gt;
&lt;h3&gt;
  
  
  What Makes It Special?
&lt;/h3&gt;

&lt;p&gt;The Offscreen API is unique in several ways:&lt;br&gt;&lt;br&gt;
— Creates a hidden document with full DOM access&lt;br&gt;&lt;br&gt;
 — Runs in the background without opening new windows&lt;br&gt;&lt;br&gt;
— Inherits extension permissions (with some limits)&lt;br&gt;&lt;br&gt;
 — Only uses chrome.runtime API for communication&lt;/p&gt;
&lt;h3&gt;
  
  
  Important Limitations
&lt;/h3&gt;

&lt;p&gt;Before diving in, there are some key things to understand:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You can only use static HTML files from your extension
&lt;/li&gt;
&lt;li&gt;Only one offscreen document can be open at a time*
&lt;/li&gt;
&lt;li&gt;Documents can’t be focused
&lt;/li&gt;
&lt;li&gt;The window.opener property is always null
&lt;/li&gt;
&lt;li&gt;Only chrome.runtime API is available&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;*Note: In split mode with an active incognito profile, you can have one document per profile.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  The Basics
&lt;/h3&gt;

&lt;p&gt;First, declare it in your manifest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "manifest_version": 3,
  "permissions": ["offscreen"],
  "background": {
    "service_worker": "background.js"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create an offscreen document:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// background.js
async function createOffscreen() {
  // Check if we already have an offscreen document
  const existing = await chrome.offscreen.getOffscreenDocument();
  if (existing) return;

  // Create an offscreen document
  await chrome.offscreen.createDocument({
    url: 'offscreen.html',
    reasons: ['AUDIO_PLAYBACK'], // Or other reasons
    justification: 'Playing notification sounds' // Explain why you need it
  });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Real World Examples
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Audio Notifications
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// background.js
async function playNotificationSound() {
  await createOffscreen();
  // Send message to offscreen document
  chrome.runtime.sendMessage({
    target: 'offscreen',
    type: 'PLAY_SOUND',
    sound: 'notification.mp3'
  });
}

// offscreen.html
const audio = new Audio();
chrome.runtime.onMessage.addListener((message) =&amp;gt; {
  if (message.target !== 'offscreen') return;

  if (message.type === 'PLAY_SOUND') {
    audio.src = message.sound;
    audio.play();
  }
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Image Processing
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// background.js
async function processImage(imageUrl) {
  await createOffscreen();

  return new Promise((resolve) =&amp;gt; {
    chrome.runtime.onMessage.addListener(function listener(message) {
      if (message.type === 'IMAGE_PROCESSED') {
        chrome.runtime.onMessage.removeListener(listener);
        resolve(message.data);
      }
    });

    chrome.runtime.sendMessage({
      target: 'offscreen',
      type: 'PROCESS_IMAGE',
      imageUrl
    });
  });
}

// offscreen.html
async function processImage(url) {
  const img = new Image();
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');

  await new Promise((resolve) =&amp;gt; {
    img.onload = resolve;
    img.src = url;
  });

  canvas.width = img.width;
  canvas.height = img.height;
  ctx.drawImage(img, 0, 0);

  // Apply image processing
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  // ... process image data ...

  return canvas.toDataURL();
}

chrome.runtime.onMessage.addListener(async (message) =&amp;gt; {
  if (message.type === 'PROCESS_IMAGE') {
    const processed = await processImage(message.imageUrl);
    chrome.runtime.sendMessage({
      type: 'IMAGE_PROCESSED',
      data: processed
    });
  }
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Mastering the Offscreen API
&lt;/h3&gt;

&lt;h4&gt;
  
  
  The Art of Resource Management
&lt;/h4&gt;

&lt;p&gt;Think of the Offscreen API like a smart assistant — don’t keep them around when you don’t need them. Here’s a pattern I’ve found incredibly useful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class OffscreenManager {
  constructor() {
    this.activeOperations = new Map();
    this.retryAttempts = 0;
    this.maxRetries = 3;
  }

  async execute(task, timeout = 5000) {
    const operationId = crypto.randomUUID();

    try {
      // Create offscreen document if needed
      await this.ensureOffscreen();

      // Set up task tracking
      const operation = {
        startTime: Date.now(),
        status: 'running',
        timeout: setTimeout(() =&amp;gt; this.handleTimeout(operationId), timeout)
      };

      this.activeOperations.set(operationId, operation);

      // Execute and track
      const result = await task();
      this.completeOperation(operationId);
      return result;

    } catch (error) {
      if (this.retryAttempts &amp;lt; this.maxRetries) {
        this.retryAttempts++;
        return this.execute(task, timeout);
      }
      throw error;
    }
  }

  async cleanup() {
    if (this.activeOperations.size === 0) {
      await chrome.offscreen.closeDocument();
      this.retryAttempts = 0;
    }
  }
}

// Usage
const manager = new OffscreenManager();

// Process multiple images in batch
const results = await manager.execute(async () =&amp;gt; {
  const images = ['image1.jpg', 'image2.jpg', 'image3.jpg'];
  return Promise.all(images.map(img =&amp;gt; processImage(img)));
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Smart Error Recovery
&lt;/h4&gt;

&lt;p&gt;Instead of just handling errors, let’s make our offscreen operations self-healing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const smartOffscreen = {
  async createWithFallback(options) {
    try {
      await chrome.offscreen.createDocument(options);
    } catch (error) {
      // If creation fails, check if we have zombie documents
      const existing = await chrome.offscreen.getOffscreenDocuments();
      if (existing.length &amp;gt; 0) {
        // Clean up and retry
        await Promise.all(existing.map(doc =&amp;gt; 
          chrome.offscreen.closeDocument(doc.url)));
        await chrome.offscreen.createDocument(options);
      }
    }
  },

  // Or automatically retry failed operations
  async withRetry(operation, maxAttempts = 3) {
    for (let attempt = 1; attempt &amp;lt;= maxAttempts; attempt++) {
      try {
        return await operation();
      } catch (error) {
        if (attempt === maxAttempts) throw error;
        // Exponential backoff
        await new Promise(r =&amp;gt; setTimeout(r, Math.pow(2, attempt) * 100));
      }
    }
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These patterns make your offscreen operations more robust and self-managing. The key is to treat your offscreen document like a valuable resource — use it wisely, monitor it carefully, and clean up when you’re done.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to Use the Offscreen API
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Use It When:
&lt;/h4&gt;

&lt;p&gt;— You need DOM APIs in the background&lt;br&gt;&lt;br&gt;
 — You’re playing audio or processing media&lt;br&gt;&lt;br&gt;
 — You need WebSocket connections&lt;br&gt;&lt;br&gt;
 — You’re doing complex canvas operations&lt;br&gt;&lt;br&gt;
 — You need to use certain web APIs unavailable in service workers&lt;/p&gt;

&lt;h4&gt;
  
  
  Don’t Use It When:
&lt;/h4&gt;

&lt;p&gt;— Simple data processing is enough&lt;br&gt;&lt;br&gt;
 — Content scripts or a background script can handle the task&lt;br&gt;&lt;br&gt;
 — You don’t need DOM capabilities&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;The Offscreen API bridges the gap between service workers and traditional background pages. While it shouldn’t be your go-to solution for everything, it’s invaluable when you need DOM capabilities in the background.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Remember:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
 — Only create offscreen documents when needed&lt;br&gt;&lt;br&gt;
 — Clean up resources when done&lt;br&gt;&lt;br&gt;
 — Use appropriate reasons and justifications&lt;br&gt;&lt;br&gt;
 — Consider performance implications&lt;/p&gt;

&lt;p&gt;If you found this article helpful, feel free to clap and follow for more JavaScript and Chrome.API tips and tricks.&lt;/p&gt;

&lt;p&gt;You can also give a recent chrome extension I released that uses a lot of the functionalities from the articles — &lt;a href="https://chromewebstore.google.com/detail/web-%C3%A0-la-carte/kmabmmhdgfdapmefodmilkncnnommkmm" rel="noopener noreferrer"&gt;Web à la Carte&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have gotten this far, I thank you and I hope it was useful to you! Here is a cool image of cats next to a screen that is off as a thank you!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AChPw_LilakBorMFd" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AChPw_LilakBorMFd" width="1024" height="1536"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Photo by Rhamely on Unsplash&lt;/em&gt;&lt;/p&gt;




</description>
      <category>chrome</category>
      <category>chromeextension</category>
      <category>javascripttips</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Content Scripts vs chrome.scripting: Understanding Modern Extension Development</title>
      <dc:creator>Kristian Ivanov</dc:creator>
      <pubDate>Mon, 17 Feb 2025 03:07:33 +0000</pubDate>
      <link>https://dev.to/k_ivanow/content-scripts-vs-chromescripting-understanding-modern-extension-development-3bc9</link>
      <guid>https://dev.to/k_ivanow/content-scripts-vs-chromescripting-understanding-modern-extension-development-3bc9</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AHmkrvHTvG0rKjrno" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AHmkrvHTvG0rKjrno" width="1024" height="683"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Photo by Swello on Unsplash&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;While building extensions, I keep seeing a common question pop up: “&lt;em&gt;Should I use content scripts or chrome.scripting?&lt;/em&gt;” The answer isn’t as straightforward as you might think, especially since content scripts are more powerful than many developers realize. Let’s dive into the real differences and when to use each approach.&lt;/p&gt;
&lt;h3&gt;
  
  
  The background/info
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Content Scripts&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
 — Released: December 2009 (Chrome Extensions Launch)&lt;br&gt;&lt;br&gt;
 — Part of the original extension system&lt;br&gt;&lt;br&gt;
 — Updated in Manifest V2 (2012) with more options&lt;br&gt;&lt;br&gt;
 — Updated in Manifest V3 (2021) with additional features like world: "MAIN"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;chrome.scripting&lt;br&gt;&lt;br&gt;
 — &lt;/strong&gt; Released: January 2021 (with Manifest V3)&lt;br&gt;&lt;br&gt;
 — Designed to replace chrome.tabs.executeScript&lt;br&gt;&lt;br&gt;
 — Part of the move away from background pages to service workers&lt;br&gt;&lt;br&gt;
 — Considered the modern approach to script injection&lt;/p&gt;
&lt;h3&gt;
  
  
  The Basics
&lt;/h3&gt;

&lt;p&gt;Both content scripts and chrome.scripting let you inject code into web pages, but they serve different purposes and have different strengths.&lt;/p&gt;
&lt;h4&gt;
  
  
  Content Scripts
&lt;/h4&gt;

&lt;p&gt;Content scripts can work in two ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Isolated World&lt;/strong&gt; (Default):
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// manifest.json
{
 "content_scripts": [{
 "matches": ["&amp;lt;all_urls&amp;gt;"],
 "js": ["content.js"]
 }]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Main World&lt;/strong&gt; (Page Context):
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// manifest.json
{
 "content_scripts": [{
 "matches": ["&amp;lt;all_urls&amp;gt;"],
 "js": ["content.js"],
 "world": "MAIN"
 }]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  chrome.scripting
&lt;/h4&gt;

&lt;p&gt;Chrome’s scripting API provides dynamic injection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await chrome.scripting.executeScript({
 target: { tabId },
 func: () =&amp;gt; {
 // Your code here
 }
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Real Differences
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Execution Timing
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Content Scripts:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
 — Load automatically when pages match&lt;br&gt;&lt;br&gt;
 — Can run at document_start, document_end, or document_idle&lt;br&gt;&lt;br&gt;
 — Stay active throughout page lifetime&lt;br&gt;&lt;br&gt;
 — Can be dynamically injected too&lt;/p&gt;

&lt;p&gt;Manifest-based injection&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "content_scripts": [
    {
      "matches": ["&amp;lt;all_urls&amp;gt;"],
      "js": ["early.js"],
      "run_at": "document_start"
    },
    {
      "matches": ["&amp;lt;all_urls&amp;gt;"],
      "js": ["late.js"],
      "run_at": "document_idle"
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dynamic injection&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chrome.tabs.executeScript({
  file: 'dynamic.js',
  runAt: 'document_start'
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;chrome.scripting:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
 — Runs on demand&lt;br&gt;&lt;br&gt;
 — Single execution context&lt;br&gt;&lt;br&gt;
 — Cleanup after execution&lt;br&gt;&lt;br&gt;
 — Better for one-off tasks&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function modifyPage(tabId) {
  await chrome.scripting.executeScript({
    target: { tabId },
    func: () =&amp;gt; {
      // Do something once and finish
    }
  });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. State Management
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Content Scripts:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let state = {
  observers: new Set(),
  modifications: new Map()
};

// State persists across functions
function addObserver(target) {
  const observer = new MutationObserver(() =&amp;gt; {
    // Handle changes
  });
  observer.observe(target);
  state.observers.add(observer);
}

function cleanup() {
  state.observers.forEach(o =&amp;gt; o.disconnect());
  state.observers.clear();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;chrome.scripting:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Each execution gets fresh state
async function observePage(tabId) {
  await chrome.scripting.executeScript({
    target: { tabId },
    func: () =&amp;gt; {
      // State only lives for this execution
      const observer = new MutationObserver(() =&amp;gt; {
        // Handle changes
      });
      observer.observe(document.body);

      // State is lost after execution
    }
  });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Context Access
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Content Scripts (Isolated):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// content.js (default world)
// No access to page's variables
window.pageVariable; // undefined

// Need message passing for page communication
window.postMessage({ type: 'getData' }, '*');
window.addEventListener('message', (e) =&amp;gt; {
  if (e.data.type === 'response') {
    // Handle data
  }
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Content Scripts (Main World):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// content.js with world: "MAIN"
// Direct access to page context
window.pageVariable; // Works!
document.querySelector('#app').shadowRoot; // Works!

// Can modify page functions
window.originalFunc = window.someFunc;
window.someFunc = function() {
  // Modified behavior
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;chrome.scripting:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chrome.scripting.executeScript({
  target: { tabId },
  func: () =&amp;gt; {
    // Always runs in page context
    window.pageVariable; // Works!

    // Can modify prototype methods
    Element.prototype.originalRemove = Element.prototype.remove;
    Element.prototype.remove = function() {
      // Custom remove logic
    };
  }
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4. Performance Considerations
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Content Scripts:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Loaded with the page, always running&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const observer = new MutationObserver(() =&amp;gt; {
  // Constant monitoring
});

observer.observe(document.body, {
  childList: true,
  subtree: true
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;chrome.scripting:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Only runs when needed&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function checkPage(tabId) {
  const results = await chrome.scripting.executeScript({
    target: { tabId },
    func: () =&amp;gt; {
      // Quick operation and done
      return document.querySelectorAll('.target').length;
    }
  });
  return results[0].result;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Real World Example
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Element Removal
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Content Script Approach:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// manifest.json
{
 "content_scripts": [{
 "matches": ["&amp;lt;all_urls&amp;gt;"],
 "js": ["darkmode.js"],
 "world": "MAIN" // We want page context
 }]
}

// content.js - Always running, waiting for commands
chrome.runtime.onMessage.addListener((message, sender, sendResponse) =&amp;gt; {
  if (message.type === 'removeElement') {
    const { selector } = message;
    const elements = document.querySelectorAll(selector);
    const count = elements.length;

    elements.forEach(el =&amp;gt; el.remove());
    sendResponse({ count });
  }
});

// background.js - Sends command to content script
async function removeElement(tabId, selector) {
  return new Promise((resolve) =&amp;gt; {
    chrome.tabs.sendMessage(tabId, {
      type: 'removeElement',
      selector
    }, (response) =&amp;gt; {
      resolve(response?.count || 0);
    });
  });
}


// Usage:
chrome.action.onClicked.addListener(async (tab) =&amp;gt; {
  const count = await removeElement(tab.id, '.ad-container');
  console.log(`Removed ${count} elements`);
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;chrome.scripting Approach:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// background.js - Direct injection and execution
async function removeElement(tabId, selector) {
  const results = await chrome.scripting.executeScript({
    target: { tabId },
    args: [selector],
    func: (selector) =&amp;gt; {
      const elements = document.querySelectorAll(selector);
      const count = elements.length;
      elements.forEach(el =&amp;gt; el.remove());
      return count;
    }
  });

  return results[0].result;
}

// Usage - exactly the same:
chrome.action.onClicked.addListener(async (tab) =&amp;gt; {
  const count = await removeElement(tab.id, '.ad-container');
  console.log(`Removed ${count} elements`);
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;The choice between content scripts and chrome.scripting isn’t mutually exclusive. Modern extensions often use both:&lt;/p&gt;

&lt;p&gt;— Content scripts for persistent features and state management&lt;br&gt;&lt;br&gt;
 — Chrome.scripting for heavy lifting and one-off operations&lt;br&gt;&lt;br&gt;
 — Communication between the two for complex features&lt;/p&gt;

&lt;p&gt;The key is understanding their strengths:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Content Scripts When:&lt;/strong&gt;  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You need persistent monitoring
&lt;/li&gt;
&lt;li&gt;You want automatic injection on page load
&lt;/li&gt;
&lt;li&gt;You’re maintaining state across operations
&lt;/li&gt;
&lt;li&gt;You need early page load intervention
&lt;/li&gt;
&lt;li&gt;You want declarative injection via manifest&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Use chrome.scripting When:&lt;/strong&gt;  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You need one-time operations
&lt;/li&gt;
&lt;li&gt;You want to pass data directly to injected code
&lt;/li&gt;
&lt;li&gt;You’re doing heavy DOM manipulation
&lt;/li&gt;
&lt;li&gt;You need cleaner error handling
&lt;/li&gt;
&lt;li&gt;You want better performance for occasional operations&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Use Both When:&lt;/strong&gt;  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Building complex features
&lt;/li&gt;
&lt;li&gt;Handling both persistent and one-off tasks
&lt;/li&gt;
&lt;li&gt;Managing state while doing heavy lifting
&lt;/li&gt;
&lt;li&gt;Optimizing performance for different operations&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you found this article helpful, feel free to clap and follow for more JavaScript and Chrome.API tips and tricks.&lt;/p&gt;

&lt;p&gt;You can also give a recent chrome extension I released that uses a lot of the functionalities from the articles — &lt;a href="https://chromewebstore.google.com/detail/web-%C3%A0-la-carte/kmabmmhdgfdapmefodmilkncnnommkmm" rel="noopener noreferrer"&gt;Web à la Carte&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have gotten this far, I thank you and I hope it was useful to you! Here is a cool image of a cat (no scripting pun though) as a thank you!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AgNnnB6QvvqR6KQCp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F1024%2F0%2AgNnnB6QvvqR6KQCp" width="1024" height="714"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Photo by Chris Barbalis on Unsplash&lt;/em&gt;&lt;/p&gt;




</description>
      <category>javascript</category>
      <category>development</category>
      <category>chromeextension</category>
      <category>chrome</category>
    </item>
    <item>
      <title>MutationObserver: The Silent DOM Watcher (And Its TreeWalker Friend)</title>
      <dc:creator>Kristian Ivanov</dc:creator>
      <pubDate>Thu, 28 Nov 2024 01:33:17 +0000</pubDate>
      <link>https://dev.to/k_ivanow/mutationobserver-the-silent-dom-watcher-and-its-treewalker-friend-5e57</link>
      <guid>https://dev.to/k_ivanow/mutationobserver-the-silent-dom-watcher-and-its-treewalker-friend-5e57</guid>
      <description>&lt;p&gt;&lt;a href="https://levelup.gitconnected.com/mutationobserver-the-silent-dom-watcher-and-its-treewalker-friend-72fdefe635a3?source=rss-762ab83d8bc8------2" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2600%2F0%2ABw-2503HV7BqkDYQ" width="760" height="1139"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MutationObserver: Your DOM’s security camera. Watch elements change, appear &amp;amp; disappear without polling 🔍&lt;/p&gt;

&lt;p&gt;&lt;a href="https://levelup.gitconnected.com/mutationobserver-the-silent-dom-watcher-and-its-treewalker-friend-72fdefe635a3?source=rss-762ab83d8bc8------2" rel="noopener noreferrer"&gt;Continue reading on Level Up Coding »&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>development</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>TreeWalker: A Practical Guide to DOM Traversal</title>
      <dc:creator>Kristian Ivanov</dc:creator>
      <pubDate>Mon, 18 Nov 2024 06:45:57 +0000</pubDate>
      <link>https://dev.to/k_ivanow/treewalker-a-practical-guide-to-dom-traversal-hn6</link>
      <guid>https://dev.to/k_ivanow/treewalker-a-practical-guide-to-dom-traversal-hn6</guid>
      <description>&lt;p&gt;Recently I've started working on a new Chrome extension in my free time and in the research on how to handle some of the functionalities, I've started discovering more and more functionalities that JS has in dealing with the DOM of the page.&lt;/p&gt;

&lt;p&gt;Given how an overwhelming number of people using JS are using it only through a framework, this would make an interesting topic for a series of short articles for people to learn a bit more about the underlying technologies that the frameworks they rely on are actually using.&lt;/p&gt;

&lt;p&gt;We've all been there — you need to find specific elements in the DOM, but &lt;code&gt;querySelector&lt;/code&gt; and &lt;code&gt;getElementsBy*&lt;/code&gt; aren't quite cutting it. Maybe you need to find all text nodes containing a specific phrase, or you want to traverse elements matching certain conditions while skipping others. Enter TreeWalker - a powerful but often overlooked DOM traversal API.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is TreeWalker?
&lt;/h2&gt;

&lt;p&gt;TreeWalker is a DOM interface that lets you efficiently traverse and filter DOM nodes. Think of it as a more powerful and flexible alternative to methods like &lt;code&gt;querySelector&lt;/code&gt;. While &lt;code&gt;querySelector&lt;/code&gt; gives you elements matching a CSS selector, TreeWalker lets you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navigate the DOM tree in any direction (forward, backward, up, down)&lt;/li&gt;
&lt;li&gt;Filter nodes based on custom conditions&lt;/li&gt;
&lt;li&gt;Skip certain parts of the tree entirely&lt;/li&gt;
&lt;li&gt;Access text nodes directly (something &lt;code&gt;querySelector&lt;/code&gt; can't do)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Creating a TreeWalker
&lt;/h2&gt;

&lt;p&gt;Let's start with a basic example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;walker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createTreeWalker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Root node to start traversal&lt;/span&gt;
    &lt;span class="nx"&gt;NodeFilter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SHOW_TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Only show text nodes&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;acceptNode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Only accept text nodes that aren't empty&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
                &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;NodeFilter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FILTER_ACCEPT&lt;/span&gt;
                &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NodeFilter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FILTER_REJECT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The three parameters are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Root node — where to start traversing&lt;/li&gt;
&lt;li&gt;What types of nodes to show (text, elements, comments, etc.)&lt;/li&gt;
&lt;li&gt;A filter function that decides which nodes to accept or reject&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Real World Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Find and Replace Text
&lt;/h3&gt;

&lt;p&gt;Here's something you'll actually use — finding and replacing text while preserving HTML structure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;replaceText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;walker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createTreeWalker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;NodeFilter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SHOW_TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;acceptNode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;NodeFilter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FILTER_ACCEPT&lt;/span&gt;
                    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NodeFilter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FILTER_REJECT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;walker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nextNode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Usage&lt;/span&gt;
&lt;span class="nf"&gt;replaceText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;old text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is much more efficient than using innerHTML and won't break event listeners or form input values.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Custom DOM Query
&lt;/h3&gt;

&lt;p&gt;Need to find elements matching complex conditions? TreeWalker has you covered. Let's build something more complex — say you need to find all &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; elements that contain specific text, but only if they're inside &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; elements with a certain class, and ignore any that are inside &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; elements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;findElementsByComplexCondition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;walker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createTreeWalker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;NodeFilter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SHOW_ELEMENT&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;NodeFilter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SHOW_TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;acceptNode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Skip nodes we don't care about early&lt;/span&gt;
                &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodeType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ELEMENT_NODE&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
                    &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tagName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BUTTON&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NodeFilter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FILTER_REJECT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Skip button and its contents&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="c1"&gt;// Check for matching span elements&lt;/span&gt;
                &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodeType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ELEMENT_NODE&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
                    &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tagName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SPAN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="c1"&gt;// Check if parent is a div with required class&lt;/span&gt;
                    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parentElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
                        &lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tagName&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DIV&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
                        &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parentClass&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NodeFilter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FILTER_SKIP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;

                    &lt;span class="c1"&gt;// Check if span contains the required text&lt;/span&gt;
                    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NodeFilter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FILTER_SKIP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;

                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NodeFilter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FILTER_ACCEPT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NodeFilter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FILTER_SKIP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;walker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nextNode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This would match:&lt;/p&gt;

&lt;p&gt;✅ Will match:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"message-container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;Error: Invalid input&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;❌ Won't match all of the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"other-container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;Error: Invalid input&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;Error: Invalid input&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"message-container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;Success!&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;[Rest of code examples...]&lt;/p&gt;

&lt;h2&gt;
  
  
  The Swiss Army Knife of DOM Traversal
&lt;/h2&gt;

&lt;p&gt;TreeWalker isn't limited to forward traversal. You can move in any direction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Move to next node&lt;/span&gt;
&lt;span class="nx"&gt;walker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nextNode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Move to previous node&lt;/span&gt;
&lt;span class="nx"&gt;walker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;previousNode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Move to first child&lt;/span&gt;
&lt;span class="nx"&gt;walker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;firstChild&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Move to last child&lt;/span&gt;
&lt;span class="nx"&gt;walker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lastChild&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Move to parent&lt;/span&gt;
&lt;span class="nx"&gt;walker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parentNode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When Should You Use TreeWalker?
&lt;/h2&gt;

&lt;p&gt;TreeWalker shines when:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You need to find text nodes (&lt;code&gt;querySelector&lt;/code&gt; can't do this)&lt;/li&gt;
&lt;li&gt;You have complex filtering requirements&lt;/li&gt;
&lt;li&gt;You need to traverse the DOM in a specific order&lt;/li&gt;
&lt;li&gt;Performance matters (TreeWalker is generally faster than recursive DOM traversal)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  TypeScript Support
&lt;/h2&gt;

&lt;p&gt;Good news for TypeScript users — the types are built right in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;TreeWalker&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;currentNode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NodeFilter&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;whatToShow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;firstChild&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;lastChild&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;nextNode&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;nextSibling&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;parentNode&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;previousNode&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;previousSibling&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;TreeWalker is one of those APIs that, once you know about it, you'll find yourself reaching for more often than you might expect. It's particularly useful for building accessibility tools, content management systems, or any application that needs to analyze or modify DOM content programmatically.&lt;/p&gt;

&lt;p&gt;While it might seem complex at first, TreeWalker's power and flexibility make it worth adding to your toolkit. Next time you find yourself writing a recursive DOM traversal function, consider whether TreeWalker might be a better fit.&lt;/p&gt;

&lt;p&gt;P.S. If you've made it this far, here's a pro tip: Combine TreeWalker with MutationObserver to create powerful DOM monitoring tools. But that's a topic for another article... 😉&lt;/p&gt;




&lt;p&gt;If you found this article helpful, feel free to like and follow for more JavaScript tips and tricks.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover photo by Branko Stancevic on Unsplash&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1660739675626-bf7876d554cb" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1660739675626-bf7876d554cb" alt="Cat in a tree" width="720" height="1081"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover photo by Branko Stancevic on Unsplash&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Cat photo by Jan Gustavsson on Unsplash&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>dom</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Express &lt;&gt; NextJS &lt;&gt; NestJS Cheatsheet for beginners</title>
      <dc:creator>Kristian Ivanov</dc:creator>
      <pubDate>Sun, 29 Sep 2024 10:00:52 +0000</pubDate>
      <link>https://dev.to/k_ivanow/express-nextjs-nestjs-cheatsheet-for-beginners-7bf</link>
      <guid>https://dev.to/k_ivanow/express-nextjs-nestjs-cheatsheet-for-beginners-7bf</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;I recently had to learn NestJS to get ready for a project that was written on it. Not NextJS, mind you, but NestJS. Like some of you, I’ve heard about it, but never had a chance to actually use it. It has been growing in popularity for quite a while now, catching up with the grandfather of NodeJS back-end development — ExpressJS just earlier this year. Here is their official tweet:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F3664%2F0%2AgezqTLT6kRjiwJQI" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F3664%2F0%2AgezqTLT6kRjiwJQI" alt="[https://x.com/nestframework/status/1772586212408041758](https://x.com/nestframework/status/1772586212408041758)" width="1832" height="1308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That being said, there is a big discrepancy on npm in terms of pure downloads — Express stands tall at &lt;a href="https://www.npmjs.com/package/express" rel="noopener noreferrer"&gt;32,397,019 weekly downloads&lt;/a&gt; at the time of writing, &lt;a href="https://www.npmjs.com/package/next?activeTab=readme" rel="noopener noreferrer"&gt;Next is at 7,263,688&lt;/a&gt;, and &lt;a href="https://www.npmjs.com/package/@nestjs/core" rel="noopener noreferrer"&gt;Nest is at 3,579,676&lt;/a&gt;. Now Nest really is tied to Express to some extent, which will be mentioned in the article.&lt;/p&gt;

&lt;p&gt;I want to highlight that NestJS has &lt;a href="https://nestjs.com/" rel="noopener noreferrer"&gt;excellent documentation&lt;/a&gt; (&lt;em&gt;filled with cats as a bonus, but only on desktop…&lt;/em&gt;) that provides more than enough information to get you started and explain all of its basic and advanced use cases. Always a great place to start!&lt;/p&gt;

&lt;p&gt;If you are like me, after an N number of frameworks and languages, the easiest way to learn a new one is to compare and match it to the things you know. Because of that, I thought a comparison between NestJS and the more popular/traditional/established JS frameworks could be useful to understand some of the concepts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Express.js&lt;/strong&gt; is great for lightweight, flexible APIs or small projects with minimal restrictions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Next.js&lt;/strong&gt; is ideal for React-based apps where SEO and server-side rendering are crucial.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;NestJS&lt;/strong&gt; excels in large-scale, enterprise-level applications with a focus on modularity and maintainability. It is also by far the most opinionated. A lot of those opinions are coming from other languages that some of the more experienced &lt;em&gt;(to avoid saying older devs)&lt;/em&gt; are familiar with — inversion of control, dependency injection — Java + Spring, .NET Core, Angular (the main inspiration for NestJS), PHP + Laravel or Symfony, etc.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s start with some basic simple examples and work our way up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Starting a Simple Server
&lt;/h3&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
And finally NextJS -&amp;gt; next dev

&lt;p&gt;In NestJS, the server is bootstrapped by creating an application using NestFactory and specifying the main application module. The structure is more organized with AppModule at the core.&lt;/p&gt;

&lt;p&gt;ExpressJS is more straightforward, creating the server directly by invoking express().&lt;/p&gt;

&lt;p&gt;Next.js doesn't require manually setting up a server unless you're using a custom server—its built-in server handles everything under the hood.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Handling a Route
&lt;/h3&gt;

&lt;p&gt;Where most of your work lies.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
NestJS uses the @Controller and @Get decorators to map routes to controller methods, offering more structure with modular routing. Get used to the decorators and annotations for NestJS. They are a critical part of the language (same as Java + Spring for instance).

&lt;p&gt;ExpressJS allows you to define routes freely using app.get(), which provides flexibility but less built-in structure.&lt;/p&gt;

&lt;p&gt;And in Next.js, routing is file-based, where the file name in the pages directory automatically maps to a route, giving you simplicity but less control over route definition.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Handling Query Parameters
&lt;/h3&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
ExpressJS relies on req.query to access query parameters, offering a more manual approach.

&lt;p&gt;Next.js uses the useRouter hook to retrieve query parameters within the component, aligning more with React conventions.&lt;/p&gt;

&lt;p&gt;NestJS uses the @Query() decorator to easily extract query parameters in a controller method.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Handling Path Parameters
&lt;/h3&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
In ExpressJS, path parameters are defined in the route itself using :paramName. For example, app.get('/users/:id') defines the id as a dynamic parameter. You can then access this parameter using req.params.paramName, which is flexible but less structured than NestJS.

&lt;p&gt;In Next.js, path parameters are handled using &lt;strong&gt;dynamic routes&lt;/strong&gt;. To define a path parameter in Next.js, you create a file with square brackets, such as [id].js, inside the pages directory. The file name ([id]) is used to create a route that dynamically matches any value passed as id. You then use the useRouter() hook to access the parameter within the component. This approach is simple and React-friendly but not as explicit as the NestJS approach.&lt;/p&gt;

&lt;p&gt;In NestJS, path parameters are defined using the &lt;a class="mentioned-user" href="https://dev.to/param"&gt;@param&lt;/a&gt;() decorator in combination with the &lt;a class="mentioned-user" href="https://dev.to/get"&gt;@get&lt;/a&gt;(':id') decorator. The &lt;a class="mentioned-user" href="https://dev.to/get"&gt;@get&lt;/a&gt;(':id') specifies that the id is dynamic, and &lt;a class="mentioned-user" href="https://dev.to/param"&gt;@param&lt;/a&gt;('id') is used to extract the parameter in the controller. Path parameters in NestJS are explicitly defined and tied to controller methods, making them highly structured.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Adding Middleware
&lt;/h3&gt;

&lt;p&gt;With a lot of the basics out of the way, let’s get to something more interesting. Middleware is used for various things — checking permissions, validations, logging, and much more. While in Express it is just middleware, in NestJS several different things are doing similar work.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Next.js doesn’t have built-in middleware support in the same way that NestJS and Express do. Middleware functions are generally implemented in custom servers (like Express).

&lt;p&gt;Notice how both Express and Nest have a very similar structure. It is time to share the fact that Nest actually uses Express or Fastify under the hood.&lt;/p&gt;

&lt;p&gt;In ExpressJS, middleware is more flexible and can be applied globally using app.use() or scoped to specific routes by passing the middleware function as a second argument in route definitions. Express is more unopinionated, giving developers freedom in how middleware is structured.&lt;/p&gt;

&lt;p&gt;NestJS offers a highly structured and modular way to handle middleware, integrating it into its dependency injection system, which is ideal for large applications requiring reusable middleware logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Dependency Injection
&lt;/h3&gt;

&lt;p&gt;This is one of the more opinionated parts of NestJS when compared to the other two frameworks.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
NestJS heavily relies on &lt;strong&gt;Dependency Injection&lt;/strong&gt; (DI), which is a core part of its architecture. You can inject services using the @Injectable decorator. Meanwhile, neither Express nor Next support it out of the box, so typically, you’d pass dependencies manually.

&lt;p&gt;The dependency injection is one of the core design patterns used NestJS. It fits into it the following way:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;NestJS uses an IoC container to manage dependency lifecycles, automatically injecting dependencies where needed. This shifts the responsibility of creating and managing dependencies from the developer to the framework, streamlining the setup, and reducing manual effort when wiring services, controllers, and other components.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;With DI, NestJS enforces a consistent pattern across the application, ensuring that all dependencies are handled the same way. This makes the architecture predictable and reduces confusion.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;As your application grows, the IoC container in NestJS efficiently handles an increasing number of dependencies, ensuring smooth scaling. This prevents the chaos of manually managing complex service dependencies, making the codebase more maintainable and reducing the likelihood of errors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DI in NestJS encourages loose coupling by separating services and their dependencies, making it easier to modify or replace parts of the system without breaking the entire application. This decoupling also enables easier testing and mocking of service.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While all of these points are great for large-scale applications, the DI pattern can be a complication for smaller projects, especially if you are not used to it.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Inversion of control
&lt;/h3&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
NestJS automates dependency management using an IoC container, while ExpressJS and Next.js rely on manual instantiation of dependencies. Notice how we are just saying that userService or userRepository are of a specific type. After that, all of the additional handling is taken care of by NestJS. We don’t have to think about when to spawn those components, how many instances of them will be there, or anything else. We do have control over those of course.

&lt;p&gt;This makes the last example much more scalable, modular, and easier to test.&lt;/p&gt;

&lt;p&gt;The obvious cons to this approach are — the increased learning curve. This is true for all opinionated frameworks, as well as that it might feel like an overkill for small applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  8 — Permissions Management
&lt;/h3&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
While we are on the subject of differences between NestJS and the other two frameworks, I think it is time to mention Guards. Guards are a core feature for managing permissions. They provide a clean, scalable way to control access based on user roles or any other condition. Guards are highly reusable and can be applied to specific routes or globally across the application.

&lt;p&gt;Express and Next are relying on middleware or manual checks on the session, which makes it easier for small apps, but grows in complexity for the larger the application scales up.&lt;/p&gt;

&lt;h3&gt;
  
  
  9— Data transformation — Pipes
&lt;/h3&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Similar to how Guards work, Pipes are NestJS specific feature. Their main goal in this example is to manipulate or transform the data before passing it onto the controller.

&lt;p&gt;While the same can be achieved in ExpressJS and NextJS with middleware, using Pipes provides for better error handling and easier maintenance at scale.&lt;/p&gt;

&lt;p&gt;Not to mention you have the freedom to create your own custom pipes.&lt;/p&gt;

&lt;h3&gt;
  
  
  10 — Data validation — Pipes pt.2
&lt;/h3&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Notice that while the annotations/decorators can be odd at first, depending on where you are coming from, the larger the code grows, some things are actually easier to maintain and read. Personal opinion, but while the example in NestJS is longer by a few lines, each line is much shorter and easier to follow.

&lt;p&gt;Additionally, it is once again highlighting how things are more maintainable at scale with NestJS.&lt;/p&gt;

&lt;p&gt;You can check NestJS’s &lt;a href="https://docs.nestjs.com/pipes#class-validator" rel="noopener noreferrer"&gt;official docs&lt;/a&gt; to see a longer version of this example with additional explanations. It involves a longer explanation than what I am aiming for in this article, so I’ll just be linking it.&lt;/p&gt;

&lt;p&gt;Thank you for making it to the end! I was aiming for a few examples between these mostly recognized frameworks and NestJS. This is the first article in a short series I’ll be making on NestJS and trying to tie in its concepts to more common frameworks as a way to illustrate and make it more easy to remember and understand.&lt;/p&gt;

&lt;p&gt;If you have gotten this far, I thank you and I hope it was useful to you! Here is a cool image of a cat as a thank you!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F6048%2F0%2A2_k8lLLDsqolUdSf" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F6048%2F0%2A2_k8lLLDsqolUdSf" alt="Photo by [Jesson Mata](https://unsplash.com/@jessonmata?utm_source=medium&amp;amp;utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral)" width="760" height="1013"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>15 Linux commands I’ve used most recently and love in general</title>
      <dc:creator>Kristian Ivanov</dc:creator>
      <pubDate>Tue, 24 Sep 2024 13:58:27 +0000</pubDate>
      <link>https://dev.to/k_ivanow/15-linux-commands-ive-used-most-recently-and-love-in-general-6o2</link>
      <guid>https://dev.to/k_ivanow/15-linux-commands-ive-used-most-recently-and-love-in-general-6o2</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F14016%2F0%2Aei1BgvjKsT2o_s8-" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F14016%2F0%2Aei1BgvjKsT2o_s8-" alt="Photo by [Lukas](https://unsplash.com/@lukash?utm_source=medium&amp;amp;utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral)" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Intro
&lt;/h3&gt;

&lt;p&gt;I’ve been using Linux for 15+ years. Not because I am a hacker or anything. It is just easier to set up dev environments compared to Windows, it has better hardware options than Mac. There is the occasional Nvidia driver issue, but as long as you are using it for work and don’t expect it to have a good gaming performance, despite a lot of advancements in Proton, Lutris being amazing and Steam having Linux support for some titles like Dota2.&lt;/p&gt;

&lt;p&gt;It provides extreme flexibility and options to configure everything to your liking. It also provides tens of thousands of commands (&lt;em&gt;based on the number of man pages&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;The problem with this is that, as a biology teacher of mine used to say, the main function of the brain is to forget things it doesn’t need all the time. Because there are so many different commands, there is always something new to discover and a lot of things that were forgotten, because they aren’t used on a daily basis.&lt;/p&gt;

&lt;p&gt;So with all of that in mind, I’ll try to share N number of useful commands I’ve discovered or used last week or so from time to time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reading large files —less
&lt;/h3&gt;

&lt;p&gt;When dealing with large files, such as logs, opening them in an editor can be slow and memory-intensive. less provides an efficient way to view and navigate large files without loading the entire file into memory.&lt;/p&gt;

&lt;p&gt;less largefile.txt&lt;/p&gt;

&lt;p&gt;In this example, /error can be used to search for the keyword "error" within the file, and F allows you to follow new lines as they are added (similar to tail -f).&lt;/p&gt;

&lt;p&gt;You can efficiently scroll through and search thousands of lines of logs to find specific errors without opening the entire file in a text editor. This is especially useful when working with large system logs or data files.&lt;/p&gt;

&lt;p&gt;Try opening something large with nano or vim and see the difference.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://linux.die.net/man/1/tail" rel="noopener noreferrer"&gt;Link for more details&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Find Files by Size — find
&lt;/h3&gt;

&lt;p&gt;Disk space can quickly fill up, especially on systems with large media files, backups, or logs. Instead of manually checking file sizes across directories, you can use find to locate large files that might be hogging space.&lt;/p&gt;

&lt;p&gt;find / -type f -size +500M&lt;/p&gt;

&lt;p&gt;In this example, we are searching for all files larger than 500MB across the entire file system.&lt;/p&gt;

&lt;p&gt;Automatically find and address large files taking up space without manually traversing directories. It’s especially helpful when performing system maintenance or managing disk space on servers.&lt;/p&gt;

&lt;p&gt;Recently I had an unpleasant experience files taking tens of gigabytes of storage space. It took me quite some time to figure out the root cause, and have since remembered that command fondly.&lt;/p&gt;

&lt;p&gt;You can also use find to recursively remove files based on pattern&lt;/p&gt;

&lt;p&gt;find /path -name "*.log" -type f -delete&lt;/p&gt;

&lt;p&gt;This finds and deletes all .log files under the specified directory and its subdirectories.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://linux.die.net/man/1/find" rel="noopener noreferrer"&gt;Link for more details&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Search Inside Compressed Files — zgrep
&lt;/h3&gt;

&lt;p&gt;Instead of decompressing these files every time you need to search for something inside them, zgrep allows you to search within compressed files directly.&lt;/p&gt;

&lt;p&gt;zgrep "ERROR" /var/log/system.log.gz&lt;/p&gt;

&lt;p&gt;Example of a return&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Jun 23 10:15:22 ERROR failed to start service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The matched line from within the compressed file is returned, without needing to extract the contents first.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://linux.die.net/man/1/zgrep" rel="noopener noreferrer"&gt;Link for more details&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Preview File Contents with Head/Tail
&lt;/h3&gt;

&lt;p&gt;When analyzing logs or large files, sometimes you only need to see the first or last few lines, such as in cases of debugging or inspecting recently added log entries. head and tail make this quick and efficient. This is particularly beneficial when dealing with large files where only the start or end matters (e.g., logs).&lt;/p&gt;

&lt;p&gt;head -n 20 access.log&lt;/p&gt;

&lt;p&gt;tail -n 20 access.log&lt;/p&gt;

&lt;p&gt;In both examples we are showing up to 20 lines of data. In the first one, from the start of the document and in the second one, from the end of it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://linux.die.net/man/1/head" rel="noopener noreferrer"&gt;Link for more details&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://linux.die.net/man/1/tail" rel="noopener noreferrer"&gt;Link for more details&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Bulk Text Replacement with sed
&lt;/h3&gt;

&lt;p&gt;When working with configuration files, scripts, or large datasets, you may need to replace certain text patterns across multiple files. Instead of manually opening each file, sed automates the text replacement process in bulk.&lt;/p&gt;

&lt;p&gt;sed -i 's/oldword/newword/g' *.txt&lt;/p&gt;

&lt;p&gt;This replaces all occurrences of “oldword” with “newword” in all .txt files within the current directory.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://linux.die.net/man/1/sed" rel="noopener noreferrer"&gt;Link for more details&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Find and Replace for Complex Patterns — perl
&lt;/h3&gt;

&lt;p&gt;When you need to perform more complex text replacements, such as matching patterns using regular expressions, perl is a powerful tool. It handles advanced search-and-replace scenarios that sed might not handle well.&lt;/p&gt;

&lt;p&gt;perl -pi -e 's/(abc\d+)/xyz$1/g' *.log&lt;/p&gt;

&lt;p&gt;This replaces all instances of “abc” followed by digits (e.g., “abc123”) with “xyz” followed by the same digits (e.g., “xyz123”) in all .log files.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://linux.die.net/man/1/perl" rel="noopener noreferrer"&gt;Link for more details&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Sort and Uniquely Count Lines
&lt;/h3&gt;

&lt;p&gt;Analyzing log files or datasets often requires identifying unique lines and counting their occurrences. It’s useful for tasks like understanding user activity or identifying common patterns in logs.&lt;/p&gt;

&lt;p&gt;sort access.log | uniq -c&lt;/p&gt;

&lt;p&gt;The sort command sorts the file, and uniq -c counts how many times each unique line appears.&lt;/p&gt;

&lt;p&gt;10 127.0.0.1 — — [23/Jun/2023] “GET /feature1”&lt;br&gt;
5 192.168.1.1 — — [23/Jun/2023] “GET /feature2”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://linux.die.net/man/1/sort" rel="noopener noreferrer"&gt;Link for more details&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://linux.die.net/man/1/uniq" rel="noopener noreferrer"&gt;Link for more details&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitor Multiple Log Files Simultaneously with— multitail
&lt;/h3&gt;

&lt;p&gt;Using multitail, you can view and track changes in multiple log files in real-time in a single terminal. Let’s be honest, after a while the terminal is the same as the browser — too many tabs opened. This way, you can keep things more maintainable.&lt;/p&gt;

&lt;p&gt;multitail /var/log/syslog /var/log/auth.log&lt;/p&gt;

&lt;p&gt;This command opens both syslog and auth.log in separate panes within the terminal, showing updates to both logs as they occur.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://linux.die.net/man/1/multitail" rel="noopener noreferrer"&gt;Link for more details&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Parallel Processing with — xargs
&lt;/h3&gt;

&lt;p&gt;When performing tasks like file deletion, backups, or processing large numbers of files, doing these tasks sequentially can take a long time. xargs allows you to run commands in parallel, significantly reducing execution time.&lt;/p&gt;

&lt;p&gt;find ~/files-to-transfer/ -type f | xargs -P 4 -I {} scp {} &lt;a href="//mailto:user@remote-server.com"&gt;user@remote-server.com&lt;/a&gt;:/remote/path/&lt;/p&gt;

&lt;p&gt;The command will transfer multiple files in parallel to the remote server.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;find ~/files-to-transfer/ -type f: This finds all the files in the ~/files-to-transfer/ directory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;xargs: Passes these files as arguments to scp.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;-P 4: This runs up to 4 parallel scp processes, speeding up the file transfer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;-I {}: This tells xargs to replace {} with the name of each file found by find.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;scp {}: Copies each file {} to the remote server (&lt;a href="mailto:user@remote-server.com"&gt;user@remote-server.com&lt;/a&gt;:/remote/path/).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://linux.die.net/man/1/xargs" rel="noopener noreferrer"&gt;Link for more details&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Batch File Renaming with — mmv
&lt;/h3&gt;

&lt;p&gt;mmv is incredibly powerful when you need to rename large sets of files with complex rules. It allows you to use wildcards and placeholders for advanced renaming tasks, such as adding prefixes, changing parts of file names, or moving files across directories in bulk.&lt;/p&gt;

&lt;p&gt;mmv ‘backup_*.txt’ ‘#1_$(date +%Y-%m-%d).bak’&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;'backup_*.txt': This pattern matches all files starting with backup_ and ending with .txt.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;h1&gt;
  
  
  1: This is a placeholder that captures the part of the filename after backup_ (i.e., report1, report2, data1, data2).
&lt;/h1&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;$(date +%Y-%m-%d): This shell command inserts the current date in the format YYYY-MM-DD.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;.bak: The new file extension for the renamed files.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://ss64.com/bash/mmv.html" rel="noopener noreferrer"&gt;Link for more details&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Search for Text in Multiple Files with — grep
&lt;/h3&gt;

&lt;p&gt;When debugging or searching through large codebases, logs, or configuration files, you often need to find specific keywords or patterns across many files. grep allows you to search through multiple files quickly and efficiently.&lt;/p&gt;

&lt;p&gt;It is basically ctrl+shift+f for your terminal&lt;/p&gt;

&lt;p&gt;grep -Hnr “ERROR” /var/log/&lt;/p&gt;

&lt;p&gt;This will recursively search for the word “ERROR inside all log files in the specified directory.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ss64.com/bash/grep.html" rel="noopener noreferrer"&gt;Link for more details&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Filter Log Files with — awk
&lt;/h3&gt;

&lt;p&gt;Log files and datasets are often structured in columns (e.g., CSV files or tab-separated data). awk allows you to extract specific columns and apply filters based on conditions, making it ideal for data analysis and report generation.&lt;/p&gt;

&lt;p&gt;awk ‘$3 &amp;gt; 100’ file.txt&lt;/p&gt;

&lt;p&gt;This command prints only the lines where the value in the third column is greater than 100. A return from it can look something like&lt;/p&gt;

&lt;p&gt;line 5: 120&lt;br&gt;
line 9: 133&lt;br&gt;
line 13: 101&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ss64.com/bash/awk.html" rel="noopener noreferrer"&gt;Link for more details&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Extract Specific Columns from Files with — cut
&lt;/h3&gt;

&lt;p&gt;Similar to awk. Cut can be used to extract only certain columns/fields from files. The difference is the lack of filtering available without the filtering.&lt;/p&gt;

&lt;p&gt;cut -d’,’ -f2,3 file.csv&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;-d',': Specifies the delimiter (in this case, a comma).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;-f2,3: Extracts the second and third columns.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://ss64.com/bash/cut.html" rel="noopener noreferrer"&gt;Link for more details&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Convert Images from One Format to Another with — convert
&lt;/h3&gt;

&lt;p&gt;If you need to convert images from one format to another, such as converting .png files to .jpg, or resizing images for web optimization. The convert command from ImageMagick makes this easy. You can also use it for converting, resizing, optimizing, or applying other transformations in bulk.&lt;/p&gt;

&lt;p&gt;convert image.png image.jpg&lt;/p&gt;

&lt;p&gt;This one is self explanatory.&lt;/p&gt;

&lt;p&gt;convert input.png -resize 800x600 -gravity southeast -draw “image Over 0,0 0,0 ‘watermark.png’” -quality 85 output.jpg&lt;/p&gt;

&lt;p&gt;This one is a bit more interesting. Here are some details for it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;input.png: The original image you want to process.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;-resize 800x600: Resizes the image to 800x600 pixels while maintaining the aspect ratio (if the dimensions don't match exactly).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;-gravity southeast: Positions the watermark in the bottom-right corner of the image.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;-draw "image Over 0,0 0,0 'watermark.png'": Draws the watermark image (watermark.png) onto the original image at the specified position.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;-quality 85: Sets the output image quality to 85% (a balance between quality and file size for web use).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;output.jpg: The final processed image in .jpg format.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And another one:&lt;/p&gt;

&lt;p&gt;for img in *.png; do convert “$img” -resize 1024x768 -colorspace Gray “${img%.png}.jpg”; done&lt;/p&gt;

&lt;p&gt;This one is a bit complex as well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;for img in *.png; do ...; done: Loops through all .png files in the current directory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;convert "$img": Refers to each image file in the loop.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;-resize 1024x768: Resizes each image to 1024x768 pixels while maintaining the aspect ratio.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;-colorspace Gray: Converts each image to grayscale.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;"${img%.png}.jpg": Saves the output image in .jpg format with the same base name, but replacing .png with .jpg.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And my favorite use case — creating a .gif out of images:&lt;/p&gt;

&lt;p&gt;convert -delay 20 -loop 0 frame*.png animation.gif&lt;/p&gt;

&lt;p&gt;&lt;a href="https://linux.die.net/man/1/convert" rel="noopener noreferrer"&gt;Link for more details&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Killing process in a UX friendly way — xkill
&lt;/h3&gt;

&lt;p&gt;Speaking of my favorite use cases. The command I have used the most over the last 15+ years is xkill.&lt;/p&gt;

&lt;p&gt;Running xkill turns your mouse cursor into a crosshair (X), and you can click on any window to immediately force-close it.&lt;/p&gt;

&lt;p&gt;Why I love it? Because I can just click on the problematic window instead of manually searching for processes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://linux.die.net/man/1/xkill" rel="noopener noreferrer"&gt;Link for more details&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have gotten this far, I thank you and I hope it was useful to you! Here is a cool image of a cat as a thank you!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F12000%2F0%2Ax81CorXP9qPdN5Gp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F12000%2F0%2Ax81CorXP9qPdN5Gp" alt="Photo by [Yerlin Matu](https://unsplash.com/@yerlinmatu?utm_source=medium&amp;amp;utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral)" width="720" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>linux</category>
      <category>bash</category>
      <category>terminal</category>
    </item>
    <item>
      <title>Simplify Chrome Extension Development: Add React without CRA</title>
      <dc:creator>Kristian Ivanov</dc:creator>
      <pubDate>Mon, 23 Sep 2024 12:38:26 +0000</pubDate>
      <link>https://dev.to/k_ivanow/simplify-chrome-extension-development-add-react-without-cra-5h4c</link>
      <guid>https://dev.to/k_ivanow/simplify-chrome-extension-development-add-react-without-cra-5h4c</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F9030%2F0%2A5RWBPWN4gflrwtWd" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F9030%2F0%2A5RWBPWN4gflrwtWd" alt="Photo by [Lautaro Andreani](https://unsplash.com/@lautaroandreani?utm_source=medium&amp;amp;utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral)" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Intro
&lt;/h3&gt;

&lt;p&gt;We all love React, right (except for the Primeagen). It simplifies building complex UIs tremendously by providing an easy approach to handling states and access to a giant library of modules and components you can use from npm. It even comes up with CRA to bootstrap and make even the initial setup just one command.&lt;/p&gt;

&lt;p&gt;I am a fan of Create-React-App (CRA). It is great for most web apps and after having seen a lot of ejected apps from it, or ones that have been building/bundling it with gulp (I feel old sometimes) even without using TypeScript, I’ve grown to appreciate its simplicity and how easy it has made my life when starting any new project.&lt;/p&gt;

&lt;p&gt;However, it doesn’t really cut it when building something like a Chrome extension and generates far too much bloat. Additionally, in something like an extension, the UI can be just a fraction of the project, with most of the logic being wrapped in the background/service worker or in the content scripts. This means adding a ton of bloat and tying up everything with the way React is easiest to use is not always the best approach.&lt;/p&gt;

&lt;p&gt;Additionally, what if you started with something really simple on the UI side (just a few lines of HTML and tailwind) to try and test your idea, you wrote a bunch of code for the other parts of the extension and then decided to make the UI easier to maintain and scale and add React to it?&lt;/p&gt;

&lt;p&gt;In this guide, we’ll build a Chrome extension using TypeScript, Webpack and Tailwind, and only then will bring React &lt;strong&gt;without&lt;/strong&gt; CRA in much more lightweight way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;I recently wanted to start a very simple Chrome extension to explore some potential use cases &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver" rel="noopener noreferrer"&gt;Mutation Observer&lt;/a&gt; to customize pages. I also wanted to use this opportunity to play around with TypeScript since I haven’t had the chance to use it in a while.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1 — Set Up the Project&lt;/strong&gt;&lt;br&gt;
Let’s kick things off by creating a new directory for our project and initializing it with npm:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir ts-chrome-extension
cd ts-chrome-extension
npm init -y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2 — Let’s add TypeScript&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install --save-dev typescript
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Once installed, we need to create a TypeScript configuration file. This file will tell the TypeScript compiler how to behave. Let’s create the &lt;strong&gt;tsconfig.json&lt;/strong&gt; file:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "es2015"],
    "module": "esnext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;More details on the tsconfig.json file can be found in the &lt;a href="https://www.typescriptlang.org/docs/handbook/tsconfig-json.html" rel="noopener noreferrer"&gt;official docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3 — Let’s add the basic files&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir -p src/scripts &amp;amp;&amp;amp; mkdir src/popup &amp;amp;&amp;amp; touch src/manifest.json src/scripts/{background.ts,contentScript.ts} src/popup/{popup.html,popup.ts}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Notice the multiple creates with touch in the same command. This is a neat way to save time that I’ll cover in a different article soon.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;At this point, the structure of our folder should look like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── package.json
├── package-lock.json
├── popup.ts
├── src
│   ├── manifest.json
│   ├── popup
│   │   ├── popup.html
│   │   └── popup.ts
│   └── scripts
│       ├── background.ts
│       └── contentScript.ts
└── tsconfig.json

4 directories, 9 files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can list like this with the Linux tree command.&lt;br&gt;
&lt;em&gt;In this case with added *&lt;/em&gt;-I node_modules *&lt;em&gt;to avoid the usual npm bloat.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Each File Does:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;background.ts&lt;/strong&gt;: This script runs in the background to handle extension events like installation or message passing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;contentScript.ts&lt;/strong&gt;: This is injected into the web pages to allow the extension to interact with the page’s content.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;popup.html&lt;/strong&gt;: The HTML file that defines the UI layout for your popup window.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;popup.ts&lt;/strong&gt;: Initially, this file handles direct DOM manipulation or event listeners. We’ll later replace this with a React component.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4 — The Manifest File&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The manifest file is the backbone of your Chrome extension. It tells Chrome what your extension does and where to find its scripts. We’ve created ours, so let’s fill it out&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "manifest_version": 3,
  "name": "TypeScript Chrome Extension",
  "version": "1.0",
  "description": "A Chrome extension built using TypeScript.",
  "action": {
    "default_popup": "popup.html"
  },
  "background": {
    "service_worker": "background.js"
  },
  "content_scripts": [
    {
      "matches": ["&amp;lt;all_urls&amp;gt;"],
      "js": ["contentScript.js"]
    }
  ],
  "permissions": ["storage"]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here’s a breakdown of what’s happening:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Action and Popup&lt;/strong&gt;: The "action" key tells Chrome where the popup HTML is located. The popup will load when users click the extension icon.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Service Worker&lt;/strong&gt;: The "service_worker" in the "background" key is needed for background tasks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Content Scripts&lt;/strong&gt;: These allow the extension to interact with web pages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Permissions&lt;/strong&gt;: The "storage" permission enables the extension to store user data via Chrome’s storage API.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find the Chrome extension’s manifest.json full documentation &lt;a href="https://developer.chrome.com/docs/extensions/reference/manifest" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5 — The Popup Files&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Since we’re not using React yet, the popup will be simple and handle basic DOM interactions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5.1 —popup.html&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang="en"&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset="UTF-8" /&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&amp;gt;
    &amp;lt;title&amp;gt;Popup&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Hello, Chrome Extension!&amp;lt;/h1&amp;gt;
      &amp;lt;button id="alertButton"&amp;gt;Click me&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;script src="popup.js"&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is a simple HTML page with a button and a script reference to popup.js (which will be bundled from popup.ts).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5.2 — popup.ts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This file will handle basic interaction for the popup:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;document.addEventListener('DOMContentLoaded', () =&amp;gt; {
  const alertButton = document.getElementById('alertButton');

  if (alertButton) {
    alertButton.addEventListener('click', () =&amp;gt; {
      alert('Button clicked!');
    });
  }
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This simple script waits for the DOM to load, then adds a click listener to the button that triggers an alert. This structure sets the foundation for the popup functionality before we add React.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6 — Compile TypeScript&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s compile the TypeScript files into JavaScript. Run the following command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx tsc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This will transpile your TypeScript files into JavaScript. The popup.ts, background.ts, and contentScript.ts will be compiled to popup.js, background.js, and contentScript.js, respectively. But since we’ll soon add Webpack to manage the bundling, this is just a temporary step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7 — Adding webpack for Bundling&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now that we have the basic structure, we need Webpack to handle bundling all our scripts into optimized files that Chrome can load. It can handle the copying of icons, transpiling ts into js, automatically injecting script or style files and much more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7.1 — Install Webpack and necessary loaders&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install --save-dev webpack webpack-cli ts-loader html-webpack-plugin mini-css-extract-plugin css-loader postcss postcss-loader copy-webpack-plugin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;webpack&lt;/strong&gt;: The core bundler.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ts-loader&lt;/strong&gt;: Transpiles TypeScript for Webpack.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;html-webpack-plugin&lt;/strong&gt;: Helps generate HTML files with injected script tags.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CSS and PostCSS Loaders&lt;/strong&gt;: For handling CSS (we’ll use these later with Tailwind).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CopyWebpackPlugin&lt;/strong&gt;: The CopyWebpackPlugin ensures that the manifest.json and any other assets (like icons) are copied from the src folder to the dist folder.&lt;br&gt;
— We define patterns, such as copying manifest.json from src/ to the root of the dist/ directory.&lt;br&gt;
— If you have icons or other assets (e.g., images, fonts), you can add similar patterns.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;7.2 — Webpack Configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next, we need to configure Webpack to bundle everything. Create webpack.config.js at the root of your project:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const path = require('path'); // &amp;lt;-- This is missing and causes the ReferenceError

const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = (env, argv) =&amp;gt; {
  const isProduction = argv.mode === 'production';

  return {
    entry: {
      popup: './src/popup/popup.ts',
      background: './src/scripts/background.ts',
      contentScript: './src/scripts/contentScript.ts',
    },
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: '[name].js',
    },
    resolve: {
      extensions: ['.ts', '.js'],
    },
    module: {
      rules: [
        {
          test: /\.ts$/,
          use: 'ts-loader',
          exclude: /node_modules/,
        },
        {
          test: /\.css$/i,
          use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'],
        },
      ],
    },
    plugins: [
      new MiniCssExtractPlugin({
        filename: '[name].css',
      }),
      new HtmlWebpackPlugin({
        filename: 'popup.html',
        template: 'src/popup/popup.html',
        chunks: ['popup'],
      }),
      new CopyWebpackPlugin({
        patterns: [
          { from: 'src/manifest.json', to: 'manifest.json' },
//          { from: 'src/icons', to: 'icons' },  // Copy any additional assets
        ],
      }),
    ],
    mode: isProduction ? 'production' : 'development',
    devtool: isProduction ? false : 'inline-source-map',  // Disable source maps in production
  };
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Explanation&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Entry Points&lt;/strong&gt;: We’ve specified the entry points for popup.ts, background.ts, and contentScript.ts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;: The bundled files will be output to the dist folder.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Loaders&lt;/strong&gt;: We use ts-loader to handle TypeScript and css-loader/postcss-loader For CSS processing (later on when we introduce Tailwind CSS).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;HtmlWebpackPlugin&lt;/strong&gt;: This plugin automatically injects the bundled popup.js file into the popup.html file, ensuring that our scripts are correctly linked.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;7.3 — Build the Extension with Webpack&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s bundle everything using Webpack by running:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx webpack --mode development
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This will output the bundled files into the dist directory.&lt;/p&gt;

&lt;p&gt;If you are having issues because of the icons section, you can either remove it or put some placeholder .pngs for now.&lt;/p&gt;

&lt;p&gt;You will have something like this&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk24nrab61unzs7up78rw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk24nrab61unzs7up78rw.png" width="336" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you click the button you will get an alert. Twice. But why?&lt;/p&gt;

&lt;p&gt;Remember how I said that webpack can inject script tags among other things? Check out your built popup.js in the dist folder and you will see the popup.js has been injected twice (once from the HTML file itself, and once from webpack).&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang="en"&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset="UTF-8" /&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&amp;gt;
    &amp;lt;title&amp;gt;Popup&amp;lt;/title&amp;gt;
  &amp;lt;script defer src="popup.js"&amp;gt;&amp;lt;/script&amp;gt;&amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Hello, Chrome Extension!&amp;lt;/h1&amp;gt;
      &amp;lt;button id="alertButton"&amp;gt;Click me&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;script src="popup.js"&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To fix this, simply remove the script tag in the popup.html in the src/popup folder so it looks like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang="en"&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset="UTF-8" /&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&amp;gt;
    &amp;lt;title&amp;gt;Popup&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Hello, Chrome Extension!&amp;lt;/h1&amp;gt;
      &amp;lt;button id="alertButton"&amp;gt;Click me&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Don’t worry. Webpack will still add it inside.&lt;/p&gt;

&lt;p&gt;Test it out.&lt;/p&gt;

&lt;p&gt;Take a look at the dist folder as well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8 — Adding Tailwind for Styling.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Tailwind CSS is a utility-first CSS framework that allows you to style your UI without writing custom CSS for each component.&lt;/p&gt;

&lt;p&gt;Over the years people have fallen in love with it. Sure, it is easier to add some basic styling on your own, but when you know you will be spending time on that project, Tailwind makes it easier to scale later on and keep it consistent for other people if they join you (the same class will always mean the same thing, vs when you implement it yourself, font-size-small can mean any number of things.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8.1 — Installing Tailwind&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install tailwindcss postcss postcss-loader autoprefixer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;8.2 — Configure Tailwind and PostCSS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next, we need to initialize Tailwind and configure PostCSS. Start by initializing Tailwind with the following command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx tailwindcss init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This will create a tailwind.config.js File in the root of your project. Open this file and modify it to include the paths where Tailwind should apply its styles (in our case, inside the src folder):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module.exports = {
  content: ['./src/**/*.{ts,tsx,html}'],
  theme: {
    extend: {},
  },
  plugins: [],
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The content key tells Tailwind where to look for classes in your files.&lt;/p&gt;

&lt;p&gt;Next, create a postcss.config.js File in the root directory and configure it to use Tailwind and Autoprefixer:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;8.3 — Create the Tailwind CSS Entry File&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In src/styles, create a new file called tailwind.css. This is where we’ll import the basic Tailwind styles:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@tailwind base;
@tailwind components;
@tailwind utilities;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This file will include all of Tailwind’s utility classes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8.4 — Modify the Webpack Configuration to Handle CSS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We need to ensure Webpack knows how to process CSS files using PostCSS. We already installed postcss-loader and css-loader earlier, so now we just need to make sure it’s properly configured.&lt;/p&gt;

&lt;p&gt;Change this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  test: /\.css$/i,
  use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'],
},
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  test: /\.css$/i,
  use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'], // Process Tailwind CSS
},
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This ensures Webpack can process Tailwind CSS when we include it in our popup. Speaking of this…&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8.5 — Import Tailwin into the Popup&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To start using Tailwind, we need to import the tailwind.css file into our popup script (popup.ts). Open popup.ts and add this import statement at the top:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import '../styles/tailwind.css';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;8.6 — Add Some Simple Tailwind Styling&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s update the popup.html file to use some Tailwind classes for some basic styling. After all, all of the configs we just wrote and connected will be &lt;strong&gt;&lt;em&gt;almost&lt;/em&gt;&lt;/strong&gt; useless without it.&lt;/p&gt;

&lt;p&gt;Can you guess or explain why it is ‘&lt;strong&gt;&lt;em&gt;almost&lt;/em&gt;&lt;/strong&gt;’ and not &lt;strong&gt;&lt;em&gt;completely useless&lt;/em&gt;&lt;/strong&gt; if we add tailwind but not any tailwind classes? Discuss it in the comments ;)&lt;/p&gt;

&lt;p&gt;This is what our popup.html should look like now:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang="en"&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset="UTF-8" /&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&amp;gt;
    &amp;lt;title&amp;gt;Popup&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body class="bg-gray-100 p-4 w-80"&amp;gt;
    &amp;lt;div class="flex flex-col items-center"&amp;gt;
      &amp;lt;h1 class="text-xl font-bold mb-4"&amp;gt;Hello, Chrome Extension!&amp;lt;/h1&amp;gt;
      &amp;lt;button id="alertButton" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"&amp;gt;
        Click me
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;script src="popup.js"&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here, we’ve added some basic Tailwind classes to style the popup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Background&lt;/strong&gt;: The popup’s background color is a soft gray (bg-gray-100).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Centered Content&lt;/strong&gt;: Flexbox is used to center the content vertically and horizontally (flex flex-col items-center).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Styled Button&lt;/strong&gt;: The button has Tailwind’s built-in button styles for hover effects, colors, and rounded corners (bg-blue-500, hover:bg-blue-700, text-white, rounded).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s rebuild it and check the difference. -&amp;gt; npx webpack --mode development&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F33nj8nan9kq0xxq4rjhu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F33nj8nan9kq0xxq4rjhu.png" width="636" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Isn’t that better?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9 — Adding React to the Popup&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’ve got a functional extension with Tailwind styling and TypeScript for some syntax niceties and early problem detection. We also even have postcss and autoprefixer, which I’ll cover in a different article. And everything works.&lt;/p&gt;

&lt;p&gt;Imagine however you need to make the UI more complex to manage loading and unloading data, handling tabs, and showing different UI based on the user’s subscription status. We might be getting ahead of ourselves, but for the sake of this guide, let’s assume there is a reason for it and add React to solve all of these problems. In my use case I added React to handle loading a dynamic set of data (which could’ve been done as a simpler template, but in combination with color pickers and switches of the UI based&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9.1 — Install React and ReactDOM&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install react react-dom @types/react @types-react-dom
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;9.2 Update Webpack for React&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We need to update Webpack so it can handle .tsx files (React with TypeScript). Remember, this was the main goal we set up to do.&lt;/p&gt;

&lt;p&gt;To handle this, modify the webpack.config.js file to include React as an entry point:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;entry: {
    popup: './src/popup/popup.tsx',  // Update this to point to the new React component
    ...
  },
  resolve: {
    extensions: ['.ts', '.tsx', '.js'],  // Add .tsx to resolve for React files
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,  // Handle both .ts and .tsx files
        ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;9.3 — Convert popup.ts to React&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now we’re going to convert popup.ts to a React component. Rename popup.ts to popup.tsx and update it with a basic React component:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from 'react';
import { createRoot } from 'react-dom/client';
import '../styles/tailwind.css';

const Popup = () =&amp;gt; {
  const handleClick = () =&amp;gt; {
    alert('Button clicked!');
  };

  return (
    &amp;lt;div className="flex flex-col items-center bg-gray-100 p-4"&amp;gt;
      &amp;lt;h1 className="text-xl font-bold mb-4"&amp;gt;Hello, React Chrome Extension!&amp;lt;/h1&amp;gt;
      &amp;lt;button
        className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
        onClick={handleClick}
      &amp;gt;
        Click me
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};


const container = document.getElementById('root')
const root = createRoot(container as HTMLDivElement)
root.render(&amp;lt;Popup /&amp;gt;);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This simple React component mirrors the functionality of the original popup.ts but uses React’s onClick event for the button.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9.4 — Update popup.html&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We don’t need to change much in popup.html except ensure it has the proper div to mount our React component. Here’s the updated popup.html:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang="en"&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset="UTF-8" /&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&amp;gt;
    &amp;lt;title&amp;gt;Popup&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body class="bg-gray-100 w-80"&amp;gt;
    &amp;lt;div id="root"&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;9.5 — Update tsconfig.json&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We need to add "jsx": "react" and "moduleResolution”: “node"to our tsconfig.json It should look like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "es2015"],
    "module": "esnext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "jsx": "react",
    "moduleResolution": "node"
  },
  "include": ["src/**/*"]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;9.6 — Rebuild with Webpack&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once the changes are made, run Webpack to rebuild the extension -&amp;gt; npx webpack --mode development&lt;/p&gt;

&lt;p&gt;Now, when you reload the extension in Chrome and open the popup, you should see a React-powered UI styled with Tailwind CSS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion — Why Not Use Create-React-App?
&lt;/h2&gt;

&lt;p&gt;CRA makes a lot of assumptions about how you’re building your app, bundling everything into a single page with dynamically injected JS. This approach works great for a React web app but doesn’t mesh well with Chrome extensions, which use separate HTML pages for the popup, background scripts, and sometimes even options pages.&lt;/p&gt;

&lt;p&gt;The best parts of building without CRA? &lt;strong&gt;Control and learning.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You decide what gets included, how the bundling works, and how lean or rich your extension becomes.&lt;/p&gt;

&lt;p&gt;I also believe it is a good idea to try and see how difficult something is without the library, framework, or bootstrapping helpers. It helps learn a bit more about all of the technologies that are being used indirectly and hidden from you.&lt;/p&gt;

&lt;p&gt;If you have gotten this far, I thank you and I hope it was useful to you! Here is a cool image of a cat as a thank you!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F12032%2F0%2An31sWETt6ZrOng5Z" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F12032%2F0%2An31sWETt6ZrOng5Z" alt="Photo by [Humberto Arellano](https://unsplash.com/@bto16180?utm_source=medium&amp;amp;utm_medium=referral) on [Unsplash](https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral)" width="760" height="505"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>react</category>
      <category>programming</category>
    </item>
    <item>
      <title>Hiring for remote work in 2022</title>
      <dc:creator>Kristian Ivanov</dc:creator>
      <pubDate>Sat, 17 Sep 2022 10:36:17 +0000</pubDate>
      <link>https://dev.to/k_ivanow/hiring-for-remote-work-in-2022-37b3</link>
      <guid>https://dev.to/k_ivanow/hiring-for-remote-work-in-2022-37b3</guid>
      <description>&lt;p&gt;I've written twice about remote work so far, but only from the perspective of an engineer looking for work.&lt;/p&gt;

&lt;p&gt;Last year me and old co-worker of mine decided to start something on our own. It started as something to be done on the side. However, after I cashed out from a previous start up that I was an employee of, it became a full time job, and earlier this year we started doing our first hires for the engineering department to speed up the development process. I'll try to cover what platforms we used, which were better and some notes so people could eventually skip some of the wasted time we had.&lt;/p&gt;

&lt;p&gt;I'll break down the different options in several categories to make it easier.&lt;br&gt;
&lt;em&gt;Edit&lt;/em&gt;: Even though I didn't intend it at the start. The options are ordered from worse to best (in my humble experience of course).&lt;/p&gt;

&lt;h2&gt;
  
  
  Remote working platforms
&lt;/h2&gt;

&lt;p&gt;This would be your average platform for posting jobs and looking for jobs. I have been a fan of &lt;a href="https://weworkremotely.com" rel="noopener noreferrer"&gt;We Work Remotely&lt;/a&gt; when I had to work for a job before, but the list of them is pretty long. Some of the ones that spring to mind are&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://angel.co/jobs" rel="noopener noreferrer"&gt;Angel List&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.remotetechjobs.com/" rel="noopener noreferrer"&gt;Remote Tech Jobs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://otta.com" rel="noopener noreferrer"&gt;Otta&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nodesk.co/remote-jobs" rel="noopener noreferrer"&gt;No Desk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.dice.com" rel="noopener noreferrer"&gt;Dice&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://remoteworkhub.com/" rel="noopener noreferrer"&gt;Remote Work Hub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="//javascriptjob.xyz/remote/jobs"&gt;javascriptjob.xyz/remote/jobs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="//remoteok.com/"&gt;remoteok.com/&lt;/a&gt;
You can check a detailed breakdown from a candidate perspective here &lt;a href="https://dev.to/k_ivanow/finding-remote-work-in-2022-26j"&gt;Finding remote work in 2022&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High traffic and visibility, especially if you pay for a promotion&lt;/li&gt;
&lt;li&gt;The post would usually be up for an entire month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cost! As I've mentioned, I am a fan of we work remotely when looking for a job. From a recruiter's perspective, things are a little bit different though. Most (all that I've tried) platforms will require you to pay for your posting. This is normal, as the website needs to be profitable to exist. However, based on your funding and recruitment needs, $448 per month for a job posting and an upgrade to show it among all of the postings that have a promotional listing, which are more of them, can be pretty steep. Especially when you post on multiple platforms at the same time. It is true that an early stage start up has plenty of expenses (especially the legal ones), but 4 similar platforms would still sum up to $1500-$2000 per month.&lt;/li&gt;
&lt;li&gt;No guarantee. You have no guarantee if and how many candidates will apply to your posting.&lt;/li&gt;
&lt;li&gt;Most of them require a company page and details. If you are still in stealth, this can be annoying.&lt;/li&gt;
&lt;li&gt;Angel List has a particular low quality of applicants, despite it's fame as a start up place.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  LinkedIn
&lt;/h2&gt;

&lt;p&gt;With the pandemic over the last few years, the world migrated more and more towards remote work and so did LinkedIn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visibility. More and more people are using LinkedIn to find a job, especially after they added the easy-apply option.&lt;/li&gt;
&lt;li&gt;Cost! It is cheap and the billing is based on the number of days, the posting is up. The moment you cancel it, you stop being charged, unlike the platforms, which are charging per month.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It requires a company page. If you are still in stealth, this can be annoying. You can circumvent this by using something like &lt;a href="https://www.linkedin.com/company/stealth-startup-51" rel="noopener noreferrer"&gt;Stealth Startup&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;It still requires location for the employees! This is my biggest gripe with LinkedIn, as it limits the remote working, even though it shouldn't.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Recruiters
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;They will be searching proactively for people, and should (theoretically at least) find more people faster than a simple job post.
+/- Cost (this can be a con as well). Most of them won't charge you unless they find a candidate. If they do, however, their cost will be calculated based on the monthly salary of the candidate. In the case of an early stage start up, this can be beneficial, as most of the early employees prefer having more shares instead of a bigger salary, which in turn reduces the payment to the recruiter.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  HackerNews
&lt;/h2&gt;

&lt;p&gt;This one is a bit more complicated as it has a few different parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://news.ycombinator.com/jobs" rel="noopener noreferrer"&gt;https://news.ycombinator.com/jobs&lt;/a&gt; / &lt;a href="https://www.ycombinator.com/jobs" rel="noopener noreferrer"&gt;https://www.ycombinator.com/jobs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://news.ycombinator.com/item?id=32677265" rel="noopener noreferrer"&gt;Ask HN: Who is hiring? (September 2022)&lt;/a&gt; - new thread each month&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://news.ycombinator.com/item?id=32677261" rel="noopener noreferrer"&gt;Ask HN: Who wants to be hired? (September 2022)&lt;/a&gt; - new thread each month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Searchers build on top of the information from those threads&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kennytilton.github.io/whoishiring/" rel="noopener noreferrer"&gt;https://kennytilton.github.io/whoishiring/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hnjobs.emilburzo.com/" rel="noopener noreferrer"&gt;https://hnjobs.emilburzo.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can have several different approaches with this one.&lt;/p&gt;

&lt;p&gt;Passive - you can post on it for each monthly thread and check/wait for responses.&lt;/p&gt;

&lt;p&gt;Proactive - you can check the listings of who is looking for a job. Then research them, see if they are matching your criterias and then reach out to them. It shows a lot more personal approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Friends/Old co-workers
&lt;/h2&gt;

&lt;p&gt;This should be your first stop. If you have any people you've worked with before or any friends that you think would be a good fit, try and talk to them. Still, make them follow as much of the recruitment process you have set in place as possible. You can skip the first couple of stages, but always have them talk with your co-founders as well to make sure everyone is on board. Make it clear that this is a working relationship and is completely separate from your friendship in the case they are a friend.&lt;/p&gt;

&lt;h2&gt;
  
  
  General recruitment notes:
&lt;/h2&gt;

&lt;p&gt;Try to be as accommodating as possible for the candidate in terms of time of the meeting and duration. Don't make them bend backwards by asking them to stay until 3 AM so they can meet you up after you've had morning coffee! Try to find a time that works for both you, and them.&lt;/p&gt;

&lt;p&gt;Don't ask them why they are excited to work for your company! In my experience (sorry for being cynical) people are looking for a job based on this criteria (in order of importance):&lt;br&gt;
1 - Salary&lt;br&gt;
2 - Title/company structure - is it a step up, or how easy it would be to step up based on the company structure&lt;br&gt;
3 - Technologies (for engineers) - is the company using something they find interesting and have or want to have an experience with.&lt;br&gt;
4 - Direct managers - How easy would be for them to work with you. This brings things back to the be as accommodating as much as possible point and leaving a good impression. And old HR friend of mine had the saying that "People don't leave companies, they leave their bosses".&lt;br&gt;
5 - What is the company product or what the company does. So don't expect them to focus on this one as much as you want.&lt;/p&gt;

&lt;p&gt;Don't go overboard with the technical take at home test. People most often than not already have a job that they want to change. Try to keep it as simple as possible and within 2-3 hours. &lt;br&gt;
I got into the habit of specifying only the tech stack and some basic complexity requirements and letting the candidate run wild with it. It showed much more of what they are interested in and comfortable with than otherwise and was a better source for a discussion during out next in person interview.&lt;/p&gt;

&lt;p&gt;Don't go overboard with the cover letter (same reasoning as the technical take at home test), but do ask them to write something. Focus on a few key points and don't provide too rigid of a structure. I believe this also shows much more about them and it would be a lot more interesting read than the usual stock cover letter. &lt;br&gt;
I am mentioning it for another reason - the remote work leads to a lot of written communication. Be it slack, emails, specifications, jira tickets or anything else, they should be able to read and write well. The same goes for the recruiter too, by the way!&lt;br&gt;
It is worth mentioning I've started working at a company before and liked another company before that, simply because of the cover letter requirements and that I honestly had fun writing it and telling a story within it.&lt;/p&gt;

&lt;p&gt;Be quick to reply to their emails. Nobody wants to wait for 6 months to hear back from a company. I expect the same from candidates, as well.&lt;/p&gt;

&lt;p&gt;Don't go overboard with the recruitment process by having a million steps. The bigger the company, the more complex this tends to become, but still, do your best to keep it simple and easy moving. Also, do your best to let the applicant know what the next step is and what would be expected of them during the next meeting, to put them at ease.&lt;/p&gt;

&lt;p&gt;Be transparent - in your requirements, expectations, when rejecting them, or when offering them the position at the end.&lt;/p&gt;

&lt;p&gt;P.S. Everything written above is from my own limited perspective after recruiting and interviewing for a couple of start ups and now doing it for my own. So far I've had to handle 2000+ candidates for several positions. It is not a lot, so take everything I've said with a grain of salt. I would love to discuss what others have come across while searching for new colleagues in the comments below or on &lt;a href="https://twitter.com/k_ivanow" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Last disclaimer - The image at the top of the article is from &lt;a href="https://unsplash.com/@eprouzet" rel="noopener noreferrer"&gt;Eric Prouzet&lt;/a&gt; at &lt;a href="https://unsplash.com" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>remote</category>
      <category>career</category>
      <category>startup</category>
      <category>management</category>
    </item>
    <item>
      <title>Finding remote work in 2022</title>
      <dc:creator>Kristian Ivanov</dc:creator>
      <pubDate>Sun, 11 Sep 2022 15:07:11 +0000</pubDate>
      <link>https://dev.to/k_ivanow/finding-remote-work-in-2022-26j</link>
      <guid>https://dev.to/k_ivanow/finding-remote-work-in-2022-26j</guid>
      <description>&lt;p&gt;A few years back I wrote &lt;a href="https://dev.to/k_ivanow/finding-remote-work-in-2019-6ce"&gt;how to find remote work in 2019&lt;/a&gt; after a personal research of having to find a remote work for the first time not by being approached by a headhunter, but on my own. That article did better than a lot of other things I've written. However, recently I had to hire people about of a very very early stage startup (the very first hires other than the co-founders), and noticed the list and feedback I had on my original article needed some updates, especially with the business world embracing the remote work more and more after the pandemic.&lt;/p&gt;

&lt;p&gt;So here they are - in this article I'll cover the most popular platforms I've noticed as being used and their pros and cons and in another one I'll cover the perspective of someone looking to hire and the pros and cons of the platforms then, as they more often than not are quite different.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://weworkremotely.com" rel="noopener noreferrer"&gt;We Work Remotely&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;Still my favorite several years later! I've found most of my job opportunities through WWR.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;br&gt;
+All companies I have contacted for job postings on this website were fast to +respond. &lt;br&gt;
+All had a quickly moving hiring process. &lt;br&gt;
+It has several types of jobs - marketing, programming, etc. &lt;br&gt;
+It also has the benefit of showing where candidates must be located for the job. &lt;br&gt;
+It has kept growing over the years. It now has 28,530 postings&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;br&gt;
-It doesn't allow for an easy apply like other entries on the list.&lt;br&gt;
-The increased number of companies leads to a bit of a dilution of how often companies reply and how quick their process is.&lt;br&gt;
-There is no streamlined way of applying for a job. The button for applying can lead to anywhere - outside platforms or even just email addresses without any guidance on what information is of interest to the company.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://www.remotetechjobs.com/" rel="noopener noreferrer"&gt;Remote Tech Jobs&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;Built as a replacement of Stack Overflow jobs, based on their description.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;br&gt;
+It has much better filtering capabilities than other options, to find what you are looking for&lt;br&gt;
+All jobs get taken down after 30 days at most, ensuring fresh listings.&lt;br&gt;
+Has a lot of open opportunities. Almost 5 000 at the time of writing, which when the previous point is considered is a lot of activity from companies looking for candidates.&lt;br&gt;
+Has an easy-apply option.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;br&gt;
-They also aggregate jobs from 11 other platforms, aiming to be the go to place for finding remote work. However, when tested with several postings from We Work Remotely, they didn't appear in the results of Remote Tech Jobs.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://www.linkedin.com/feed/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;br&gt;
+The most obvious candidate for everyone. &lt;br&gt;
+The number of companies looking to hire through LinkedIn has increased over the last several years.&lt;br&gt;
+Has an easy-apply for a lot of the open positions&lt;br&gt;
+Can filter by location as well (in case you need/want to go to an office as well or are looking for a simpler legal process (when both you and your employer are in the same country), or if you are just considering moving there)&lt;br&gt;
+Can easily research the company's profile right there in LinkedIn, including for active or past connections to get some inside information&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;br&gt;
-It still has mostly permanent positions&lt;br&gt;
-A lot of early-stage startups don't post on LinkedIn while they are still in stealth mode, so if you are interested to be one of the first hires at a company, other entries such as AngelList or HackerNews would be better options&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://angel.co" rel="noopener noreferrer"&gt;AngelList&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;br&gt;
+Has a lot of active listings&lt;br&gt;
+Companies are required to list their salary range for the posting, so there is less of a chance of surprises&lt;br&gt;
+Companies also list expected bonuses in terms of stock options&lt;br&gt;
+Has a streamlined application process&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;br&gt;
-There is a lot of competition especially because of the easy application process so you'll need to stand out&lt;br&gt;
-Most of the companies on AngelList are early stage start ups. This can be viewed as a benefit for some people as well&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://otta.com" rel="noopener noreferrer"&gt;Otta&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;br&gt;
+Each company posting jobs at Otta is screened by them so the quality feels a lot better than some other platforms&lt;br&gt;
+Extremely good filtering&lt;br&gt;
+Streamlined application process&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;br&gt;
-Lower number of overall job postings&lt;/p&gt;

&lt;h4&gt;
  
  
  HackerNews
&lt;/h4&gt;

&lt;p&gt;This one is a bit more complicated as it has a few different parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://news.ycombinator.com/jobs" rel="noopener noreferrer"&gt;https://news.ycombinator.com/jobs&lt;/a&gt; / &lt;a href="https://www.ycombinator.com/jobs" rel="noopener noreferrer"&gt;https://www.ycombinator.com/jobs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://news.ycombinator.com/item?id=32677265" rel="noopener noreferrer"&gt;Ask HN: Who is hiring? (September 2022)&lt;/a&gt; - new thread each month&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://news.ycombinator.com/item?id=32677261" rel="noopener noreferrer"&gt;Ask HN: Who wants to be hired? (September 2022)&lt;/a&gt; - new thread each month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Searchers build on top of the information from those threads&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kennytilton.github.io/whoishiring/" rel="noopener noreferrer"&gt;https://kennytilton.github.io/whoishiring/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hnjobs.emilburzo.com/" rel="noopener noreferrer"&gt;https://hnjobs.emilburzo.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;br&gt;
+More personal approach for each position&lt;br&gt;
+You'll most likely get interviewed by the actual people you'll work with and not an HR, recruiter, or a headhunter&lt;br&gt;
+A lot of open positions and overall active community&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;br&gt;
-No easy filtering like other options on the list&lt;br&gt;
-Some listings might have expired and not been closed&lt;br&gt;
-No easy way to find still opened listings (from the end of last month for instance)&lt;br&gt;
-If you are posting your own profile to get contacted, you'll need to do it every month (if you don't find a job right away)&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Other noteworthy mentions that have declined over the years&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://nodesk.co/remote-jobs" rel="noopener noreferrer"&gt;No Desk&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.dice.com" rel="noopener noreferrer"&gt;Dice&lt;/a&gt;&lt;br&gt;
&lt;a href="https://remoteworkhub.com/" rel="noopener noreferrer"&gt;Remote Work Hub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pilot.co" rel="noopener noreferrer"&gt;Pilot&lt;/a&gt;&lt;br&gt;
&lt;a href="http://Upstack.co" rel="noopener noreferrer"&gt;Upstack&lt;/a&gt;&lt;br&gt;
&lt;a href="http://gun.io" rel="noopener noreferrer"&gt;Gun.io&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Notes:&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Always read the job posting in its entirety! Some companies would ask questions in their listing that they'd like to get an answer to when applying. A sort of a replacement for the usual stock cover letters.&lt;br&gt;
Prepare yourselves for a lot of companies not closing their job postings after finding a candidate. Especially on HackerNews.&lt;/p&gt;

&lt;p&gt;P.S. Everything written above is from my own limited perspective when I have applied for different jobs or noticed when deciding on which platform to post a job to get the maximal result. It may vary for different disciplines and/or cases. I would love to discuss what others have come across while searching for job opportunities in the comments below or on &lt;a href="https://twitter.com/k_ivanow" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Last disclaimer - The image at the top of the article is from &lt;a href="https://unsplash.com/@kintecus" rel="noopener noreferrer"&gt;Ostap Senyuk&lt;/a&gt; at &lt;a href="https://unsplash.com" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>remote</category>
      <category>interview</category>
      <category>career</category>
      <category>jobs</category>
    </item>
    <item>
      <title>The interview is a two way process, what to look for and what to watch out as an interviewer or a candidate</title>
      <dc:creator>Kristian Ivanov</dc:creator>
      <pubDate>Thu, 09 Jul 2020 13:25:29 +0000</pubDate>
      <link>https://dev.to/k_ivanow/the-interview-is-a-two-way-process-what-to-look-for-and-what-to-watch-out-as-an-interviewer-or-a-candidate-5c2p</link>
      <guid>https://dev.to/k_ivanow/the-interview-is-a-two-way-process-what-to-look-for-and-what-to-watch-out-as-an-interviewer-or-a-candidate-5c2p</guid>
      <description>&lt;h1&gt;
  
  
  Intro
&lt;/h1&gt;

&lt;p&gt;For about an year I've interviewed a lot (and I do mean A LOT of candidates at &lt;a href="https://www.chilipiper.com/" rel="noopener noreferrer"&gt;Chili Piper&lt;/a&gt;, before that I've did interviews for my previous employers and I had a period of experimenting with different platforms to find remote work (which led to plenty of interviews). You can read more about it here &lt;a href="https://dev.to/k_ivanow/finding-remote-work-in-2019-6ce"&gt;Finding remote work in 2019&lt;/a&gt;. I've also talked a lot with friends, co-workers, etc. on job hunting, recruitment and so on. What is listed below is what I've noticed is a good or bad indicator for both candidates and interviewers.&lt;/p&gt;

&lt;h1&gt;
  
  
  Pre-interview
&lt;/h1&gt;

&lt;h3&gt;
  
  
  The job posting
&lt;/h3&gt;

&lt;p&gt;The way a job posting is written tells you about the company. This can take several posts on its own, but in general if the listing is using every popular framework and language, it was most likely not written by an engineer, but by an HR, so expect that initially you'll have to deal with HR, before actually talking with your future co-workers. If it was written by an engineer, it is highly likely that the company will have issues in the future. For instance I recently had an offer with the following tech stack - FE with experience with WebGL graphic libraries such as Three.js, Babylon.js, Paraview, etc. DevOps stack including Jenkins, JFrog, GitLab, Docker, Kubernetes, Ansible. Understanding of 3D Math, ReactJS/React Native, Angular, NodeJS with Express, Flask, Django, Rails, Spring or PHP. Any SQL or NoSQL (list them here). Languages as JS, Python, .Net core or Java. Since this wasn't written by a recruitment agency or an HR, it makes me wonder how many projects a single dev has to work on, how are they being maintained, how is the tech stack being chosen, how you find new devs, how you onboard them, etc.&lt;br&gt;
After a while you'll get to decipher the usual lingo of competitive salary, growing environment and so on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Requiring you to write a cover letter
&lt;/h3&gt;

&lt;p&gt;If they (the company) requires for it to not be a stock letter it is a huge &lt;strong&gt;Pro&lt;/strong&gt;. The template cover letters are boring to both write and read.&lt;br&gt;
From a company perspective, this automatically filters most of the candidates that are just applying on every position they see. The cover letter requires the candidate to read and follow your instructions. It is also a great way to evaluate the candidate's writing skills. After all a lot of the communication in a company is in a written form. This is especially true for remote companies.&lt;br&gt;
From a candidate's perspective, this is a great chance to hone your writing skills and dazzle them by telling them a short story about yourself. Cover letters typically come with a set of guidelines or question telling you what to focus one. This can be used as a great indicator on what the company values are and whether or not you really want to work there. Additionally this will give you something to talk about on the interview.&lt;/p&gt;

&lt;h3&gt;
  
  
  Speed
&lt;/h3&gt;

&lt;p&gt;Speed of replying, setting up meetings, how far the meetings are in the future, how fast they reply if you follow up with questions, etc. As everything else, this is valid for both the employer and the candidate.&lt;br&gt;
As a candidate I typically avoid companies that are taking a couple of weeks to get back to you and are setting up your first interview or even the follow ups several weeks in the future. If they do that you can typically expect a higher amount of bureaucracy involved not just in the hiring process.&lt;br&gt;
If a candidate takes so long to respond and set up an interview, they are most likely not that interested in working for/with you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing (Pre-Interview)
&lt;/h3&gt;

&lt;p&gt;If you are using &lt;a href="https://coderbyte.com/" rel="noopener noreferrer"&gt;coderbyte&lt;/a&gt;, &lt;a href="https://toggl.com/hire/pricing" rel="noopener noreferrer"&gt;toggle hire&lt;/a&gt;, &lt;a href="https://www.hackerrank.com/" rel="noopener noreferrer"&gt;hacker rank&lt;/a&gt;, etc., try to tailor the coding exercises and questions to your actual needs. For instance, asking what the "S" in the SOLID principles stands for or is 0.1 + 0.2 equals 0.3 in JS isn't really an accurate representation on the candidate's knowledge. The coding questions are finite and after a while people start getting use to them.&lt;br&gt;
Alternatively you could set up a barebone repo and give people a task to complete something with it and then run automated tests against it (typical for &lt;a href="https://www.clevertech.biz/" rel="noopener noreferrer"&gt;clevertech&lt;/a&gt;) This was you can learn a bit more about the candidate's skills. &lt;br&gt;
It all depends on how much you want to filter developers before talking to them. You'd be surprised how useful a simple writing exercise to write a function that finds if two strings are anagram can show you about a person. People almost always try to overthink and over complicate it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Interview
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Questions
&lt;/h3&gt;

&lt;p&gt;In general open ended questions are better. They require a bit more thought before answering and are closer representation to actual human communication.&lt;br&gt;
Examples for interviewers - tell me a bit about yourself (it is interesting what the candidate will focus on), what would you do in situation X, how would you react to Y, What do you thing about Z, how much supervision do they expect, etc.&lt;br&gt;
Examples for candidate - what is a typical day at Company X looks like, what is your approach on Y, how do you handle Z, how are you financed, client revenue or investors fund, how many people have you highered (to see if they are overreaching, because they have received financing or they actually need people), how many people are working on something, how hands on are they, ask them about their technology stack (even though it is already listed) follow up on why they picked the technologies they did, how did that work out for them, etc.&lt;br&gt;
Asking question as a candidate means that you care about where you are working and is not just about the paycheck. &lt;br&gt;
The questions you ask tell the other as much about you, as you'll learn by the answers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do some research
&lt;/h3&gt;

&lt;p&gt;As an interviewer you can check the candidate's previous companies and especially their side projects and ask them about it. People love being asked about their passion projects.&lt;br&gt;
As a candidate, try to learn as much as possible about the company before the interview. You'd be surprised how much a well timed question about the billing system they are using or something else that you noticed on their product will make you stand out of the crowd. If you are applying for a startup, you even have pretty good chances to ask the person that had actually develop the thing you are curious about.&lt;br&gt;
In both cases it shows that you did some preparation before hand and makes (or it seems to make) you more caring and interested.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bad phrases
&lt;/h3&gt;

&lt;p&gt;As a candidate be wary of interviewers (especially on a higher level) that use "I" too much. For example - I pay well, I pay on time, I've highered X amount of people, I've interviewed. It speaks volumes for that person ego. Some adjustments for non-native English speakers are needed of course, but you can understand if it is a mistake or intentional based on the tone of voice, speaking patterns and general context.&lt;br&gt;
As a candidate you can do that as well. Some applicants love to use "I". Usually, no matter how much of a superstar someone is, they didn't work completely alone. Using a dash of modesty when giving the overview of what you've done, by using "WE" or something similar and then filling up your specific role by another means, either in the follow up questions or something similar can do wonders.&lt;br&gt;
In general "WE" as a company, or a team, etc. are great or terrible. Individualism, especially in the leadership positions isn't good.&lt;/p&gt;

&lt;h3&gt;
  
  
  Live Coding (optional)
&lt;/h3&gt;

&lt;p&gt;Not every company has it.&lt;br&gt;
If you have it, as a company, try to use tasks that have more than one right solution and that are not usually used in online tests, articles, etc. This will help you reduce the chance that the person would have already done something similar. After a while (20-50-100 interviews) you'll start associating the chosen direction of the solution with other personality aspects. Additionally, try to keep it short and sweet. If you plan to interview someone and set up 1-1.5 hours of live coding and this is after work hours, that person can perform a bit worse on the questions after, simply because they are exhausted.&lt;br&gt;
If you do it, as a candidate, try to explain what you are doing it to some extent. It will speak to your thought process. Especially if you hit a snag at some point of doing it. Don't worry, live coding reduces everybody's skills. Even if they are not being interviews, but are just working with a colleague.&lt;/p&gt;

&lt;h1&gt;
  
  
  Subsequent interviews
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Take home coding task (optional)
&lt;/h3&gt;

&lt;p&gt;Not every company has it.&lt;br&gt;
If you have it as a company try to incorporate some open ended questions in it. If you want to be sneaky you could always try to make it a bit vague and see how many people will ask you questions and try to clarify it and/or how they'll interpret it. After all, this will happen sooner or later during the time they work with you. Either way, try to keep it short and sweet here as well. Chances are your candidates are working somewhere at the time and everything longer than one or two hours would make it difficult for them to find time for.&lt;br&gt;
As a candidate - try to do it in a repository, even if it is not required, try to make some github issues for it (if you are using github), &lt;strong&gt;do&lt;/strong&gt; make multiple commits (it will help show your work process) and definitely do it as soon as possible after the interview that "assigned" it to you. This way your next interviewer will have plenty of time to go through it and you'll have a linchpin for the next interview.&lt;/p&gt;

&lt;h3&gt;
  
  
  Avoid repetition
&lt;/h3&gt;

&lt;p&gt;As a company - try to streamline your interview process. Let the people that interview candidates sync between them what each of them will tell and ask them. When you get the same story told two or three times by an engineer, CTO and the CEO  or get asked the same few questions by all of them, it makes you question the planning abilities of those people. It is super a super easy pitfall to avoid, by sending an email with your feedback to everyone else in the process for that candidate with your feedback and then just replying to it. This way you have all of your information in the same place. As a candidate - try not to say the same thing that is on your CV. They have already read it. If you do talk about the same things, tell them in a different light. Depending on the questions they have asked you, try to talk about the things that the company will need. They have problems with X and Y? Great! Talk about how you solved something similar before, and of course, try not to make it too obvious what you are doing.&lt;/p&gt;

&lt;h3&gt;
  
  
  The payment expectation question
&lt;/h3&gt;

&lt;p&gt;I've been working remotely for ~3 years now. I hate that question! Most of the time I am based in Sofia, Bulgaria. You'd be surprised how much the IT sector is driving the average salary for the country upwards. I get that companies are trying to save as much as possible and are trying to pay the salary of a region + X, but often times, devs don't know the average salary for their own region. This calculation becomes even messier, when you take into account that when you are working remotely, usually you'll have to pay your own taxes, social security, etc.&lt;br&gt;
My typical dodge of this is to find out if they have other people at the same position. Be it FE, BE, leads, product owners, doesn't really matter. Then explain that it depends on the workload, the company, etc, and you'd prefer for the company to make you an offer. Many companies would be OK with that. If they are not, you could ask them to just average out the salary for your position and make you an offer. Most of the companies would be OK with that. If they are not, don't give them your direct expectation, but a ballpark. Otherwise if you ask too much, they can just stop the interview process and if you ask too little, you may be unhappy.&lt;br&gt;
Most decent companies will raise your salary to the average level if you ask for too little anyway, so there isn't anything to be afraid of. &lt;br&gt;
Keep in mind that this will usually take some back and forth for all sides to agree. Even if it is several sentences in the span of 5-10 minutes. Don't be afraid to discuss it and to hear "no". Things are rarely given when you don't ask for them.&lt;/p&gt;

&lt;p&gt;These are just my two cents on the topic. Take them with a grain of salt. I would love to hear what you have come across and what good or bad sings you have noticed in your career.&lt;/p&gt;

</description>
      <category>hiring</category>
      <category>career</category>
      <category>findremotework</category>
      <category>work</category>
    </item>
    <item>
      <title>Making your life easier with a Makefile</title>
      <dc:creator>Kristian Ivanov</dc:creator>
      <pubDate>Mon, 13 Apr 2020 14:07:21 +0000</pubDate>
      <link>https://dev.to/k_ivanow/making-your-life-easier-with-a-makefile-dah</link>
      <guid>https://dev.to/k_ivanow/making-your-life-easier-with-a-makefile-dah</guid>
      <description>&lt;h1&gt;
  
  
  Intro
&lt;/h1&gt;

&lt;p&gt;Recently I have been spending more time on one of my side projects. This lead to completely refactoring it and expanding the initial simplistic vanilla JS Chrome Extension to Chrome Extension bootstrapped with CRA, android hybrid version and (since last weekend) a web version.&lt;br&gt;
All of this means playing around with yarn watch | build, cordova run | build, different methods of generating build files - .apk for the Android playstore and .zip for the Chrome webstore, different imports and start ups based on the environment and so on. Updating the app version across multiple files in various folders.&lt;br&gt;
My immediate dev response to all of that was to try and bootstrap all of this as much as possible in any way. First of all this would make it less prone to errors and more consistent, and secondly I am too lazy to be bothered to execute several commands over and over for every build and try to remember all of them.&lt;br&gt;
My education had touched makefiles only in the context of C++ and when talking with friends of mine, it turned out that they had similar experience. Here is a super short description of what a Makefile is, what you can put inside it and how you can use makefiles to make your life easier by bundling sections of logic together to avoid repetition.&lt;/p&gt;
&lt;h1&gt;
  
  
  Enter Makefile
&lt;/h1&gt;

&lt;p&gt;From Wikipedia:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A makefile is a file (by default named "Makefile") containing a set of directives used by a make build automation tool to generate a target/goal.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The simplest way to look at it is - If you can type/do something from your terminal, you can put it into a makefile. If something is too complicated, you can put it into a script file and execute it from the makefile.&lt;/p&gt;

&lt;p&gt;Another way to look at it is - a way to keep a set of shell aliases in your repository so you can re-use them across multiple machines and/or users.&lt;/p&gt;
&lt;h1&gt;
  
  
  Basic anatomy of a Makefile
&lt;/h1&gt;
&lt;h3&gt;
  
  
  Commands:
&lt;/h3&gt;

&lt;p&gt;Commands are defined by a string, followed by a colon. What the command would do are the next lines. They are typically prefixed with a tab. You will see examples of commands bellow.&lt;/p&gt;

&lt;p&gt;You can define a default command to be executed. It will be run if you execute "make" without any params:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.DEFAULT_GOAL: help
default: help
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most common definition of the help command inside a make file I've seen and used is the following&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;help: ## Output available commands
    @echo "Available commands:"
    @echo
    @fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sed -e 's/##//'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will read all commands listed in the make file and display them in the terminal. It is useful if you have forgotten what the appropriate syntax is to quickly check it, or if you are just getting familiar with a makefile somebody else had added to list all options without reading the makefile itself. For instance, for me it outputs the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;help:  Output available commands
extension-clean: remove old build of the extension
extension-build:  build the chrome extension
android-clean: remove old build of the android app
android-build:  build the android app
bump-version:  update the version for the chrome extension and the android app
make android-debug: debug android
make extension-debug: debug chrome extension
make web-clean: remove old build for the web
make web-debug: debug web version
make web-build: build web version
make build: build all platforms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can bundle commands together. You can notice that I have 4 separate build directives listed above. One for each platform and one to build everything. Here is what the one to build everything looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;make build: ##build all platforms
    make extension-build
    make android-build
    make web-build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will call each of the build commands one by one and do whatever is described inside them, bootstrapping the logic even further.&lt;/p&gt;

&lt;h3&gt;
  
  
  Variables
&lt;/h3&gt;

&lt;p&gt;You can add variables into your makefile. The most common use case from my perspective is to avoid typing long path names over and over. For instance, I have this in mine&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;unsigned-app-path = platforms/android/app/build/outputs/apk/release/app-release-unsigned.apk
android-app-folder = android-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Makefiles can also get variables from the shell when executing them. If you run a makefile directive with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;make bump-version version=1.1.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will be able to use ${version} as a variable in the bump-version command in your make file. More info on that in the next section.&lt;/p&gt;

&lt;h1&gt;
  
  
  Combining Makefile with Nodejs
&lt;/h1&gt;

&lt;p&gt;I mentioned in the intro that updating the version to a newer one means editing several files and trying to keep it consistent between all of them. This is inherently error prone, "messy" and requires you to either remember where the files are or their names. Alternatively you can also bootstrap it using something in the lines of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;android-app/public/manifest.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;versionLine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`"version": "`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;postLineSeparator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;android-app/config.xml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;versionLine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`version="`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;android-app/package.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;versionLine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`"version": "`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;postLineSeparator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;    
        &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;separator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;separator&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;versionLine&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;versionLine&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]}${&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;versionLine&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postLineSeparator&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postLineSeparator&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;
            &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;separator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`updated &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; version`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;  
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although it is a bit hackish it would take of updating the version across the three listed files. But what if you want to go even further? You can combine it with a Makefile directive into something like that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node versionBump.js ${version}
    git add $(android-app-folder)/config.xml
    git add $(android-app-folder)/package.json
    git add $(android-app-folder)/public/manifest.json
    git commit -m "version bump to ${version}"
    git push -u origin master
    git tag v${version}
    git push origin v${version}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above takes 1 additional argument which is the version code and passes it into the short Nodejs script we saw earlier. Then it commits the three files containing the version and uses the version we passed when executing it for the comment as well. It also generates a git tag with the version code. This takes care of several annoying little steps of preparing for a release by executing only 1 command in the shell.&lt;/p&gt;

&lt;p&gt;Combining a Makefile + any scripting language means that the options to automate/bundle a sequence of things that you are doing frequently is limitless.&lt;/p&gt;

&lt;h1&gt;
  
  
  Outro
&lt;/h1&gt;

&lt;p&gt;What I want to leave you with is that we devs are lazy when it comes to do the same thing over and over and we try to find ways to not have to do it. If you think about it, software development is trying to make things easier for all kinds of people and professions by taking care of the repetitive tasks so people can have more time to focus on what really matters. The things listed in the article, although limited by my own use cases, can help you to do the same in your own indie or company projects.&lt;/p&gt;

</description>
      <category>make</category>
      <category>makefile</category>
      <category>bootstrap</category>
    </item>
  </channel>
</rss>
