Creating a chrome extension with parcel

3 min read

This article is not a tutorial on how to build a chrome extension from scratch. Maybe you know how to make an extension with vanilla JS and now want to build it with your favorite frontend library. We are going to follow along the process I used to build Hippocampus.

My first attempt was using vanilla JS, quickly I missed the convenience of React when building simple form elements and reacting to changes in data.

My extension needed three "entries" (stand-alone files/apps) options.html, popup.html and content.js.

I gave parcel a go and it seemed like a perfect fit. I could run

parcel build src/content.tsx src/options.html src/popup.html

and it would generate the three files I needed. My extension was written in typescript and react, but parcel will support whatever flavor of JS you want to use.

Static Files

Chrome extensions need a manifest.json and some images for the extension's icon. I used a parcel plugin parcel-plugin-static-files-copy for these files.

yarn add parcel-plugin-static-files-copy

Then in /static directory I added the mainfest.json and png icons.

parceljs chrome extension

When running a parcel command all of these static assets are copied across to the dist directory.

HTML files

The files that a chrome extension requires to be an HTML file (popup.html, options.html) you will want to create a basic HTML file.

<!-- options.html -->
<!DOCTYPE html>
<html>
  <head>
    <title>Hippocampus Settings</title>
    <meta charset="utf-8" />
  </head>
  <body>
    <div id="app"></div>
    <script src="options.tsx"></script>
  </body>
</html>

and accompany it with your JS file, in this case, a typescript/react file, but once again use your own JS flavor.

// options.tsx
import * as React from "react"
import * as ReactDOM from "react-dom"

function App() {
  return (
    <form>
      <h1>Options</h1>
      {/* options goes here... */}
    </form>
  )
}

const mountNode = document.getElementById("app")
ReactDOM.render(<App />, mountNode)

JS files

This might be react and content.js specific. To add a react rendered element into the DOM of the current webpage I needed to first create an element, append it to the body and then render react on that appended element.

// content.tsx
import * as React from "react"
import * as ReactDOM from "react-dom"

function App() {
  return (
    <section style={{ position: "fixed" }}>
      <h1>Logo:</h1>
      <input type="checkbox" />
    </section>
  )
}

var newNode = document.createElement("div")
document.body.appendChild(newNode)
ReactDOM.render(<Content />, newNode)

I wanted to add a position fixed element to the page, instead of modifying or adding to an existing element. So this might not be exactly what you will need to with your app, good luck!

Building files for publishing

When ready to publish you will want to run

parcel build src/content.tsx src/options.html src/popup.html

which will create a dist directory with the built files.

Next, you just need to create .zip file of the dist directory and your chrome extension should be ready to publish to the chrome store.

When developing the chrome extension you can use the command

parcel src/content.tsx src/options.html src/popup.html

which will watch your files and build the dist directory. Make sure you use parcel build when publishing and not the dev mode.

Before landing on parcel as a solution I tried to also use "create react app", the pain point with this was that it is not easy to have multiple "entry files" which we have already seen parcel did with ease.

You can check out the source code of Hippocampus for more details into how everything came together for this extension.

Was this article helpful?

I'm writing an ebook called Robust UI. Subscribe to get chapters from it via email