<?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: Ivan N</title>
    <description>The latest articles on DEV Community by Ivan N (@ivan_862363c9a8b0).</description>
    <link>https://dev.to/ivan_862363c9a8b0</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%2F2398760%2Facfab55e-68b6-4e5f-8776-c8ff73f0f945.png</url>
      <title>DEV Community: Ivan N</title>
      <link>https://dev.to/ivan_862363c9a8b0</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ivan_862363c9a8b0"/>
    <language>en</language>
    <item>
      <title>Tab Roulette - my first extension</title>
      <dc:creator>Ivan N</dc:creator>
      <pubDate>Mon, 02 Dec 2024 12:12:52 +0000</pubDate>
      <link>https://dev.to/ivan_862363c9a8b0/tab-roulette-my-first-extension-1oih</link>
      <guid>https://dev.to/ivan_862363c9a8b0/tab-roulette-my-first-extension-1oih</guid>
      <description>&lt;p&gt;My current goal is to create a simple Chrome extension that utilizes the background capabilities of the extension framework.&lt;/p&gt;

&lt;p&gt;To recap, the background script operates as a service worker, primarily designed to handle tasks that do not require direct user interaction. &lt;/p&gt;

&lt;p&gt;One of its key roles is to act as a communication hub or event handler, serving as the only persistent and reliable component in the browser extension architecture. Unlike content scripts, popups, or options pages, which are ephemeral, the background service worker ensures continuity by automatically restarting when terminated to handle incoming events.&lt;/p&gt;

&lt;p&gt;I plan to leverage this capability of the background script as the central controller for my extension.&lt;/p&gt;

&lt;h2&gt;
  
  
  The use case
&lt;/h2&gt;

&lt;p&gt;This first Chrome extension will be quite straightforward. It will listen for clicks on the extension's action icon and respond by triggering a roulette-like behavior. The roulette will sequentially activate the tabs currently open in the user's browser for a short period until one tab is randomly left selected. &lt;/p&gt;

&lt;p&gt;As you can see, this extension doesn't serve a practical purpose but is intended purely as a learning exercise.&lt;/p&gt;

