<?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: Gaurav Nadkarni</title>
    <description>The latest articles on DEV Community by Gaurav Nadkarni (@nadkarnigaurav).</description>
    <link>https://dev.to/nadkarnigaurav</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%2F2561895%2Fe5ebd10e-c702-4581-a5d3-6219df22f5f6.jpg</url>
      <title>DEV Community: Gaurav Nadkarni</title>
      <link>https://dev.to/nadkarnigaurav</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nadkarnigaurav"/>
    <language>en</language>
    <item>
      <title>A TypeScript Journey: When Type Safety Feels Like a Safety Harness… and Sometimes a 10 kg Stone on Your Feet</title>
      <dc:creator>Gaurav Nadkarni</dc:creator>
      <pubDate>Fri, 26 Sep 2025 04:32:13 +0000</pubDate>
      <link>https://dev.to/nadkarnigaurav/a-typescript-journey-when-type-safety-feels-like-a-safety-harness-and-sometimes-a-10-kg-stone-on-35kn</link>
      <guid>https://dev.to/nadkarnigaurav/a-typescript-journey-when-type-safety-feels-like-a-safety-harness-and-sometimes-a-10-kg-stone-on-35kn</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When I first started writing code, I lived in the world of Java. It was a place where types were everywhere, like street signs you couldn’t ignore. If you tried to drive your program the wrong way, the compiler would pull you over before you even left the garage. It was safe, predictable, and sometimes a little suffocating.&lt;/p&gt;

&lt;p&gt;Then I met JavaScript. Suddenly, I was free. No one asked me to declare the type of a variable, no one complained if I changed my mind halfway through a function, and I could write something messy and watch it still run. It felt liberating until the first time I spent an hour debugging only to find that I had misspelled a property name.&lt;/p&gt;

&lt;p&gt;That’s when TypeScript walked in. It promised the best of both worlds: the freedom of JavaScript with just enough guardrails to stop me from repeatedly shooting myself in the foot. And to be fair, it often delivered. But along the way, I also learned that type safety can feel like wearing a sturdy safety harness… or like trying to run a marathon with a 10 kg stone tied to your feet.&lt;/p&gt;

&lt;p&gt;This article is a reflection on that journey: the wins, the frustrations, the times TypeScript saved me, and the times it got in the way. If you’ve ever wrestled with types, cursed at the compiler, or wondered if you should just stick to plain JavaScript, I think you’ll relate.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Background Coming from Java and Its Static Types
&lt;/h2&gt;

&lt;p&gt;I started out in Java, where everything had to be neatly defined before you could even think about running your program. You couldn’t just say, “Here’s a variable, I’ll decide what it is later.” No, you had to declare its type, its purpose, and practically its hopes and dreams before the compiler would even let you compile.&lt;/p&gt;

&lt;p&gt;At first, I found comfort in that rigidity. The compiler was like a strict but caring teacher. It wouldn’t let you turn in sloppy work. If I tried to pass a string where an integer was expected, I’d be scolded immediately, long before my code could cause trouble in production. There was a kind of safety in knowing the rules of the game were always enforced.&lt;/p&gt;

&lt;p&gt;But over time, I also realized that Java’s strong typing could feel like bureaucracy. Sometimes I just wanted to test a simple idea, but I had to fill out all the forms: classes, interfaces and boilerplate before I could even write “Hello, World.” Still, those years shaped how I think about code: predictable, well-structured, and with the comforting knowledge that the compiler had my back.&lt;/p&gt;

&lt;p&gt;So when I later wandered into the wild west of JavaScript, you can imagine the shock.&lt;/p&gt;

&lt;h2&gt;
  
  
  JavaScript and the Need for Typing
&lt;/h2&gt;

&lt;p&gt;Switching from Java to JavaScript felt like moving from a well-ordered office into a lively street market. In JavaScript, you could pick up anything, call it whatever you wanted, and no one would stop you. Want to treat a number like a string? Go ahead. Accidentally return an object when you promised an array? No problem… JavaScript will just smile and keep running. Compared to Java’s form-filling discipline, this was a revelation.&lt;/p&gt;

&lt;p&gt;The joy was real. I could prototype faster than ever before. I didn’t have to spend half an hour explaining to the compiler that, yes, this variable really was supposed to be a string. JavaScript trusted me, and in those early days, that trust felt empowering.&lt;/p&gt;

&lt;p&gt;My joy was unmeasurable but little did I know that freedom came with hidden costs. The first time I passed a function expecting an object a &lt;code&gt;null&lt;/code&gt; value, everything exploded at runtime. The famous “undefined is not a function” error became my constant companion, and debugging started to feel like detective work in a crime scene I accidentally created.&lt;/p&gt;

&lt;p&gt;The bigger the codebase got, the harder it became to manage. Without clear types, I’d forget what shape an object was supposed to have or what a function was supposed to return. Reading my own code after a few months felt like reading someone else’s messy notes, vague hints, no guarantees, and a lot of room for misinterpretation.&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%2Fetluirily3hvclj4gv9a.webp" 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%2Fetluirily3hvclj4gv9a.webp" alt=" " width="720" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The more I built, the more I realized that JavaScript’s charm could become chaos without some structure. And that’s when I started to think: maybe what I needed wasn’t a return to Java’s rigidity, but a middle ground, something that let me keep the joy of JavaScript while avoiding the worst of its pitfalls. That’s where TypeScript entered the picture.&lt;/p&gt;

&lt;h2&gt;
  
  
  When I Found Type Safety to Be a Potent Solution
&lt;/h2&gt;

&lt;p&gt;When I first started using TypeScript, it reminded me a little of Java but without all the extra ceremony. Suddenly, my editor was more than just a place to write code. It actually helped me. It showed me what arguments a function expected, what type of value I would get back, and it warned me when I tried to use something the wrong way.&lt;/p&gt;

&lt;p&gt;The best part was how it caught small mistakes early. If I misspelled a property name or passed a number where a string was expected, TypeScript stopped me right away. These were the kind of mistakes that would have taken me hours to notice in plain JavaScript.&lt;/p&gt;

&lt;p&gt;I also liked how types worked as documentation. Instead of guessing what an object should look like or digging through old code, I could see it right in front of me. That made it easier for me to come back to my own code later, and it made teamwork smoother too. Everyone could understand the shape of data or the purpose of a function without asking around.&lt;/p&gt;

&lt;p&gt;For the first time in JavaScript, I felt a little safer. TypeScript didn’t make me a better programmer overnight, but it did save me from a lot of silly bugs and misunderstandings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why TypeScript Felt Potent:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Caught mistakes early: It prevented bugs by warning about wrong types, misspelled properties, or incorrect function usage before running the code.&lt;/li&gt;
&lt;li&gt;Made code easier to understand: Types acted as self-documenting guides, showing exactly what objects and functions were supposed to look like.&lt;/li&gt;
&lt;li&gt;Improved coding efficiency: IDE features like autocomplete and IntelliSense worked better with types, helping you write code faster and with fewer errors.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Types Sometimes Felt Like Tying a 10 kg Stone to Your Feet and Running a Marathon
&lt;/h2&gt;

&lt;p&gt;As much as I appreciated what TypeScript brought to the table, it wasn’t all sunshine and smooth sailing. Sometimes, using types felt heavy, like I had an extra 10 kg strapped to my legs while trying to run.&lt;/p&gt;

&lt;p&gt;Simple things could suddenly take longer than expected. Want to quickly pass an object to a function? If the type definitions weren’t perfectly aligned, the compiler would complain. Trying to satisfy it could take more time than writing the actual logic. Generics, union types, and complex type intersections were powerful, sure but they could also make my head spin.&lt;/p&gt;

&lt;p&gt;There were moments when I found myself fighting the type system rather than using it. A function that worked perfectly fine in JavaScript suddenly threw red squiggly lines because the type checker didn’t like something subtle I did. I’d spend minutes or sometimes hours tweaking type definitions instead of focusing on the real problem.&lt;/p&gt;

&lt;p&gt;It became clear that while types catch many mistakes, they also come with a cost. There’s a balance to strike between type safety and developer speed. Too strict, and you feel weighed down; too loose, and you risk slipping on a bug.&lt;/p&gt;

&lt;p&gt;Even so, despite the occasional frustration, I kept coming back to TypeScript because for all the extra work, it still prevented more headaches than it created.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why TypeScript Can Feel Heavy:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Extra time for simple tasks: Even small changes can require updating type definitions, slowing down development.&lt;/li&gt;
&lt;li&gt;Complexity with advanced types: Generics, union types, and intersections can be powerful but sometimes confusing or overcomplicated.&lt;/li&gt;
&lt;li&gt;Fighting the compiler: At times, the type system seems to block working code, making you spend more time satisfying the compiler than solving the actual problem.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When Type Safety Didn’t Really Work
&lt;/h2&gt;

&lt;p&gt;Even with all the benefits TypeScript brought, there were times when it couldn’t save me. Type safety can prevent certain mistakes, but it doesn’t make your logic perfect. I’ve had functions that passed all type checks but still produced wrong results because the underlying algorithm was flawed. The compiler happily approved my mistakes, leaving me scratching my head at runtime.&lt;/p&gt;

&lt;p&gt;Another frustration came from third-party libraries. Not every library has perfect type definitions. Sometimes the types were missing, incomplete, or outright wrong. That meant I either had to write my own types, ignore errors, or risk subtle bugs sneaking through none of which were particularly fun.&lt;/p&gt;

&lt;p&gt;Dynamic data like API responses or user input can also bypass the safety net. You can type everything perfectly on paper, but the real world rarely behaves as expected. JSON structures can change, fields can be missing, and suddenly the carefully typed code crashes anyway.&lt;/p&gt;

&lt;p&gt;Type inference, while helpful, isn’t always smart enough. Sometimes TypeScript guesses a type that doesn’t match reality, leading to confusing errors that are tricky to debug. And optional or union types, while designed for flexibility, can add cognitive overhead checking for every possible case can be tedious.&lt;/p&gt;

&lt;p&gt;Finally, overconfidence is a hidden danger. Passing the compiler gives a false sense of security. I’ve found myself thinking, “If it compiles, it must be correct” only to realize later that runtime bugs were still lurking.&lt;/p&gt;

&lt;p&gt;In short, TypeScript is powerful, but it’s not a silver bullet. It helps a lot, but careful thinking, thorough testing, and realistic expectations are still essential.&lt;/p&gt;

&lt;h2&gt;
  
  
  When I Choose JavaScript Over TypeScript
&lt;/h2&gt;

