Build a browser extension with Svelte
Tags: svelte, javascript, browserextension, chromeextension
Published: 2021-02-07 12:11:52


I have been learning *Svelte*. I find it extremely easy to work with. It is lightweight and fast, a suitable option for building small-scaled apps and websites. I've also been learning about how a *browser extension* is made. Then I find it a perfect playmate for Svelte, and the learning paths crossed. So today I would like to share my process of **building a complete cross-browser extension with Svelte**.

Before reading on, make sure that you're familiar with a browser extension and how to construct one with pure HTML/CSS/JS, and how to work with a front-end library/framework in a NodeJS environment. Otherwise, here are some resources to get you started:

* [Chrome Extension - Getting started](https://developer.chrome.com/docs/extensions/mv2/getstarted/) (Manifest v2)
* [Svelte - Getting started](https://svelte.dev/blog/the-easiest-way-to-get-started)
* [Build a cross-browser extension](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Build_a_cross_browser_extension)
* [Extensionizr](http://extensionizr.com/) - Chrome extension structure generator

# 1. Setup

## 1.1. Initiate a Svelte project

From the CLI, run `npx degit sveltejs/template my-extension`. This will make a copy of the [Svelte template](https://github.com/sveltejs/template) to your machine with the name `my-extension`, and a basic structure as seen in the repository.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/0qk48rw1f4at61vo91mb.png)

## 1.2. Install dependencies

Run `npm install` to install all necessary dependencies in the template's `package.json`.

To build a cross-browser extension, the [webextension-polyfill](https://github.com/mozilla/webextension-polyfill/) is required. Therefore, run `npm install --save-dev webextension-polyfill` to install it.

# 2. Prepare the Manifest

Now that we have things ready. Let's create the heart of a browser extension, the **`manifest.json`**.

Create `manifest.json` and place it inside the `public` folder. A bare minimum content of a manifest is:

```json
{
  "manifest_version": 2,
  "name": "My Extension",
  "version": "1.0.0",
}
```

And depending on the purpose and functionality of the extension, other fields may be specified. For a complete list of supported fields, refer to [Chrome's manifest file format](https://developer.chrome.com/docs/extensions/mv2/manifest/).

# 3. Add functionalities

Silly things that our extension will be capable of:

* Say hello when somebody installs our extension ([background page](https://developer.chrome.com/docs/extensions/mv2/background_pages/))
* "Hack" the website's background ([content script](https://developer.chrome.com/docs/extensions/mv2/content_scripts/))
* Configure the desired background ([popup page](https://developer.chrome.com/docs/extensions/reference/browserAction/#popup))

Now let's dig through.

## 3.1. Background page

The background page will be loaded whenever the extension is active, and react to the events we set. In our case, the extension reacts "say hello" to the event "somebody installs the extension".

Create `background.js` inside the `src` folder and add the following script:

```javascript
import browser from "webextension-polyfill";

browser.runtime.onInstalled.addListener(({ reason }) => {
  if (reason === "install") {
    alert("Hello");
  }
});
```

*More about the `runtime.onInstalled` event: [MDN](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onInstalled).*

## 3.2. Content script

The content script has direct access to the current web page. Therefore, it is a perfect solution to "hack the website's background".

Create `injection.js` inside the `src` folder and add the following script:

```javascript
import browser from "webextension-polyfill";

const key = "background";
browser.storage.local.get(key).then((data) => {
  document.body.style = `background: url(${data[key]})`;
});
```

*More about the `storage.local` API: [MDN](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/local).*

## 3.3. Popup page

The popup page is literally the front end of the extension, where users can interact with our extension. In our case, we give users the possibility to "configure the desired background". And this is where Svelte comes into the play.

Remove `props: { name: 'world' }` from `main.js`.

Replace content in `App.svelte` with the following script:

```html
<script>
  import browser from "webextension-polyfill";

  let image = "https://images.unsplash.com/photo-1586074299757-dc655f18518c?fit=crop&w=1268&q=80";

  function change() {
    browser.storage.local.set({ background: image });
  }
</script>

<main>
  Image URL: <input type="text" bind:value={image} />
  <button on:click={change}>Change</button>
</main>
```

## 3.4. Add to the Manifest

Now modify the `manifest.json` we created earlier to include these functionalities:

```json
{
  ...
  "permissions": ["storage"],
  "background": {
    "scripts": ["build/background.js"],
    "persistent": false
  },
  "browser_action": {
    "default_popup": "index.html"
  },
  "content_scripts": [
    {
      "matches": ["https://*/*"],
      "js": ["build/injection.js"]
    }
  ]
}
```

# 4. Config Rollup and build

The final step involves some configuration for Rollup to generate the `background.js` and `injection.js` to the `build` folder, so that the Manifest could recognize.

By default, the `rollup.config.js` file only outputs the `main.js` entry to the `build` folder under the alias `bundle.js`. To include the other scripts, modify the config as follows:

```javascript
export default [
  {
    input: "src/main.js",
    ...
  },
  {
    input: "src/background.js",
    output: {
      sourcemap: true,
      format: "iife",
      file: "public/build/background.js",
    },
    plugins: [resolve(), commonjs()],
    watch: {
      clearScreen: false,
    },
  },
  {
    input: "src/injection.js",
    output: {
      sourcemap: true,
      format: "iife",
      file: "public/build/injection.js",
    },
    plugins: [resolve(), commonjs()],
    watch: {
      clearScreen: false,
    },
  },
]
```

This will output multiple entries, including the `background.js` and `injection.js` to the `build` folder, and resolve any dependencies in our scripts.

And finally, run `npm run build` to compile our project and everything will be available in the `public` folder. We may then load our extension in Chrome and see the result:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/unardrhpmbqcq78zoevm.png)

*A complete repo you can find at: https://github.com/khang-nd/browser-extension-svelte*

Thank you for reading, and see you in the next post.