&lt;h2&gt;
  
  
  The manifest
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "name": "TabRoulette",
  "version": "0.0.1",
  "manifest_version": 3,
  "icons": {
    "16": "images/icon16.png",
    "32": "images/icon32.png",
    "48": "images/icon32.png",
    "128": "images/icon128.png"
  },
  "action": {
    "default_title": "Click to start",
    "default_icon": {
      "16": "images/icon16.png",
      "24": "images/icon24.png",
      "32": "images/icon32.png"
    }
  },
  "background": {
    "service_worker": "background.js"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In addition to the icons specified in the manifest for use in the Chrome Web Store and extension management interface, the most significant addition is the &lt;code&gt;action&lt;/code&gt; attribute. This attribute configures the behavior of the extension when the toolbar icon is clicked. In our case, it instructs the service worker to initiate a tab roulette upon user interaction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;To take into account&lt;/strong&gt;&lt;br&gt;
My code uses ES imports, but as shown earlier, the &lt;code&gt;service_worker&lt;/code&gt; was not explicitly declared as a module. How did it still work?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"background": {
  "service_worker": "service-worker.js",
  "type": "module"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These imports are handled and resolved by Vite during the bundling process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;As mentioned earlier, the background script will listen for clicks on the action icon and initiate a tab roulette in response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chrome.action.onClicked.addListener(async () =&amp;gt; {
 ...
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key aspects of the listener logic to highlight: First, I need to gather all the tabs currently open in the active window. This is essential because my code requires references to these tabs to cycle through them sequentially.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const currentWindow = await chrome.windows.getCurrent();
const windowTabs = await chrome.tabs.query({
  windowId: currentWindow.id,
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I initially got confused when using &lt;code&gt;chrome.tabs.query&lt;/code&gt; without specifying a &lt;code&gt;windowId&lt;/code&gt;, as it returned all the tabs across all open browser windows, whereas I only wanted the tabs from the active window. This led to unexpected results due to the larger number of tabs in the list.&lt;/p&gt;

&lt;p&gt;Once I understood this behavior, activating the tabs sequentially became straightforward. It simply involves updating the tab properties to set each one as active in sequence until a random tab is ultimately selected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chrome.tabs.update(nextTab.id!, { active: true });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another goal I wanted to achieve was to adjust the pace at which the tabs are activated—starting quickly and slowing down toward the end. To accomplish this, the native &lt;code&gt;setInterval&lt;/code&gt; function I used in the initial test was insufficient. Instead, I implemented a small utility that allowed me to create an adjustable interval, providing a way to dynamically modify its timing as needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function startInterval(callback: () =&amp;gt; void, interval: number) {
  let intervalId = setInterval(callback, interval);
  return {
    stop: () =&amp;gt; clearInterval(intervalId),
    changeInterval: (newInterval: number) =&amp;gt; {
      clearInterval(intervalId);
      intervalId = setInterval(callback, newInterval);
    },
  };
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that’s it—a simple extension with a playful use case, serving as an excuse to delve deeper into the world of browser extensions. I'm also sharing the source code with you if you're curious about the details.&lt;/p&gt;

&lt;p&gt;Oh, and I also used this project to explore the publishing process, which I found to be quite straightforward. Now, I'm just waiting for the review and final publication.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/ivaneffable/tabRoulette" rel="noopener noreferrer"&gt;https://github.com/ivaneffable/tabRoulette&lt;/a&gt;&lt;/p&gt;

</description>
      <category>extensions</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Create a CLI to scaffold browser extensions</title>
      <dc:creator>Ivan N</dc:creator>
      <pubDate>Wed, 20 Nov 2024 13:44:20 +0000</pubDate>
      <link>https://dev.to/ivan_862363c9a8b0/create-a-cli-to-scaffold-extensions-145</link>
      <guid>https://dev.to/ivan_862363c9a8b0/create-a-cli-to-scaffold-extensions-145</guid>
      <description>&lt;p&gt;In our &lt;a href="https://dev.to/ivan_862363c9a8b0/create-browser-extension-with-vite-ts-3d61"&gt;previous exercise&lt;/a&gt;, we built a browser extension using TypeScript. This involved a series of steps, including creating a Vite project and customizing it to meet the specific requirements of browser extensions. While the process wasn’t particularly lengthy or complex, we can simplify it further by automating it with a Node CLI (Command Line Interface). If you're new to CLIs, let me walk you through the one I've created!&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a Node project
&lt;/h2&gt;

&lt;p&gt;Naturally, the first step is to initialize and set up our Node project. Use the following commands to create a folder for our code and generate a basic &lt;code&gt;package.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;mkdir create-browser-extension-vite &amp;amp;&amp;amp; cd create-browser-extension-vite
npm init --yes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, I decided to modify the generated &lt;code&gt;package.json&lt;/code&gt; to include &lt;code&gt;"type": "module"&lt;/code&gt;. With this we’ll inform Node to interpret &lt;code&gt;.js&lt;/code&gt; files in the project as ES modules rather than CommonJS modules. Here's the updated &lt;code&gt;package.json&lt;/code&gt; after making some adjustments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "name": "create-browser-extension-vite",
  "version": "1.0.0",
  "scripts": {
    "test": "echo \"Error: no test specified\" &amp;amp;&amp;amp; exit 1"
  },
  "publishConfig": {
    "access": "public"
  },
  "keywords": [
      "cli",
    "create-project"
  ],
  "author": "",
  "license": "ISC",
  "description": "A CLI tool to create browser extensions with Vite",
  "type": "module"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  First steps
&lt;/h2&gt;

&lt;p&gt;Let’s begin by creating a file called &lt;strong&gt;create-project&lt;/strong&gt; in a new folder named &lt;strong&gt;bin&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;#!/usr/bin/env node

console.log("hello world");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file will act as the entry point for your command and to ensure it can be &lt;strong&gt;ran directly&lt;/strong&gt; on your computer once the package is installed globally, add the following field to the &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bin&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;bin/create-project&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it's time to test what we've built so far. First, we install the package locally by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm link
create-browser-extension-vite // execute the CLI 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once linked, you’ll have a new CLI command called &lt;code&gt;create-browser-extension-vite&lt;/code&gt;, which currently just prints “hello world” to the console. &lt;/p&gt;

&lt;p&gt;And that’s all it takes to create a basic CLI! From here, you can leverage the full power of the Node ecosystem to build anything you can imagine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling user input
&lt;/h2&gt;

&lt;p&gt;Let’s take another step toward our goal! The aim of this CLI is to generate a fully functional TypeScript browser extension with a single command. To accomplish this, the CLI will accept several optional parameters.&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%2Fc2dh9szkq7lkrp9sv5sd.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%2Fc2dh9szkq7lkrp9sv5sd.png" alt="Image description" width="467" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;name&lt;/strong&gt;: If provided, a folder with the specified name will be created. Otherwise, the current folder will contain the project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;git&lt;/strong&gt;: If specified, a Git repository will be initialized for the project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;install&lt;/strong&gt;: If specified, the project dependencies will be installed automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;yes&lt;/strong&gt;: Skips all prompts and generates the project with default settings.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first step is to create a new file, &lt;code&gt;src/cli.js&lt;/code&gt;, which will handle all the logic for collecting user preferences. This new module will be invoked from the current &lt;strong&gt;create-project&lt;/strong&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;#!/usr/bin/env node

import { cli } from "../src/cli.js";

cli(process.argv);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To streamline the process of gathering user preferences, we’ll use two useful libraries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install @inquirer/prompts arg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/arg" rel="noopener noreferrer"&gt;&lt;code&gt;arg&lt;/code&gt;&lt;/a&gt;: A powerful argument parser for handling command-line inputs.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/@inquirer/prompts" rel="noopener noreferrer"&gt;&lt;code&gt;@inquirer/prompts&lt;/code&gt;&lt;/a&gt;: A library for creating elegant and interactive command-line interfaces.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import arg from "arg";
import { confirm } from "@inquirer/prompts";

async function promptForMissingOptions(options) {
  if (options.skipPrompts) {
    return options;
  }

  return {
    ...options,
    git:
      options.git ||
      (await confirm({ message: "Initialize a git repository?" })),
  };
}

function parseArgumentsIntoOptions(rawArgs) {
  const args = arg(
    {
      "--git": Boolean,
      "--help": Boolean,
      "--yes": Boolean,
      "--install": Boolean,
      "-g": "--git",
      "-y": "--yes",
      "-i": "--install",
    },
    {
      argv: rawArgs.slice(2),
    }
  );

  return {
    skipPrompts: args["--yes"] || false,
    git: args["--git"] || false,
    runInstall: args["--install"] || false,
    projectName: args._[0],
  };
}

export async function cli(args) {
  let options = parseArgumentsIntoOptions(args);
  options = await promptForMissingOptions(options);
  console.log(options);
}

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

&lt;/div&gt;



&lt;p&gt;I’ll leave it to you to add an extra option for displaying a basic help message. This will involve introducing a new user preference controlled by the &lt;code&gt;--help&lt;/code&gt; or &lt;code&gt;-h&lt;/code&gt; parameter. If this parameter is provided, the CLI should display a simple manual explaining the command’s usage. You can refer to my solution in the repository linked below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the project
&lt;/h2&gt;

&lt;p&gt;In this step, the project will be created based on the preferences selected in the previous stage. We'll begin by creating a folder named &lt;code&gt;template&lt;/code&gt; and copying into it the files that will make up the generated project.&lt;/p&gt;

&lt;p&gt;The folder structure should look like this, and you can find the content of these files in my GitHub repository. If you’re curious about how they were created, check out my &lt;a href="https://dev.to/ivan_862363c9a8b0/create-browser-extension-with-vite-ts-3d61"&gt;previous post&lt;/a&gt;, where I discuss building a browser extension with TypeScript.&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%2F27530lohj2a9zwow4brg.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%2F27530lohj2a9zwow4brg.png" alt="Image description" width="183" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our code will utilize the files in the &lt;code&gt;template&lt;/code&gt; folder to generate the user's new browser extension and the following packages will be particularly useful in accomplishing this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;ncp chalk execa pkg-install listr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/ncp" rel="noopener noreferrer"&gt;&lt;code&gt;ncp&lt;/code&gt;&lt;/a&gt;: Facilitates recursive copying of files.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/chalk" rel="noopener noreferrer"&gt;&lt;code&gt;chalk&lt;/code&gt;&lt;/a&gt;: Adds terminal string styling.&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://npm.im/execa" rel="noopener noreferrer"&gt;&lt;code&gt;execa&lt;/code&gt;&lt;/a&gt;: Simplifies running external commands like &lt;code&gt;git&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://npm.im/pkg-install" rel="noopener noreferrer"&gt;&lt;code&gt;pkg-install&lt;/code&gt;&lt;/a&gt;: Automatically triggers either &lt;code&gt;yarn install&lt;/code&gt; or &lt;code&gt;npm install&lt;/code&gt; based on the user's preference.&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://npm.im/listr" rel="noopener noreferrer"&gt;&lt;code&gt;listr&lt;/code&gt;&lt;/a&gt;: Allows defining a list of tasks while providing a clean progress overview for the user.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll begin by creating a new file, &lt;code&gt;src/main.js&lt;/code&gt;, to contain the code that generates the project by copying the files from the &lt;code&gt;template&lt;/code&gt; folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { createProject } from "./main.js";

...

export async function cli(args) {
  let options = parseArgumentsIntoOptions(args);
  options = await promptForMissingOptions(options);
  await createProject(options);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import chalk from "chalk";
import ncp from "ncp";
import path from "path";
import { promisify } from "util";
import { execa } from "execa";
import Listr from "listr";
import { projectInstall } from "pkg-install";

const copy = promisify(ncp);

async function copyTemplateFiles(options) {
  return copy(options.templateDirectory, options.targetDirectory, {
    clobber: false,
  });
}

async function initGit(options) {
  const result = await execa("git", ["init"], {
    cwd: options.targetDirectory,
  });
  if (result.failed) {
    return Promise.reject(new Error("Failed to initialize git"));
  }
  return;
}

export async function createProject(options) {
  options = {
    ...options,
    targetDirectory: options.projectName || process.cwd(),
  };

  const currentFileUrl = import.meta.url;
  const templateDir = path.resolve(
    new URL(currentFileUrl).pathname,
    "../../template"
  );
  options.templateDirectory = templateDir;

  const tasks = new Listr([
    {
      title: "Copy project files",
      task: () =&amp;gt; copyTemplateFiles(options),
    },
    {
      title: "Initialize git",
      task: () =&amp;gt; initGit(options),
      enabled: () =&amp;gt; options.git,
    },
    {
      title: "Install dependencies",
      task: () =&amp;gt;
        projectInstall({
          cwd: options.targetDirectory,
        }),
      skip: () =&amp;gt;
        !options.runInstall
          ? "Pass --install to automatically install dependencies"
          : undefined,
    },
  ]);

  await tasks.run();
  console.log("%s Project ready", chalk.green.bold("DONE"));
  return true;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above utilizes &lt;code&gt;Listr&lt;/code&gt; to execute the series of actions needed to generate the new project, from copying files with &lt;code&gt;ncp&lt;/code&gt; to setting up the Git repository. Also note how we used &lt;code&gt;promisify&lt;/code&gt; to convert the callback-based copy method of &lt;code&gt;ncp&lt;/code&gt; into a promise-based function, making the code more readable and maintainable.&lt;/p&gt;

&lt;p&gt;And that’s it! These are the steps I followed to create my new CLI tool, the one I’ll be using to streamline the creation of my new browser extensions. You can use it too! Because I’ve published it on &lt;a href="https://www.npmjs.com/package/create-browser-extension-vite" rel="noopener noreferrer"&gt;npm&lt;/a&gt; for anyone to generate their own extensions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/ivaneffable/create-browser-extension-vite" rel="noopener noreferrer"&gt;https://github.com/ivaneffable/create-browser-extension-vite&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.twilio.com/en-us/blog/how-to-build-a-cli-with-node-js" rel="noopener noreferrer"&gt;How to build a CLI with Node.js&lt;/a&gt;&lt;/p&gt;

</description>
      <category>extensions</category>
      <category>javascript</category>
      <category>npm</category>
      <category>node</category>
    </item>
    <item>
      <title>Create Browser Extension with Vite + TS</title>
      <dc:creator>Ivan N</dc:creator>
      <pubDate>Sun, 10 Nov 2024 12:43:12 +0000</pubDate>
      <link>https://dev.to/ivan_862363c9a8b0/create-browser-extension-with-vite-ts-3d61</link>
      <guid>https://dev.to/ivan_862363c9a8b0/create-browser-extension-with-vite-ts-3d61</guid>
      <description>&lt;p&gt;Let's begin this exercise focusing on setting up the most basic extension structure, one that includes just a manifest file and a service worker. The manifest acts as the configuration file, specifying what the extension is allowed to do, while the service worker handles background tasks, primarily responding to browser events, in this instance, it logs when the extension is installed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "name": "Simple",
  "version": "0.0.1",
  "manifest_version": 3,
  "background": {
    "service_worker": "background.js"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;manifest.json&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;console.log("Initialized background script!");

chrome.runtime.onInstalled.addListener((object) =&amp;gt; {
  console.log("Installed background script!");
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;background.js&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The service worker can be as complex as needed, but we'll keep it straightforward for now as the goal of this task is to transform this simple two-file extension into a Vite project.&lt;/p&gt;

&lt;p&gt;So why use Vite? Primarily because I've decided to work with TypeScript, which will significantly improve code quality, maintainability, and productivity. Beyond offering self-documentation, TypeScript allows me to catch type-related errors at compile-time rather than at runtime, enhancing the overall development experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vite
&lt;/h2&gt;

&lt;p&gt;Although a Node project with TypeScript would suffice to compile my TS code into the vanilla JavaScript required by my extension, I opted for Vite. Vite, especially with its Rollup bundler, offers extensive capabilities like tree-shaking and bundle minification. Additionally, Vite’s robust plugin ecosystem would allow me to easily integrate React if I decide to create UI components for the extension in the future.&lt;/p&gt;

&lt;p&gt;With that in mind, let’s start by creating the Vite project using its vanilla-ts template.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm create vite@latest simple-extension -- --template vanilla-ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After running the command and installing dependencies, you’ll have a small web project set up with TypeScript. However, since this setup isn’t quite what we need, we’ll start with some cleanup. First, delete the &lt;code&gt;index.html&lt;/code&gt; file from the root folder, as it’s not required anymore. Then, remove all files in the &lt;code&gt;src&lt;/code&gt; folder except for &lt;code&gt;vite-env.d.ts&lt;/code&gt;, which provides type definitions for Vite-specific features. Finally, delete everything in the &lt;code&gt;public&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;⚠️ If you encounter errors in your tsconfig file about unknown compiler options, ensure that your editor is using the same TypeScript version installed for the project.&lt;/p&gt;

&lt;p&gt;Next, create a new file in the src folder named &lt;code&gt;background.ts&lt;/code&gt; and copy the code from the original &lt;code&gt;background.js&lt;/code&gt; file into it.&lt;br&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%2F2un456rwytqspndjlspd.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%2F2un456rwytqspndjlspd.png" alt="project files so far" width="181" height="241"&gt;&lt;/a&gt;&lt;br&gt;
You’ll soon notice that TypeScript requires additional context to function properly, as it doesn’t recognize the &lt;code&gt;chrome&lt;/code&gt; object without the appropriate types. To address this, install &lt;code&gt;@types/chrome&lt;/code&gt; to provide the necessary type definitions for chrome.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i -D @types/chrome
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once installed, the TypeScript errors disappear. However, you may notice a warning message in the &lt;code&gt;onInstalled&lt;/code&gt; callback. This is due to the linting properties that Vite has configured for us in our TypeScript setup.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;tsconfig.json&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Since we’re defining an &lt;code&gt;object&lt;/code&gt; that isn’t used, let’s go ahead and remove it. That should be all we need, so now we can compile the project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There seems to be an issue—the default Vite configuration is still attempting to use some of the files we just removed as the code entry point.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x Build failed in 6ms
error during build:
Could not resolve entry module "index.html".
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to customize our Vite configuration to run in &lt;a href="https://vite.dev/guide/build#library-mode" rel="noopener noreferrer"&gt;Library Mode&lt;/a&gt;. Here’s the minimal configuration required for this project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { defineConfig } from "vite";

// https://vitejs.dev/config/
export default defineConfig({
  build: {
    rollupOptions: {
      input: {
        background: "./src/background.ts",
      },
      output: {
        entryFileNames: `[name].js`,
      },
    },
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;vite.config.ts&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the configuration above, we’re simply setting the project’s input to &lt;code&gt;./src/background.ts&lt;/code&gt; and adjusting the output filename to &lt;code&gt;[name].js&lt;/code&gt;. By default, Vite appends a hash to filenames for production builds (e.g., &lt;code&gt;background-[hash].js&lt;/code&gt;), but we need an exact filename match for our &lt;code&gt;manifest.json&lt;/code&gt; to work correctly.&lt;/p&gt;

&lt;p&gt;Speaking of &lt;code&gt;manifest.json&lt;/code&gt;, where should it be located? This part is straightforward: any file that should be copied as-is, without transformations, to the output folder should be placed in the &lt;code&gt;public&lt;/code&gt; folder. Now, if we build the project again, we’ll find our two-file browser extension compiled in the &lt;code&gt;dist&lt;/code&gt; folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;Let’s now verify everything is working as expected. If you haven’t tested a browser extension locally before, just follow these simple steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the Extensions page by entering &lt;code&gt;chrome://extensions&lt;/code&gt; in a new tab.&lt;/li&gt;
&lt;li&gt;Enable Developer Mode by toggling the &lt;strong&gt;Developer mode&lt;/strong&gt; switch.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Load unpacked&lt;/strong&gt; and select the extension directory, which is the &lt;code&gt;dist&lt;/code&gt; folder generated by Vite.
&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%2Fzjb2qrtpyboua37xbons.png" alt="installed extension" width="411" height="212"&gt;
And that’s it! The extension is now installed. If you click on the service worker link, the DevTools for this service worker will open, where you can view the logs generated by our worker in action.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://github.com/ivaneffable/simple-vite-extension" rel="noopener noreferrer"&gt;simple-vite-extension&lt;/a&gt;&lt;/p&gt;

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