&lt;p&gt;Even after all my experiences with TypeScript, there are definitely times when I still reach for plain JavaScript. Sometimes, the speed and simplicity of JS just make more sense.&lt;/p&gt;

&lt;p&gt;For quick prototypes or one-off scripts, the overhead of setting up types can feel unnecessary. If I just want to test an idea, hack together a small tool, or experiment with a library, TypeScript can slow me down more than it helps. JavaScript lets me move fast, make changes on the fly, and see results immediately.&lt;/p&gt;

&lt;p&gt;There are also situations where the project is short-lived or the code won’t be maintained long-term. Spending extra time defining types for a small script or a hacky solution doesn’t make sense. In these cases, the flexibility and minimal setup of JavaScript outweigh the safety net TypeScript provides.&lt;/p&gt;

&lt;p&gt;Finally, working solo sometimes means I know my own code well enough that type safety feels redundant. I can read my code quickly, keep track of variable shapes, and spot mistakes without the compiler holding my hand. TypeScript is amazing, but it’s not always necessary when the context doesn’t call for it.&lt;/p&gt;

&lt;p&gt;In short, I reach for JavaScript when speed, simplicity, and flexibility matter more than strict safety.&lt;/p&gt;

&lt;h2&gt;
  
  
  When I Choose TypeScript
&lt;/h2&gt;

&lt;p&gt;Even though there are times I prefer plain JavaScript, there are plenty of situations where TypeScript is my go-to. The main factor is usually scale. When a project is large or expected to last a long time, the safety and structure that TypeScript provides become invaluable.&lt;/p&gt;

&lt;p&gt;TypeScript shines in team projects. Types act like a shared language that reduces confusion and miscommunication. When someone else writes a function or module, I can understand exactly what inputs it expects and what outputs it provides, without needing to read through pages of code or ask too many questions.&lt;/p&gt;

&lt;p&gt;I also choose TypeScript when working with APIs or external data sources. Having explicit types helps catch unexpected changes or missing fields in responses, which can save hours of debugging later.&lt;/p&gt;

&lt;p&gt;And then there’s maintainability. Returning to code after weeks or months is always easier with TypeScript. Types serve as reminders of how things are structured, making it simpler to add features or refactor without breaking unrelated parts of the code.&lt;/p&gt;

&lt;p&gt;In short, TypeScript becomes my tool of choice whenever clarity, reliability, and collaboration matter more than raw speed. It’s not just about preventing bugs, it’s about building confidence in the code that I and others will rely on.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Takeaway
&lt;/h2&gt;

&lt;p&gt;TypeScript has been a fascinating journey for me. Coming from Java, I was used to strict types and compile-time safety, and JavaScript initially felt like a breath of fresh air. That freedom was exhilarating but it came with hidden costs, from runtime errors to messy, hard-to-maintain code.&lt;/p&gt;

&lt;p&gt;TypeScript offered a middle ground. It caught many of the mistakes that would have haunted me in plain JavaScript, improved readability, and made teamwork much smoother. At the same time, it’s not without friction. Sometimes the type system feels heavy, overly strict, or just plain confusing. And of course, it can’t protect you from logic errors or unpredictable runtime data.&lt;/p&gt;

&lt;p&gt;For me, the choice between JavaScript and TypeScript is all about context. I reach for JavaScript when speed, flexibility, or small-scale projects matter. I choose TypeScript when clarity, maintainability, and collaboration are priorities.&lt;/p&gt;

&lt;p&gt;At the end of the day, types are a tool, not a guarantee. They guide you, catch mistakes early, and save time but they don’t replace careful thinking, testing, or judgment. And yes, sometimes they feel like a little extra weight but a safer, more confident weight.&lt;/p&gt;

&lt;p&gt;Thank you for reading! If you enjoyed this article or have your own experiences with JavaScript and TypeScript, I’d love to hear from you! Share your thoughts in the comments, or reach out to me on X (@gauravnadkarni) or via email (&lt;a href="mailto:nadkarnigaurav@gmail.com"&gt;nadkarnigaurav@gmail.com&lt;/a&gt;).&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>typesafety</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Building an Extendable Data Migration Utility in Java Using the Strategy Pattern</title>
      <dc:creator>Gaurav Nadkarni</dc:creator>
      <pubDate>Tue, 23 Sep 2025 04:36:52 +0000</pubDate>
      <link>https://dev.to/nadkarnigaurav/building-an-extendable-data-migration-utility-in-java-using-the-strategy-pattern-3amb</link>
      <guid>https://dev.to/nadkarnigaurav/building-an-extendable-data-migration-utility-in-java-using-the-strategy-pattern-3amb</guid>
      <description>&lt;p&gt;How I applied the Strategy Pattern to handle multiple source systems and keep the utility scalable&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;While working on a Java-based utility, I encountered a challenge: moving data between different systems that each had their own export formats. Our target system was Jira Cloud, which accepted data in a specific JSON format through its APIs.&lt;/p&gt;

&lt;p&gt;The complication came from the source systems. One example was GitHub, where I needed to export issues in JSON. Other systems exported their data in entirely different formats. The requirement was clear: I needed a flexible solution that could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Support multiple source systems, each with different data formats&lt;/li&gt;
&lt;li&gt;Transform the extracted data into a unified format compatible with Jira&lt;/li&gt;
&lt;li&gt;Be easily extendable to support new platforms in the future&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The load step (pushing data to Jira) was straightforward. The real complexity lay in the extract and transform steps. That’s where the Strategy Pattern turned out to be the right approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Strategy Pattern?
&lt;/h2&gt;

&lt;p&gt;The Strategy Pattern allows you to encapsulate different algorithms (or logics) into separate classes and make them interchangeable at runtime.&lt;/p&gt;

&lt;p&gt;For our use case, the “algorithms” were the extraction and transformation processes, which varied depending on the source system. Using the Strategy Pattern gave us two key benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flexibility: I could easily switch between different extraction and transformation strategies at runtime based on the source system.&lt;/li&gt;
&lt;li&gt;Extensibility: Adding support for a new platform meant just implementing a new strategy without changing the existing code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Defining the Strategy Interfaces
&lt;/h3&gt;

&lt;p&gt;I broke the problem into two main steps: Extract and Transform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extract Strategy&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;public interface ExtractStrategy { 
  String extractData();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Transform Strategy&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;public interface TransformStrategy {
  String transformData(String rawData);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Implementing Strategies for GitHub
&lt;/h2&gt;

&lt;p&gt;Let’s take GitHub as an example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Extract strategy for GitHub
public class GitHubExtractStrategy implements ExtractStrategy {
    @Override
    public String extractData() {
        // Simulating extraction from GitHub
        return "{ \"title\": \"Issue 101\", \"body\": \"Sample issue description\" }";
    }
}

// Transform strategy for GitHub data
public class GitHubTransformStrategy implements TransformStrategy {
    @Override
    public String transformData(String rawData) {
        // Convert GitHub issue JSON into Jira-compatible JSON
        return "{ \"fields\": { \"summary\": \"Issue 101\", \"description\": \"Sample issue description\" } }";
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similarly, if another system like ServiceNow was introduced later, I could create &lt;code&gt;ServiceNowExtractStrategy&lt;/code&gt; and &lt;code&gt;ServiceNowTransformStrategy&lt;/code&gt; without changing existing code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context to Tie Strategies Together
&lt;/h2&gt;

&lt;p&gt;I used a Context class to apply the strategies dynamically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class DataProcessor {
    private ExtractStrategy extractStrategy;
    private TransformStrategy transformStrategy;

    public DataProcessor(ExtractStrategy extractStrategy, TransformStrategy transformStrategy) {
        this.extractStrategy = extractStrategy;
        this.transformStrategy = transformStrategy;
    }

    public void process() {
        String rawData = extractStrategy.extractData();
        String transformedData = transformStrategy.transformData(rawData);

        // Load step (pushing to Jira)
        System.out.println("Loading to Jira: " + transformedData);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using the Strategies at Runtime
&lt;/h2&gt;

&lt;p&gt;The client code simply decides which strategies to use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class Main {
    public static void main(String[] args) {
        // GitHub strategies
        ExtractStrategy extractStrategy = new GitHubExtractStrategy();
        TransformStrategy transformStrategy = new GitHubTransformStrategy();

        DataProcessor processor = new DataProcessor(extractStrategy, transformStrategy);
        processor.process();

        // In future, just plug in new strategies for other platforms
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Outcome and Takeaways
&lt;/h2&gt;

&lt;p&gt;With this design, I achieved the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each source system’s extraction and transformation logic stayed isolated.&lt;/li&gt;
&lt;li&gt;The utility was easy to extend and supporting a new system was just a matter of writing a new strategy.&lt;/li&gt;
&lt;li&gt;The load step remained independent of extraction and transformation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Strategy Pattern fit naturally into the Extract-Transform-Load (ETL) process, letting us build a scalable and maintainable migration utility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Different Data Formats
&lt;/h2&gt;

&lt;p&gt;While strategies in our utility were tied to platforms, I noticed that data formats (JSON, CSV, XML) played an important role inside each strategy.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub APIs exported JSON.&lt;/li&gt;
&lt;li&gt;Some other platforms exported CSV.&lt;/li&gt;
&lt;li&gt;Others used XML.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of making data formats their own strategies, I treated them as implementation details within platform-specific strategies. This way, the decision of which format to parse remained encapsulated in each platform strategy, keeping the overall design clean and platform-centric.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up:
&lt;/h2&gt;

&lt;p&gt;This is how I used the Strategy Pattern in a real-world scenario. It not only solved the immediate problem of handling GitHub and other systems but also gave us a foundation to support future integrations with minimal changes.&lt;/p&gt;

&lt;p&gt;Thank you for reading! I’d love to hear your thoughts or questions in the comments. If you are passionate about building global-ready products, let’s connect on X (@gauravnadkarni) or reach out to me via email (&lt;a href="mailto:nadkarnigaurav@gmail.com"&gt;nadkarnigaurav@gmail.com&lt;/a&gt;).&lt;/p&gt;

</description>
      <category>strategypattern</category>
      <category>designpatterns</category>
    </item>
    <item>
      <title>How to Ghost Your Data: Implementing Soft Deletes in Prisma</title>
      <dc:creator>Gaurav Nadkarni</dc:creator>
      <pubDate>Thu, 18 Sep 2025 06:37:30 +0000</pubDate>
      <link>https://dev.to/nadkarnigaurav/how-to-ghost-your-data-implementing-soft-deletes-in-prisma-10nm</link>
      <guid>https://dev.to/nadkarnigaurav/how-to-ghost-your-data-implementing-soft-deletes-in-prisma-10nm</guid>
      <description>&lt;p&gt;Learn how to implement soft deletes in Prisma, avoid common pitfalls, and keep those ghost records under control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;It was just another regular day at the office. I was staring at a database schema, plotting out a shiny new feature, when my phone buzzed. One of the guys from the support team was on the line.&lt;/p&gt;

&lt;p&gt;Apparently, a customer had accidentally deleted a few records through the application UI and now wanted to know the answer to the million-dollar question: “Can you get them back?”&lt;/p&gt;

&lt;p&gt;The answer, of course, was the soul-crushing, developer-standard “No.”&lt;/p&gt;

&lt;p&gt;So, the poor customer had to recreate those records from scratch. And me? I couldn’t stop thinking, there had to be a better way.&lt;/p&gt;

&lt;p&gt;Sure, the obvious answer was to restore from a backup. But let’s be real: digging through backups just to fish out a single record is like searching for a specific grain of rice in a bag slow, tedious, and way too much effort for what should be a simple fix.&lt;/p&gt;

&lt;p&gt;And that’s when it hit me: maybe what I needed wasn’t a backup. Maybe what I needed was soft deletes, a way to delete data without actually, you know, deleting it.&lt;/p&gt;

&lt;p&gt;For the examples in this article, let’s imagine a simple setup with User and Post tables you know, the classic “social media app that everyone builds to learn something new.” I’ll use this example to explain how to implement soft deletes in the sections ahead. And don’t worry, you won’t be left piecing things together from scattered snippets of code. I’ve provided the full codebase in a GitHub repo (linked at the end of the article).&lt;/p&gt;

&lt;p&gt;This article is for anyone who wants to understand what soft deletes are, why they matter, and how they can be configured with Prisma without losing your database sanity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Soft Deletes Are the Chill Way to Delete
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is soft deletion, after all?&lt;/strong&gt;&lt;br&gt;
Simply put, it’s a way to mark a record as deleted in your database without actually removing it. Think of it like putting something in the “trash” on your computer, it’s out of sight for the user, but still sitting safely in the database, waiting to be restored (or permanently purged later).&lt;/p&gt;

&lt;p&gt;Here’s how it works: the record stays in the database, but your application filters it out during queries so that users never see it. We’ll get into the “how” a bit later, but first, let’s talk about why soft deletes can save your bacon:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Oops moments from users: Someone accidentally deletes their data, and you can swoop in like a hero to restore it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Audit trails: Track when a record was marked as deleted, which can save hours of debugging or help with compliance checks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Safe relationship changes: No more scary errors because a dependent record suddenly vanished relationships stay intact.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;With soft deletes, you can pretend the data is gone while secretly keeping it around like a responsible digital hoarder.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Sketching Your Data’s Retirement Plan
&lt;/h2&gt;

&lt;p&gt;Now that we know what soft deletes are, let’s talk about how they actually work, from the frontend all the way to the database.&lt;/p&gt;

&lt;p&gt;At its core, soft deletion is simply about, marking a record in the database as “deleted” instead of truly removing it. To do this, we add a special column, let’s call it our “soft deletion identifier” which could be a boolean, text, or datetime column. This column acts like a little flag to tell us, “Hey, this record is technically gone, but we’re keeping it around just in case.”&lt;/p&gt;

&lt;p&gt;Here’s what the flow usually looks like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add the “soft deletion identifier” column to the tables you want to soft delete.&lt;/li&gt;
&lt;li&gt;Update your database queries to ignore records where the “soft deletion identifier” is set.&lt;/li&gt;
&lt;li&gt;User creates a record → the “soft deletion identifier” column is set to false or null (depending on your column type).&lt;/li&gt;
&lt;li&gt;User sees the record in the app like normal.&lt;/li&gt;
&lt;li&gt;Later, the user deletes the record → the “soft deletion identifier” column is updated (to &lt;code&gt;true&lt;/code&gt;, the current timestamp, or maybe even the ID of the user who deleted it).&lt;/li&gt;
&lt;li&gt;On the next query, your app skips over those flagged records to the user, it’s as if the record is gone, even though it’s still there, quietly existing in your database.&lt;/li&gt;
&lt;li&gt;This simple mechanism is the foundation for everything else we’ll build in the upcoming sections, from Prisma client extension to relationship handling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s the bare-minimum schema.prisma with only the fields you need for User, and Post including the deleted_at column which is the “soft deletion identifier” column.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;model User {
 id String @id @default(uuid())
 email String @unique
 name String
 posts Post[]
 comments Comment[]
 deleted_at DateTime? @db.Timestamptz
}

model Post {
 id String @id @default(uuid())
 title String
 content String?
 author User @relation(fields: [authorId], references: [id])
 authorId String
 deleted_at DateTime? @db.Timestamptz
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Extending the Prisma Client: Teaching It New Tricks
&lt;/h2&gt;

&lt;p&gt;Now that we’ve updated our database and know the high-level plan, it’s time to bring Prisma into the picture. Remember the deleted_at column we added earlier? Prisma doesn’t magically know what this column means. To Prisma, it’s just another field.&lt;/p&gt;

&lt;p&gt;That’s where Prisma extensions come in. With &lt;code&gt;client.$extends&lt;/code&gt;, we can hook into lifecycle methods, preprocess queries, and even add our own custom methods. Here’s the core idea of the &lt;code&gt;softDelete&lt;/code&gt; extension:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Intercept queries before they run (findUnique, findFirst, findMany, etc.) and automatically filter out records where deleted_at is set.&lt;/li&gt;
&lt;li&gt;Post-process results so the deleted_at field is stripped out unless explicitly requested.&lt;/li&gt;
&lt;li&gt;Add new helper methods (restore, hardDelete, etc.) so developers can safely control deletion behavior when needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means soft deletes become the default behavior across the app. Developers don’t have to sprinkle &lt;code&gt;deleted_at: null&lt;/code&gt; conditions everywhere. Prisma just takes care of it behind the scenes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const softDeleteExtension = Prisma.defineExtension((client) =&amp;gt;
 client.$extends({
 name: “softDeleteExtension”,
 query: { $allModels: { /* overrides */ } },
 model: { $allModels: { /* custom methods */ } },
 })
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside &lt;code&gt;query.$allModels&lt;/code&gt;, we intercept Prisma's built-in methods like &lt;code&gt;findUnique&lt;/code&gt;, &lt;code&gt;findFirst&lt;/code&gt;, and &lt;code&gt;findMany&lt;/code&gt;. For example, here's how we handle &lt;code&gt;findMany&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async findMany({ args, query }) {
  let queryArgs = args;// Only include non-deleted records by default
  queryArgs.where = { …queryArgs.where, deleted_at: null };

  const records = await query(queryArgs);

  // Remove deleted_at unless explicitly requested
 return records.map(({ deleted_at, …rest }) =&amp;gt; rest);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way, any query automatically ignores soft-deleted records unless you explicitly opt in.&lt;/p&gt;

&lt;p&gt;While working with Prisma’s &lt;code&gt;$extends&lt;/code&gt;, I picked up a few important points, some of them the hard way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The query key in $extends lets you override Prisma’s built-in query functions like findFirst, update, or create. This means you can tweak the arguments before they hit the database or even massage the results after they come back.&lt;/li&gt;
&lt;li&gt;The model key is where you can add custom methods (like restore or hardDelete) directly to your Prisma client. Super handy when soft delete logic becomes a first-class citizen in your app.&lt;/li&gt;
&lt;li&gt;Both query and model objects have a $allModels key, which is a catch-all way to override Prisma’s default implementation across every model.&lt;/li&gt;
&lt;li&gt;You can still target specific models (like user or post) to add/override methods for just that model. At runtime, Prisma checks if a model-specific method exists. If it does, that one gets called first and if you call the provided query function inside it, Prisma falls back to the $allModels implementation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of it like inheritance in OOP: model-specific overrides win, but the generic &lt;code&gt;$allModels&lt;/code&gt; safety net is always there if you want to enforce cross-model rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Soft Deleting Records: Because Delete Buttons Need Therapy
&lt;/h2&gt;

&lt;p&gt;Normally, &lt;code&gt;prisma.post.delete({ where: { id } })&lt;/code&gt; would wipe the record from the DB. With our extension, the &lt;code&gt;delete&lt;/code&gt; call is overridden to mark it as deleted instead of removing it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async delete({ args, query, model }) {
  // If hard delete requested, bypass soft delete
  const hardDeleteRecords = shouldHardDeleteRecords(args);
  if (hardDeleteRecords) {
     const { hardDelete, ...rest } = args;
     return query(rest);
   }

   args.where = { ...args.where, deleted_at: null };
   return (client as any)[model].update({
      where: { ...args.where },
      data: { deleted_at: new Date() },
   });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your “Delete” button is safe by default. And if you ever want to truly purge data, the extension exposes a hardDelete method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async hardDelete(where: Record&amp;lt;string, unknown&amp;gt;) {
    return (this as any).delete({
        hardDelete: true,
        where,
    });
}

//call to hard delete method
await prisma.post.hardDelete({ id: "123" });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Restoring Soft Deleted Records: Your Data’s Resurrection Button
&lt;/h2&gt;

&lt;p&gt;One of the best parts about soft deletes is the ability to undo mistakes. If a record was deleted by accident, you can bring it back with the custom &lt;code&gt;restore&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async restore(where: Record&amp;lt;string, unknown&amp;gt;) {
  return (this as any).update({
    where,
    data: { deleted_at: null as unknown as Date }, // resurrect the record
  });
}
async findFirstWithDeleted(
    where?: Record&amp;lt;string, unknown&amp;gt;,
    include?: Record&amp;lt;string, unknown&amp;gt;
) {
    return (this as any).findFirst({
      includeDeleted: true,
      where,
      include,
  });
},

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, if a user deletes a post by mistake:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`await prisma.post.restore({ id: "123" });`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And just like that the post is back in the app.&lt;/p&gt;

&lt;p&gt;You can also query deleted records when needed (say, in an admin dashboard) using the custom helper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await prisma.post.findFirstWithDeleted({ id: "123" })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Handling Relationships: The Awkward Family Drama
&lt;/h2&gt;

&lt;p&gt;Now comes the trickiest part: “relationships”. If soft deletes were a family, this is where the awkward drama begins.&lt;/p&gt;

&lt;p&gt;Take a simple example: you have user records and post records. A user can create posts. Easy enough. But what happens when you delete a user? You also need to delete (or soft delete) their posts. So far, manageable.&lt;/p&gt;

&lt;p&gt;But wait, the complexity ramps up with “soft deletes”. When you soft delete a user, you also have to soft delete all the posts connected to that user, and when you restore the user, guess what? Yup, you need to restore their posts too.&lt;/p&gt;

&lt;p&gt;Now here’s where things get awkward. Imagine a post was manually deleted by the user before the user account itself was soft deleted. When you restore the user later, do you also restore that post? You probably shouldn’t but unless you’re keeping track of how it was deleted, you don’t have a way to tell the difference. Suddenly, you’re not just restoring posts… you’re resurrecting ones that were meant to stay buried. Zombie posts, anyone? 🧟‍♂️&lt;/p&gt;

&lt;p&gt;And it doesn’t stop there. If you try to restore a post, but the author (user) is also soft deleted, do you restore the user too? This cascade effect gets complicated really fast, especially as your data model grows more complex (think &lt;code&gt;User&lt;/code&gt; → &lt;code&gt;Post&lt;/code&gt; → &lt;code&gt;Comment&lt;/code&gt; → &lt;code&gt;Like&lt;/code&gt; → &lt;code&gt;Tag&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Here’s a simplified code example of how we handle this using Prisma transactions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async delete({ args, query, model }) {
  const hardDeleteRecords = shouldHardDeleteRecords(args);

  return client.$transaction(async (tx) =&amp;gt; {
    const user = await (tx as any).user.findFirst({
      where: args.where,
    });

    if (!user) {
      return user;
    }

    if (hardDeleteRecords) {
      await (tx as any).post.deleteMany({
        where: { authorId: user.id },
      });

      return (tx as any).user.delete({
        where: { id: user.id },
      });
    }

    const updatedUser = await (tx as any).user.update({
      where: { id: user.id },
      data: { deleted_at: new Date() },
    });

    await (tx as any).post.updateMany({
      where: {
        authorId: user.id,
        deleted_at: null,
      },
      data: { deleted_at: new Date() },
    });

    return updatedUser;
  });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we use Prisma’s transaction object to make sure that when a user is deleted (hard or soft), their connected posts are handled at the same time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transactions: Where My Brain Nearly Filed for Bankruptcy
&lt;/h3&gt;

&lt;p&gt;If there’s one place that really tripped me up while working with soft deletes in Prisma, it was transactions. A few things I wish someone had told me before I spent hours questioning my career choices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Prisma client object and the transaction object are two different beasts. Don’t confuse them.&lt;/li&gt;
&lt;li&gt;Inside a transaction, the transaction object won’t have the extended methods you lovingly added with &lt;code&gt;$extends&lt;/code&gt;. It’s bare-bones.&lt;/li&gt;
&lt;li&gt;Types can get very messy here. Honestly, the safest route is to keep your complex logic (like hard deletes or restore flows) fully contained inside the transaction block.&lt;/li&gt;
&lt;li&gt;Whatever you do, don’t use the global client object inside a transaction block. If you do, those queries will run outside the transaction… which kind of defeats the whole point.
So yeah, think of transactions in Prisma as a special sandbox. Once you’re in, you have to play by its rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Guarding Against Chaos: No &lt;code&gt;deleted_at&lt;/code&gt; Updates on Create/Update
&lt;/h2&gt;

&lt;p&gt;We just saw how relationships can complicate soft deletes and turn them into a bit of a balancing act. But there’s another landmine you need to avoid: accidentally messing with your &lt;code&gt;soft delete identifier&lt;/code&gt; column (deleted_at).&lt;/p&gt;

&lt;p&gt;Think of deleted_at as a big red button. If you (or worse, your teammate) set a value there by mistake, the record is instantly marked as deleted, even though you never meant to. Not fun.&lt;/p&gt;

&lt;p&gt;That’s why it’s critical to keep this column completely off-limits during “create” and “update” operations. The rule of thumb: never touch deleted_at directly in your app code. It should only be managed through your Prisma extension - your safe wrapper that knows when and how to set it.&lt;/p&gt;

&lt;p&gt;By keeping that guardrail in place, you reduce the risk of random “why did my user disappear?” mysteries in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unique Constraints: Can Two Ghosts Share the Same Email?
&lt;/h2&gt;

&lt;p&gt;Here’s another fun wrinkle: unique constraints.&lt;/p&gt;

&lt;p&gt;Take the classic case of a user model with an email field that has a unique constraint. In a hard delete world, once a user is gone, that email (or username) is free to use again. But with soft deletes, the old record is still hanging around in the database, just marked as invisible.&lt;/p&gt;

&lt;p&gt;So what happens if someone tries to sign up with the same email? Boom 💥, you hit a constraint violation. The database doesn’t care that the record is “soft deleted”. It still sees it as existing.&lt;/p&gt;

&lt;p&gt;This creates an awkward situation where ghost records can block new, legitimate ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  Possible ways to handle this:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Partial unique indexes (if your database supports them): Create a unique index that only applies to records where deleted_at IS NULL. That way, soft-deleted rows won’t interfere.&lt;/li&gt;
&lt;li&gt;Archival tweaks: Instead of keeping the same email intact, you could append something to it during soft delete (e.g., &lt;code&gt;user@example.com&lt;/code&gt; → &lt;code&gt;user+deleted123@example.com&lt;/code&gt;). This frees up the original email for reuse.&lt;/li&gt;
&lt;li&gt;Application-level checks: Add logic in your Prisma extension or service layer to filter out soft-deleted records when enforcing uniqueness.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each option has trade-offs. Archival tweaks are simple but messy, partial indexes are clean but database-dependent, and app-level checks give flexibility but add complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Tips: Because Nobody Likes a Slow Ghost Hunt
&lt;/h2&gt;

&lt;p&gt;Soft deletes sound simple in theory: just add a deleted_at field and filter it out in queries. Easy, right? Well… not quite.&lt;/p&gt;

&lt;p&gt;Once your database starts growing, those “invisible” rows still sit there, quietly bloating your tables. Every time you query &lt;code&gt;WHERE deleted_at IS NULL&lt;/code&gt;, the database has to scan through all the rows including the ghosts. And that’s where performance can take a hit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why indexing deleted_at matters?&lt;/strong&gt;&lt;br&gt;
By adding an index on the deleted_at column, you give your database a fast way to separate the living from the dead. Instead of scanning every row, the database can jump straight to the subset you care about: those that aren’t soft deleted.&lt;/p&gt;

&lt;p&gt;In real-world terms, this means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster queries: when fetching active users, posts, or any entity.&lt;/li&gt;
&lt;li&gt;Consistent performance as your database grows.&lt;/li&gt;
&lt;li&gt;Less load on your database server, which translates into lower costs in the long run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add this to the &lt;code&gt;schema.prisma&lt;/code&gt; file in the required model&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@@index([deleted_at])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when Prisma runs queries like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const activeUsers = await prisma.user.findMany({ where: { deleted_at: null } });`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the database doesn’t slog through a swamp of deleted rows. It uses the index to quickly find the living ones.&lt;/p&gt;

&lt;p&gt;⚡ Pro tip: If you have composite queries (like filtering by &lt;code&gt;deleted_at&lt;/code&gt; and &lt;code&gt;authorId&lt;/code&gt;), consider creating a composite index ((&lt;code&gt;authorId&lt;/code&gt;, &lt;code&gt;deleted_at&lt;/code&gt;)) for even faster lookups.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@@index([authorId, deleted_at])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Counting, Grouping, and Other Math-y Things
&lt;/h2&gt;

&lt;p&gt;Soft deletes aren’t just about hiding rows, they can quietly sabotage your analytics too. Imagine pulling a report of &lt;code&gt;active users&lt;/code&gt; and your dashboard proudly says 1,000. Except… 200 of those users are technically “deleted,” just still hanging around in the database like party guests who won’t take the hint. 🎃&lt;/p&gt;

&lt;p&gt;By default, Prisma’s &lt;code&gt;count&lt;/code&gt;, &lt;code&gt;aggregate&lt;/code&gt;, and &lt;code&gt;groupBy&lt;/code&gt; queries don’t know about your &lt;code&gt;deleted_at&lt;/code&gt; filter. That means they’ll happily include ghost records unless you explicitly tell them not to.&lt;/p&gt;

&lt;p&gt;For example, this innocent-looking query will count &lt;code&gt;everyone&lt;/code&gt;, living and undead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const totalUsers = await prisma.user.count();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result: includes soft-deleted rows too.&lt;/p&gt;

&lt;p&gt;To fix it, always add a filter for &lt;code&gt;deleted_at: null&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const totalActiveUsers = await prisma.user.count({
  where: { deleted_at: null },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your numbers reflect reality (well, database reality at least).&lt;/p&gt;

&lt;p&gt;The same applies to aggregations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const avgPostsPerUser = await prisma.post.aggregate({
  _avg: { id: true },
  where: { deleted_at: null },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And groupings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const postsByAuthor = await prisma.post.groupBy({
  by: ['authorId'],
  _count: { _all: true },
  where: { deleted_at: null },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key takeaway 📝:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always filter out deleted records in analytical queries.&lt;/li&gt;
&lt;li&gt;Or better yet, bake this logic into your Prisma extension so you don’t have to remember it each time (your future self will thank you).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Otherwise, your analytics will be haunted by numbers that don’t match what your users actually see and nothing freaks out a product manager faster than “phantom” users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-Up: Congrats, You’re Now a Soft Delete Whisperer
&lt;/h2&gt;

&lt;p&gt;If you’ve made it this far, give yourself a pat on the back (or at least a strong coffee). You’ve survived the rollercoaster of soft deletes, from handling relationships (aka the family drama nobody asked for) to making sure your analytics don’t get haunted by ghost rows.&lt;/p&gt;

&lt;p&gt;A few best practices to keep in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep deletes consistent: Don’t mix hard and soft deletes without a clear strategy.&lt;/li&gt;
&lt;li&gt;Guard your deleted_at column: Only your Prisma extension should touch it. Treat it like the “Do Not Disturb” sign on your database’s hotel door.&lt;/li&gt;
&lt;li&gt;Index smartly: Adding an index on deleted_at can save you from painfully slow queries.&lt;/li&gt;
&lt;li&gt;Watch your analytics: Remember, ghosts love to sneak into counts and aggregates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The truth is, soft deletes aren’t magic, they’re just a way of giving your data a comfy afterlife instead of sending it straight to oblivion. But like anything in engineering, the devil’s in the details.&lt;/p&gt;

&lt;p&gt;And hey, if your database seems like it needs therapy after all those “not-quite-deletes,” don’t worry, it’s just part of growing up in a world where nothing ever really goes away (except maybe your weekend plans).&lt;/p&gt;

&lt;p&gt;Thank you for reading! I’d love to hear your thoughts or questions in the comments. If you are passionate about building global-ready products, let’s connect on X (@gauravnadkarni) or reach out via email (&lt;a href="mailto:nadkarnigaurav@gmail.com"&gt;nadkarnigaurav@gmail.com&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Github Code (NextJS project with instructions to setup, execute and see soft deletes in action):&lt;br&gt;
&lt;a href="https://github.com/gauravnadkarni/prisma-soft-delete-article-code" rel="noopener noreferrer"&gt;https://github.com/gauravnadkarni/prisma-soft-delete-article-code&lt;/a&gt;&lt;/p&gt;

</description>
      <category>prisma</category>
      <category>softdeletes</category>
      <category>nextjs</category>
      <category>database</category>
    </item>
    <item>
      <title>Chrome Extension Development - Develop minimal app with TypeScript, React, Tailwind CSS and Webpack</title>
      <dc:creator>Gaurav Nadkarni</dc:creator>
      <pubDate>Thu, 26 Dec 2024 09:00:00 +0000</pubDate>
      <link>https://dev.to/nadkarnigaurav/chrome-extension-development-develop-minimal-app-with-typescript-react-tailwind-css-and-webpack-3a46</link>
      <guid>https://dev.to/nadkarnigaurav/chrome-extension-development-develop-minimal-app-with-typescript-react-tailwind-css-and-webpack-3a46</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In this blog, we will explore how to set up and develop a Chrome extension using TypeScript, React, Tailwind CSS, and Webpack. We will create a minimal extension called "NoteMe" ✍️ to put our understanding to the test. Our extension will include the following features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allow users to add multiple notes for a given website&lt;/li&gt;
&lt;li&gt;Enable users to view saved notes for a given website&lt;/li&gt;
&lt;li&gt;Provide the option to delete notes for a given website&lt;/li&gt;
&lt;li&gt;Save notes locally in the browser’s storage&lt;/li&gt;
&lt;li&gt;Optionally sync notes with a backend for cloud storage&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Refresher
&lt;/h3&gt;

&lt;p&gt;In this blog, we will learn how to build a Chrome extension using modern technologies. This guide assumes that you already have some familiarity with building and uploading an extension to Chrome during local development. If you are new to this or need a detailed walkthrough of the basics, I recommend checking out my previous blog: &lt;a href="https://dev.to/nadkarnigaurav/step-into-chrome-extension-development-easy-setup-with-typescript-and-webpack-1khp"&gt;Link&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Extension sneak peek
&lt;/h3&gt;

&lt;p&gt;The extension will include the following components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Toggle Button&lt;/strong&gt;: A button to open and close the sidebar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sidebar&lt;/strong&gt;: A versatile panel where users can:
Write new notes.
View saved notes.
Delete saved notes.
Sync notes with the backend (provision available in the code, though no backend is connected currently).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Popup&lt;/strong&gt;: A small window allowing users to reposition the toggle button (used to open/close the sidebar) at prespecified positions on the screen
&lt;strong&gt;Note&lt;/strong&gt;: While there’s no backend integration in this implementation, the code includes provisions to connect a backend in the future.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below are screenshots showcasing how the extension will look upon completion:&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%2Fu33lpq404yqqtn74vbkg.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%2Fu33lpq404yqqtn74vbkg.png" alt="Screenshot of the app running with facebook.com" width="800" height="397"&gt;&lt;/a&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq2kd6klgxsoi80lmil4m.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%2Fq2kd6klgxsoi80lmil4m.png" alt="Screenshot of the app running with google.com" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Before diving into this tutorial, ensure you have the following tools installed on your system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node.js&lt;/strong&gt; (v18.16 LTS or later)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NPM&lt;/strong&gt; (Node Package Manager, bundled with Node.js)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TypeScript&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Webpack&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VS Code Editor&lt;/strong&gt; (or any code editor of your choice)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Extension from 40,000 Feet
&lt;/h3&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%2F05xz55m7r1qv8ujskle3.jpg" 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%2F05xz55m7r1qv8ujskle3.jpg" alt="control flow diagram of the chrome extension" width="661" height="561"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The figure above provides a high-level overview of the internal workings of this extension. Here are some key points we can derive from the diagram:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;content script&lt;/strong&gt; interacts directly with the &lt;code&gt;DOM&lt;/code&gt; of the parent web page, enabling it to modify the page's content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Popup&lt;/strong&gt;, &lt;strong&gt;background&lt;/strong&gt;, and &lt;strong&gt;content scripts&lt;/strong&gt; communicate with each other through Chrome's runtime messaging system.&lt;/li&gt;
&lt;li&gt;For tasks related to Chrome storage or backend API calls, &lt;strong&gt;content&lt;/strong&gt; or &lt;strong&gt;popup scripts&lt;/strong&gt; delegate the responsibility to the &lt;strong&gt;background worker&lt;/strong&gt; using the runtime messaging system.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;background script&lt;/strong&gt; acts as the sole mediator with the app backend and Chrome's storage. It also relays notifications, if any, to other scripts using runtime messaging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Popup&lt;/strong&gt; and &lt;strong&gt;content scripts&lt;/strong&gt; exchange information directly through Chrome's runtime messaging system.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Setup of the extension
&lt;/h3&gt;

&lt;p&gt;While Chrome extension projects don’t mandate a specific project structure, they do require a &lt;code&gt;manifest.json&lt;/code&gt; file to be located at the root of the build directory. Taking advantage of this flexibility, we’ll define a custom project structure that helps organize different scripts effectively. This structure will enable better code reuse across scripts and minimize duplication, streamlining our development process.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 1: Create a basic directory structure for the project
&lt;/h4&gt;

&lt;p&gt;To get started, we’ll set up a foundational directory structure for the project. You can use the following bash script to create the basic structure along with the &lt;code&gt;manifest.json&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash

bash_script_absolute_path=$(pwd)
declare public_paths=("public" "public/assets" "public/assets/images")
declare source_paths=("src" "src/lib" "src/scripts" "src/scripts/background" "src/scripts/content" "src/scripts/injected" "src/scripts/popup" "src/styles")
declare public_directory_path="public"
declare manifest_file="manifest.json"
declare project_name="note-me"

create_directory () {
    if [ ! -d "$1" ]; then
        mkdir ${1}
    fi
}

create_file () {
    if [ ! -e "$2/$1" ]; then
        touch $2/$1
    fi
}

create_public_directories () {
    for public_path in "${public_paths[@]}";
    do
        create_directory $public_path
    done
}

create_source_directories () {
    for source_path in "${source_paths[@]}";
    do
        create_directory $source_path
    done
}

execute () {
    echo "creating project struture at "${bash_script_absolute_path}
    create_directory $project_name
    cd $bash_script_absolute_path"/"$project_name
    create_public_directories
    create_source_directories
    create_file $manifest_file $public_directory_path
    echo "done creating project struture at "${bash_script_absolute_path}" with project name "$project_name
}

execute
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ensure that your directory structure resembles the one shown in the screenshot below.  &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%2Fbihhnfdktsbgxb01o4s9.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%2Fbihhnfdktsbgxb01o4s9.png" alt="Screenshot showing directory structure" width="246" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 2: The &lt;code&gt;manifest.json&lt;/code&gt; file located in the &lt;code&gt;public&lt;/code&gt; directory should be structured as shown below:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "manifest_version": 3,
    "name": "NoteMe",
    "version": "1.0",
    "description": "A Chrome extension built with React and TypeScript using Webpack.",
    "action": {
      "default_popup": "popup.html",
      "default_icon": "app-icon.png"
    },
    "background": {
      "service_worker": "background.js",
      "type": "module"
    },
    "content_scripts": [
      {
        "matches": ["&amp;lt;all_urls&amp;gt;"],
        "js": ["content.js"],
        "run_at": "document_end"
      }
    ],
    "permissions": [
      "storage",
      "activeTab",
      "scripting",
      "webNavigation"
    ],
    "host_permissions": ["&amp;lt;all_urls&amp;gt;"],
    "web_accessible_resources": [
      {
        "resources": ["styles.css", "sidebar-open.png", "sidebar-close.png"],
        "matches": ["&amp;lt;all_urls&amp;gt;"]
      }
    ]
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Points to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The file extensions are &lt;code&gt;.js&lt;/code&gt; because the &lt;code&gt;.ts&lt;/code&gt; files will be compiled into &lt;code&gt;.js&lt;/code&gt; files, which are required at runtime in the Chrome environment.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;matches&lt;/code&gt; field uses &lt;code&gt;&amp;lt;all_urls&amp;gt;&lt;/code&gt; as its value, enabling the extension to operate on any webpage loaded in Chrome.&lt;/li&gt;
&lt;li&gt;Three image files are referenced: &lt;code&gt;app-icon.png&lt;/code&gt;, &lt;code&gt;sidebar-open.png&lt;/code&gt;, and &lt;code&gt;sidebar-close.png&lt;/code&gt;. You can find these files in the repository linked at the end of this blog.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;manifest.json&lt;/code&gt; file must be placed at the root level of the &lt;code&gt;dist&lt;/code&gt; directory after the project is built. To ensure this, we need to configure the webpack settings to move it appropriately during the build process.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Step 3: Initialize npm and Install Dependencies
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Start by initializing npm in your project using the following command: &lt;code&gt;npm init -y&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add the necessary &lt;strong&gt;development dependencies&lt;/strong&gt; to the &lt;code&gt;devDependencies&lt;/code&gt; section of your project. Run the following command:
&lt;code&gt;npm i --save-dev @types/chrome @types/react @types/react-dom autoprefixer copy-webpack-plugin css-loader mini-css-extract-plugin postcss postcss-loader style-loader tailwindcss ts-loader typescript webpack webpack-cli webpack-dev-server&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add the &lt;strong&gt;runtime dependencies&lt;/strong&gt; needed for running the project:
&lt;code&gt;npm i --save react react-dom&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Step 4: Create files referenced in the &lt;code&gt;manifest.json&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Create following files which are referenced in the &lt;code&gt;manifest.json&lt;/code&gt;: &lt;code&gt;backgroun.ts&lt;/code&gt;, &lt;code&gt;content.ts&lt;/code&gt; and &lt;code&gt;popup.html&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;background.ts&lt;/code&gt;: Create this file in the &lt;code&gt;src/scripts/background&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;content.ts&lt;/code&gt;: Create this file in the &lt;code&gt;src/scripts/content&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;popup.html&lt;/code&gt; Create this file in the &lt;code&gt;public&lt;/code&gt; directory&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Step 5: Update &lt;code&gt;popup&lt;/code&gt; and &lt;code&gt;background&lt;/code&gt; Code
&lt;/h4&gt;

&lt;p&gt;Add the following code to the &lt;code&gt;popup.html&lt;/code&gt; file in the &lt;code&gt;public&lt;/code&gt; directory:&lt;br&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;link rel="stylesheet" href="styles.css" type="text/css" /&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 id="root"&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;&lt;strong&gt;Note:&lt;/strong&gt;&lt;br&gt;
This file acts as the popup interface that appears when the user clicks on the extension name in the Chrome browser. The &lt;code&gt;styles.css&lt;/code&gt; and &lt;code&gt;popup.js&lt;/code&gt; files will be generated in subsequent steps, so please follow along.&lt;/p&gt;

&lt;p&gt;Add the following code to the &lt;code&gt;background.ts&lt;/code&gt; file in the &lt;code&gt;src/scripts/background&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { onAppInstalled, onMessage } from "../../lib/worker";

(() =&amp;gt; {
    chrome.runtime.onInstalled.addListener(onAppInstalled);
    chrome.runtime.onMessage.addListener(onMessage)
})();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The code above installs two listeners:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The function registered by &lt;code&gt;chrome.runtime.onInstalled.addListener&lt;/code&gt; executes whenever the extension is installed in the browser. This can be used to initialize Chrome storage or a backend (if applicable) with a predefined state.&lt;/li&gt;
&lt;li&gt;The function registered by &lt;code&gt;chrome.runtime.onMessage.addListener&lt;/code&gt; executes whenever the background script receives a message from the content or popup scripts.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Additionally, the &lt;code&gt;import&lt;/code&gt; statement brings in listeners from the &lt;code&gt;src/lib&lt;/code&gt; directory. The core app logic is built in &lt;code&gt;src/lib&lt;/code&gt;, enabling reuse across different contexts (e.g., &lt;code&gt;content&lt;/code&gt; and &lt;code&gt;background&lt;/code&gt; scripts).&lt;/p&gt;
&lt;h4&gt;
  
  
  Step 6: Walkthrough of the &lt;code&gt;src/lib&lt;/code&gt; Directory
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;src/lib&lt;/code&gt; directory houses the core logic of the extension. Below is an overview of its structure and key components:&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%2Fia07utqouup4xoebruho.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%2Fia07utqouup4xoebruho.png" alt="Screenshot showing directory structure of the lib directory" width="418" height="248"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;components&lt;/code&gt; Directory:&lt;/strong&gt;
Contains all the React components used in the extension.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;lib/components/ContentApp.tsx&lt;/code&gt;:&lt;/strong&gt;
Acts as the container component for the content script.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;lib/components/NoteMePosition.tsx&lt;/code&gt;:&lt;/strong&gt;
Contains the component responsible for the popup script.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;helpers.ts&lt;/code&gt;:&lt;/strong&gt;
Includes helper functions used throughout the extension.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;storage-model.ts&lt;/code&gt;:&lt;/strong&gt;
Manages interactions with Chrome's local storage. For details about the structure of the data stored, refer to this file along with &lt;code&gt;types.ts&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;types.ts&lt;/code&gt;:&lt;/strong&gt;
Defines the custom types used in the extension.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;worker.ts&lt;/code&gt;:&lt;/strong&gt;
Contains callbacks for background event listeners.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For detailed implementation, please refer to the actual code in the repository.&lt;/p&gt;
&lt;h4&gt;
  
  
  Step 7: Mounting React Components
&lt;/h4&gt;

&lt;p&gt;In this step, we mount the React components for rendering. These components are mounted in two different scripts:&lt;code&gt;src/scripts/content/content.ts&lt;/code&gt; and &lt;code&gt;src/scripts/popup/popup.ts&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Popup Script:&lt;/strong&gt; Found in &lt;code&gt;src/scripts/popup/popup.ts&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(() =&amp;gt; {
  const container = document.createElement("div");
  container.id = "note-me-position-component-root";
  document.body.appendChild(container);
  const root = ReactDOM.createRoot(container);
  root.render(&amp;lt;NoteMePosition buttons={BUTTONS}/&amp;gt;);
})();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Content Script:&lt;/strong&gt; Found in &lt;code&gt;src/scripts/content/content.ts&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const mountContentRootComponent = () =&amp;gt; {
  const container = document.createElement("div");
  container.id = "note-me-component-root";
  const shadowRoot = container.attachShadow({ mode: "open" });
  document.body.appendChild(container);  

  const styleLink = document.createElement("link");
  styleLink.setAttribute("rel", "stylesheet");
  styleLink.setAttribute("href", chrome.runtime.getURL("styles.css"));
  shadowRoot.appendChild(styleLink);

  const shadowContainer = document.createElement("div");
  shadowRoot.appendChild(shadowContainer);
  const root = ReactDOM.createRoot(shadowContainer);
  root.render(&amp;lt;ContentApp /&amp;gt;);
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  key points:
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Separate Mounting Scripts:&lt;/strong&gt;   The popup and content scripts operate in different contexts   &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Popup Script:&lt;/strong&gt; Runs within the context of the &lt;code&gt;popup.html&lt;/code&gt; webpage in which it is loaded.  &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content Script:&lt;/strong&gt; Runs within the context of the main webpage loaded in the browser.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shadow DOM for Content Script:&lt;/strong&gt;  

&lt;ul&gt;
&lt;li&gt;Styles injected by the content script could potentially affect the parent webpage's appearance.  &lt;/li&gt;
&lt;li&gt;To prevent this, we use the &lt;strong&gt;Shadow DOM&lt;/strong&gt; to encapsulate the styles, ensuring they remain isolated within the extension.  &lt;/li&gt;
&lt;li&gt;This is not necessary for the popup script, as it operates in its own isolated environment (&lt;code&gt;popup.html&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h4&gt;
  
  
  Step 8: Configurations for Compiling and Building
&lt;/h4&gt;

&lt;p&gt;Adding the configurations required for compiling and building the extension&lt;/p&gt;

&lt;p&gt;To successfully compile and build the extension, we need to configure the following files: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;postcss.config.js&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;tailwind.config.js&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;tsconfig.json&lt;/code&gt;&lt;/strong&gt; &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;webpack.config.js&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Key Points:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Default Settings:&lt;/strong&gt;  Wherever possible, default settings are provided to simplify the process and ensure focus remains on the primary goal—building a fully functional extension.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Details in Repository:&lt;/strong&gt;  For the complete configurations and detailed settings of these files, please refer to the code repository. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These configurations handle the TypeScript compilation, Tailwind CSS integration, and the overall Webpack build process for the extension.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing the extension
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Generate the &lt;code&gt;dist&lt;/code&gt; Directory:&lt;/strong&gt; Run the following command to create the &lt;code&gt;dist&lt;/code&gt; directory:  &lt;code&gt;npm run build&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upload to Chrome:&lt;/strong&gt;    

&lt;ul&gt;
&lt;li&gt;Open Chrome and navigate to &lt;code&gt;chrome://extensions/&lt;/code&gt;.    &lt;/li&gt;
&lt;li&gt;Enable &lt;strong&gt;Developer Mode&lt;/strong&gt; in the top-right corner.    &lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;Load Unpacked&lt;/strong&gt; and select the &lt;code&gt;dist&lt;/code&gt; directory.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify Installation:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Once loaded, the extension's icon will appear on each page in the bottom-right corner by default.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Functionality Check:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Position Control:&lt;/strong&gt; Use the controls in the popup to change the position of the icon.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notes Feature:&lt;/strong&gt; Notes are saved independently for each website and can be deleted for a specific site without affecting others.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend Simulation:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;While there is no backend connected currently, the code includes a provision to integrate with one.&lt;/li&gt;
&lt;li&gt;The current implementation mimics a backend connection using &lt;code&gt;setTimeout&lt;/code&gt; and &lt;code&gt;promises&lt;/code&gt; to simulate asynchronous interactions.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here are some screenshots captured during the testing of the extension.&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%2F8usbuvsmwad3vqeyfbjp.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%2F8usbuvsmwad3vqeyfbjp.png" alt="Screenshot showing extension running with google.com" width="800" height="394"&gt;&lt;/a&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy1658ta1m3dkoksmeily.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%2Fy1658ta1m3dkoksmeily.png" alt="Screenshot showing extension running with yahoo.com" width="800" height="392"&gt;&lt;/a&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgcb793qjq891h3iuvpck.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%2Fgcb793qjq891h3iuvpck.png" alt="Screenshot showing object structure stored in the chrome storage" width="800" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Takeaways
&lt;/h3&gt;

&lt;p&gt;Here are a few key takeaways from this blog,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We explored how various components of the Chrome environment, such as content scripts, popup scripts, and background workers, communicate with each other using Chrome's runtime messaging system.&lt;/li&gt;
&lt;li&gt;We learned how to configure and build a Chrome extension from scratch, including setting up the project structure, installing dependencies, and writing core functionality.&lt;/li&gt;
&lt;li&gt;We discovered some good practices, such as:

&lt;ul&gt;
&lt;li&gt;Enhancing code reusability across scripts for maintainability and scalability.&lt;/li&gt;
&lt;li&gt;Utilizing Shadow DOM in content scripts to prevent style conflicts with the parent webpage.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Glimpse Ahead
&lt;/h3&gt;

&lt;p&gt;In the future, I plan to work on another blog where we will explore the process of publishing a fully functional Chrome extension to the Chrome Web Store. The goal of that blog will be to: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Develop an extension complex enough to solve a real-world problem. &lt;/li&gt;
&lt;li&gt;Demonstrate the step-by-step process of publishing the extension to the Chrome Web Store. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you for taking the time to read this blog! Your interest and support mean so much to me. I’m excited to share more insights as I continue this journey. &lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;p&gt;github link: &lt;a href="https://github.com/gauravnadkarni/chrome-extension-starter-app" rel="noopener noreferrer"&gt;https://github.com/gauravnadkarni/chrome-extension-starter-app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This article was originally published on &lt;a href="https://medium.com/@nadkarnigaurav/chrome-extension-development-develop-minimal-app-with-typescript-react-tailwind-css-and-webpack-52c9d82d4d25" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>browser</category>
      <category>extensions</category>
      <category>javascript</category>
      <category>react</category>
    </item>
    <item>
      <title>Step Into Chrome Extension Development: Easy Setup with TypeScript and Webpack</title>
      <dc:creator>Gaurav Nadkarni</dc:creator>
      <pubDate>Tue, 17 Dec 2024 07:34:46 +0000</pubDate>
      <link>https://dev.to/nadkarnigaurav/step-into-chrome-extension-development-easy-setup-with-typescript-and-webpack-1khp</link>
      <guid>https://dev.to/nadkarnigaurav/step-into-chrome-extension-development-easy-setup-with-typescript-and-webpack-1khp</guid>
      <description>&lt;p&gt;As of December 2024, Chrome remains the most popular browser worldwide. Learning to develop extensions for Chrome can open exciting opportunities to explore browser-based development and enhance your understanding of how browsers work under the hood. The Chrome Web Store offers a vast array of extensions that allow users to customize the browser’s default behavior and extend the functionality of various websites.&lt;/p&gt;

&lt;p&gt;In this blog, we’ll go through setting up a local development environment for creating Chrome extensions using TypeScript and Webpack. This guide is perfect for anyone looking to grasp the basics of Chrome extension development. By the end, you’ll have a functioning development environment ready to experiment with your first extension. Before diving in, make sure you have a foundational understanding of web technologies, JavaScript, and commonly used tools in the JavaScript ecosystem. Details are listed in the prerequisites section.&lt;/p&gt;

&lt;h4&gt;
  
  
  Quick Overview of Chrome Extension Components
&lt;/h4&gt;

&lt;p&gt;Before we jump into the setup, let’s briefly understand some key components of a Chrome extension:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Popup :&lt;/strong&gt; Manages the extension’s user interface but does not have direct access to the DOM of the parent web page.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Content Script&lt;/strong&gt;: Has direct access to the DOM of the parent web page but runs in a separate execution context. This separation means it cannot directly access JavaScript objects of the parent page.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Injected Script :&lt;/strong&gt; Shares the same execution context as the parent web page, allowing access to its DOM and JavaScript objects.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Background Script&lt;/strong&gt; : Operates in an isolated context, with no direct access to the DOM or JavaScript objects of the parent page.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Popup&lt;/strong&gt;, &lt;strong&gt;Content&lt;/strong&gt;, and &lt;strong&gt;Background&lt;/strong&gt; scripts operate in the extension’s contexts, whereas the &lt;strong&gt;Injected&lt;/strong&gt; script runs in the context of the parent web page. The parent page refers to the active web page on which the extension performs its functionality. Permissions and access to these pages are defined in the &lt;code&gt;manifest.json&lt;/code&gt; file, which we’ll cover later in this blog.&lt;/p&gt;
&lt;h4&gt;
  
  
  Prerequisites
&lt;/h4&gt;

&lt;p&gt;To follow along with this tutorial, ensure you have the following tools installed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Node.js (v18.16 LTS)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;NPM (Node Package Manager)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;TypeScript&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Webpack&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;VS Code Editor (or any code editor of your choice)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Setup
&lt;/h4&gt;

&lt;p&gt;Every Chrome extension requires a file named &lt;code&gt;manifest.json&lt;/code&gt; at the root level of the project. This file serves as the configuration blueprint for your extension, containing essential details about the project. While there’s no strict rule about the overall project structure, we’ll begin by creating this file and progressively build the project as outlined in this blog.&lt;/p&gt;

&lt;p&gt;Before proceeding, ensure that all prerequisites are installed on your machine.&lt;/p&gt;

&lt;p&gt;Follow the steps below to set up your project and its dependencies:&lt;/p&gt;

&lt;p&gt;Create a directory for your project and go inside that directory. This will be the root of your project. Everything from here on will be based of the root of your project unless stated otherwise.&lt;br&gt;&lt;br&gt;
&lt;code&gt;mkdir chrome-extension &amp;amp;&amp;amp; cd ./chrome-extension&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Create a file called as &lt;code&gt;manifest.json&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"manifest_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"My First Chrome App"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A Chrome extension built with TypeScript using webpack."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"default_popup"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"popup.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"default_icon"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"icon.png"&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most of the content in the &lt;code&gt;manifest.json&lt;/code&gt; is self explanatory except &lt;code&gt;action&lt;/code&gt; object. The &lt;code&gt;default_icon&lt;/code&gt; is the icon of your app that will appear next to your app’s name in Chrome and &lt;code&gt;default_popup&lt;/code&gt; specifies the HTML file to display as a popup when the extension’s icon is clicked.&lt;/p&gt;

&lt;p&gt;Create a file called as &lt;code&gt;popup.html&lt;/code&gt; with the following content,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;First Chrome Extension&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;This is my app popup&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Include an image file named &lt;code&gt;icon.png&lt;/code&gt; in the root directory. This will serve as your app’s icon, displayed in the Chrome toolbar. Make sure the image is in a supported format (e.g., PNG) and appropriately sized.&lt;/p&gt;

&lt;h4&gt;
  
  
  Quick Test with bare minimum setup
&lt;/h4&gt;

&lt;p&gt;Before diving into more complex functionality, let’s test this basic extension to ensure everything is set up correctly. This initial test will serve as the foundation for our development process and confirm that changes we make later are working as expected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open Chrome’s Extension Management&lt;/strong&gt; : Open Chrome and navigate to the &lt;code&gt;Manage Extensions&lt;/code&gt; page by typing &lt;code&gt;chrome://extensions/&lt;/code&gt; in the address bar. This will take you to the &lt;code&gt;Extensions&lt;/code&gt; screen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enable Developer Mode&lt;/strong&gt; : Locate the &lt;code&gt;Developer mode&lt;/code&gt; toggle at the top right of the screen and switch it on if it’s not already enabled. Enabling this mode allows Chrome to load extensions built locally in addition to those downloaded from the Chrome Web Store.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Load Your Extension&lt;/strong&gt; : Click the &lt;code&gt;Load unpacked&lt;/code&gt; button at the top of the page. Browse to and select the root directory of your project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verify Installation :&lt;/strong&gt; Once loaded, your extension should appear in the list of installed extensions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Render the Popup :&lt;/strong&gt; Click on the &lt;code&gt;My First Chrome App&lt;/code&gt; icon in the top-right corner of Chrome, just above the &lt;code&gt;Manage Extensions&lt;/code&gt; button. This action should render the &lt;code&gt;popup.html&lt;/code&gt; file, displaying the content you defined earlier.&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%2Fav1hgmxch992d47yd1hi.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%2Fav1hgmxch992d47yd1hi.png" alt="Screenshot of the installed app" width="800" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have managed to get it to work then our first test was successful and we can build on this. If not then please read through the above content carefully to make sure that you have not missed any step along the way.&lt;/p&gt;

&lt;p&gt;The next step is to create a &lt;code&gt;package.json&lt;/code&gt; file for managing project dependencies. Run the following command:&lt;br&gt;&lt;br&gt;
&lt;code&gt;npm init -y&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This command initializes a &lt;code&gt;package.json&lt;/code&gt; file with default values. If you’d like to customize it, omit the &lt;code&gt;-y&lt;/code&gt; flag and answer the prompts interactively.&lt;/p&gt;

&lt;p&gt;A default &lt;code&gt;package.json&lt;/code&gt; file will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chrome-extension"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"index.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"echo &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Error: no test specified&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &amp;amp;&amp;amp; exit 1"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"keywords"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"license"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ISC"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can install all the required dependencies into the project. You can use the following commands to do so.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;npm install -D typescript&lt;/code&gt; — To install TypeScript and add it to the &lt;code&gt;devDependencies&lt;/code&gt; section of &lt;code&gt;package.json&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;npm install -D&lt;/code&gt; &lt;a href="http://twitter.com/types/chrome" rel="noopener noreferrer"&gt;&lt;code&gt;@types/chrome&lt;/code&gt;&lt;/a&gt; — To install types for Chrome and add it to the &lt;code&gt;devDependencies&lt;/code&gt; section of &lt;code&gt;package.json&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;npm install -D webpack&lt;/code&gt; — To install webpack into the project and add it to the &lt;code&gt;devDependencies&lt;/code&gt; section of &lt;code&gt;package.json&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;npm install -D webpack-cli&lt;/code&gt; — This is required because we will be doing a hot reload whenever we make a change to the codebase&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;npm i -D copy-webpack-plugin&lt;/code&gt; — This is required to copy any static assets into the output directory or the &lt;code&gt;dist&lt;/code&gt; directory&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;npm i -D path&lt;/code&gt; — This is required to resolve the path of the static assets in the webpack configurations later on&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;npm i -D&lt;/code&gt; &lt;a href="http://twitter.com/babel/core" rel="noopener noreferrer"&gt;&lt;code&gt;@babel/core&lt;/code&gt;&lt;/a&gt; &lt;a href="http://twitter.com/babel/preset-env" rel="noopener noreferrer"&gt;&lt;code&gt;@babel/preset-env&lt;/code&gt;&lt;/a&gt; &lt;code&gt;babel-loader ts-loader&lt;/code&gt; — This is required to compile the code during the webpack build process&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the next test we will have to make this app work with typescript and webpack. We already have the required dependencies installed. Now we will have to create some configuration files and write some code to get this to work.&lt;/p&gt;

&lt;p&gt;We are going to create two configuration files, one for TypeScript and other for webpack. Create a file called as &lt;code&gt;tsconfig.json&lt;/code&gt; with following content.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ES6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"commonjs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"strict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"esModuleInterop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"skipLibCheck"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"forceConsistentCasingInFileNames"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"outDir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"rootDir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./src"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"include"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/**/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above is for TypeScript compiler to correctly identify the locations of &lt;code&gt;.ts&lt;/code&gt; files and compile them properly. Per above configurations, the &lt;code&gt;.ts&lt;/code&gt; files are expected to be present under the &lt;code&gt;src&lt;/code&gt; directory or its sub-directories. and the transpiled &lt;code&gt;.js&lt;/code&gt; files will be created in the &lt;code&gt;dist&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;Now create &lt;code&gt;webpack.config.cjs&lt;/code&gt; file which will have the configurations required for building and generating the compiled files along with other static assets ex. images&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;// [webpack.config.cjs]&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&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="s2"&gt;path&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;webpack&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="s2"&gt;webpack&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;CopyWebpackPlugin&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="s2"&gt;copy-webpack-plugin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;development&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Use 'production' for production builds&lt;/span&gt;
  &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;web&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;devtool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;source-map&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;popup&lt;/span&gt;&lt;span class="dl"&gt;"&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="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;src/scripts/popup.ts&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;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[name].js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Output file name for each entry&lt;/span&gt;
    &lt;span class="na"&gt;path&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="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dist&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// Output directory&lt;/span&gt;
    &lt;span class="na"&gt;clean&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Clean the output directory before each build&lt;/span&gt;
    &lt;span class="na"&gt;libraryTarget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;umd&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Universal Module Definition&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.ts&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="s2"&gt;.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// Resolve .ts and .js extensions&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="na"&gt;module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;rules&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;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;ts$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;exclude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/node_modules/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;use&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;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;babel-loader&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;presets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@babel/preset-env&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="p"&gt;},&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ts-loader&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="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;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CopyWebpackPlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;patterns&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;from&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="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;popup-style.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// Source directory&lt;/span&gt;
          &lt;span class="na"&gt;to&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="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dist&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// Destination directory&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;from&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="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content-style.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// Source directory&lt;/span&gt;
          &lt;span class="na"&gt;to&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="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dist&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// Destination directory&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
     &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;from&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="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;icon.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// Source directory&lt;/span&gt;
          &lt;span class="na"&gt;to&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="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dist&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// Destination directory&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;from&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="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;popup.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// Source directory&lt;/span&gt;
          &lt;span class="na"&gt;to&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="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dist&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// Destination directory&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;from&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="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;manifest.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// Source directory&lt;/span&gt;
          &lt;span class="na"&gt;to&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="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dist&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// Destination directory&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;Note: &lt;code&gt;CopyWebpackPlugin&lt;/code&gt; copies the static assets from the source code to the &lt;code&gt;dist&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;Now create the required directories using the following command:&lt;br&gt;&lt;br&gt;
&lt;code&gt;mkdir src/scripts -p&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Inside the &lt;code&gt;script&lt;/code&gt; directory, create a simple Typescript file with the following content,&lt;br&gt;&lt;br&gt;
&lt;code&gt;src/scripts/popup.ts&lt;/code&gt;&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="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This is a console statement from the popup script&lt;/span&gt;&lt;span class="dl"&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;message&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 above code will print the message to the console whenever the popup is rendered. We also have to include the link of this &lt;code&gt;popup.ts&lt;/code&gt; file (which will be compiled to &lt;code&gt;popup.js&lt;/code&gt; file) in &lt;code&gt;popup.html&lt;/code&gt;. Also, create files called &lt;code&gt;popup-style.css&lt;/code&gt; and &lt;code&gt;content-style.css&lt;/code&gt;. We can later use these files for styling the popup and other pages. So lets do that,&lt;/p&gt;

&lt;p&gt;Include the links to &lt;code&gt;popup-style.css&lt;/code&gt; and &lt;code&gt;popup.js&lt;/code&gt; in &lt;code&gt;popup.html&lt;/code&gt; that we created earlier&lt;br&gt;&lt;br&gt;
&lt;code&gt;popup.html&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;....
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"popup-style.css"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
....
&lt;span class="nt"&gt;&amp;lt;script&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"popup.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
....
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;popup-style.css&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#d1bba2&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;Create &lt;code&gt;content-style.css&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* [content-style.css] */&lt;/span&gt;
&lt;span class="c"&gt;/* just as placeholder */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now its time to add the webpack related command to the &lt;code&gt;package.json&lt;/code&gt; file so that we can build the extension.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="err"&gt;....&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"script"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"webpack --watch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;....&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="err"&gt;....&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the following command to start the build process:&lt;br&gt;&lt;br&gt;
&lt;code&gt;npm run build&lt;/code&gt;&lt;br&gt;&lt;br&gt;
This command watches for changes in your files and rebuilds the project automatically.&lt;/p&gt;
&lt;h4&gt;
  
  
  Quick Test with bare minimum setup
&lt;/h4&gt;

&lt;p&gt;We can do another test now to check if our webpack related configurations are working as expected. But before you try to test, delete the old extension from the first test where you had uploaded the projects root directory to Chrome. Now this time we will have to upload the &lt;code&gt;dist&lt;/code&gt; directory as the compiled code will be in this directory and not the project’s root directory. After you upload the newer version of the extension and render the &lt;code&gt;popup&lt;/code&gt;, right click on the popup and open the dev console and check if you can see the console statement from the &lt;code&gt;popup.ts&lt;/code&gt;. The statement should be present, if not , please check for any mistakes done in the previous steps.&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%2F4c7c4yrnhoud6p0kk3j9.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%2F4c7c4yrnhoud6p0kk3j9.png" alt="Screenshot of the successful run for popup scripts" width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Enhancing the setup
&lt;/h4&gt;

&lt;p&gt;At this point, we have a basic version of the extension which is functional but there are some components which we have to add so that we can do the local development with ease.&lt;/p&gt;

&lt;p&gt;Until now we have seen &lt;code&gt;popup&lt;/code&gt; component. Now its time to add the other components. Create three files namely &lt;code&gt;injected.ts&lt;/code&gt;,&lt;code&gt;content.ts\&lt;/code&gt; and &lt;code&gt;background.ts&lt;/code&gt; in the &lt;code&gt;src/scripts&lt;/code&gt; directory with the following content,&lt;/p&gt;

&lt;p&gt;&lt;code&gt;src/scripts/injected.ts&lt;/code&gt;&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="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This is a console statement from the injected script&lt;/span&gt;&lt;span class="dl"&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;message&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;&lt;code&gt;injected.[ts|js]&lt;/code&gt; is a special file which has access to the Dom as well as the JavaScript object exposed by the parent site. We need to add this file to the host site dynamically using the content script.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;content.ts&lt;/code&gt; file:&lt;br&gt;&lt;br&gt;
&lt;code&gt;src/scripts/content.ts&lt;/code&gt;&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="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This is a console statement from the content script&lt;/span&gt;&lt;span class="dl"&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;message&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;script&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;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;injected.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Load the script from the extension&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;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Clean up once the script is loaded&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;Create &lt;code&gt;background.ts&lt;/code&gt; file:&lt;br&gt;&lt;br&gt;
&lt;code&gt;src/scripts/background.ts&lt;/code&gt;&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="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This is a console statement from the background script&lt;/span&gt;&lt;span class="dl"&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;message&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;Now we will have to update the &lt;code&gt;webpack.config.cjs&lt;/code&gt; file to add these three entries as the entry points.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;webpack.config.cjs&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;.....&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;.....&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;entry:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;.......&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;path.resolve(&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"src/scripts/content.ts"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"background"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;path.resolve(&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"src/scripts/background.ts"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"injected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;path.resolve(&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"src/scripts/injected.ts"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;.....&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;......&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a last step, we will have to update the &lt;code&gt;manifest.json&lt;/code&gt; file to include all these configurations so that Chrome’s environment can detect them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;manifest.json&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"background"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"service_worker"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"background.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"module"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"content_scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"matches"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"https://*.google.com/*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"css"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"popup-style.css"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"run_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"document_start"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"matches"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"https://*.google.com/*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"js"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"content.js"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"run_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"document_end"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"scripting"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"host_permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="s2"&gt;"https://*.google.com/*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"web_accessible_resources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"resources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"injected.js"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"matches"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"https://*.google.com/*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;....&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;manifest.json&lt;/code&gt; above we configured the extension to work with &lt;a href="http://google.com" rel="noopener noreferrer"&gt;&lt;code&gt;google.com&lt;/code&gt;&lt;/a&gt; meaning this extension will execute its logic only when &lt;a href="http://google.com" rel="noopener noreferrer"&gt;&lt;code&gt;google.com&lt;/code&gt;&lt;/a&gt; is loaded in the browser. You can change this to any other website of your choice.&lt;/p&gt;

&lt;h4&gt;
  
  
  Quick Test with Enhanced setup
&lt;/h4&gt;

&lt;p&gt;We can do the final testing now with the enhanced setup. Before proceeding, please make sure that you reinstall the app in chrome so that you avoid the issues due to mismatch with older version of the code. To see if all the components are working properly, check the different &lt;code&gt;console.log&lt;/code&gt; statements. Remember that we have put &lt;code&gt;console.log&lt;/code&gt; statements in four different files: &lt;code&gt;injected.ts&lt;/code&gt;, &lt;code&gt;background.ts&lt;/code&gt;, &lt;code&gt;content.ts&lt;/code&gt; and &lt;code&gt;popup.ts&lt;/code&gt;. There should be four messages logged in the console. So, here are the steps,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Stop the &lt;code&gt;npm run build&lt;/code&gt; command in the terminal if it was running and start it again so that it picks up the new files that we just created&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Remove and Install the app again&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open the popup from the extension settings screen, right click on that popup and open the developer console. You should see the messages coming from &lt;code&gt;background script&lt;/code&gt; and &lt;code&gt;popup script&lt;/code&gt; in this console&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open &lt;a href="https://www.google.com%60" rel="noopener noreferrer"&gt;&lt;code&gt;https://www.google.com&lt;/code&gt;&lt;/a&gt; in the browser in another tab&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Right click on the opened google site and open developer console. You should be able to see the messages coming from &lt;code&gt;content script&lt;/code&gt; and &lt;code&gt;injected script&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&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%2Fuu2rd0armig6m5gvz9f4.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%2Fuu2rd0armig6m5gvz9f4.png" alt="Screenshot of the successful run for background and popup scripts" width="800" height="418"&gt;&lt;/a&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgjjpu7t1mkvqou3qk63f.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%2Fgjjpu7t1mkvqou3qk63f.png" alt="Screenshot of the successful run for content and injected scripts" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Congratulations on successfully setting up and running your first Chrome extension! If you encounter any issues, please double-check the steps you’ve followed to ensure everything is in place. Additionally, a link to the GitHub repository is provided at the end of this blog for your reference.&lt;/p&gt;

&lt;h4&gt;
  
  
  Key Takeaways
&lt;/h4&gt;

&lt;p&gt;In this blog, we have learned how to set up a local development environment for Chrome extension development. Here are a few key takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We explored the main components involved in Chrome extension development.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We learned how to set up a development environment with TypeScript and Webpack.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We also covered how to set up and test the extension in Chrome.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Glimpse Ahead
&lt;/h4&gt;

&lt;p&gt;I’m currently working on another blog where we’ll explore a simple use case for the Chrome extension, showcasing how all the components of the Chrome development environment we discussed in this blog come together to create a functional extension.&lt;/p&gt;

&lt;p&gt;Thank you for taking the time to read this blog! Your interest and time are greatly appreciated. I’m excited to share more as I continue this journey. Happy coding!&lt;/p&gt;

&lt;p&gt;GitHub link — &lt;a href="https://github.com/gauravnadkarni/chrome-extension-starter-ts" rel="noopener noreferrer"&gt;https://github.com/gauravnadkarni/chrome-extension-starter-ts&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This article was originally published on &lt;a href="https://medium.com/@nadkarnigaurav/step-into-chrome-extension-development-easy-setup-with-typescript-and-webpack-01423770b02e" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>extensions</category>
      <category>browser</category>
      <category>javascript</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
