Vue Browser Extensions with Parcel


Vue CLI offers a great workflow for most web apps. Run a command, choose which plugins to enable, and you’re off to the races. But if you want something off the beaten path it is a little more difficult. I’ve found that setting up webpack takes longer than I’m willing to spend for a quick project. Instead, I use Parcel, which requires (almost) zero config.

Today we’ll walk through how to use Vue (or React, or Typescript, or anything else that needs a build step) in a Browser Extension. Our browser extension will work in both Chrome and Firefox (using the webextension-polyfill project).

The hard part, as it should be, is deciding what to build. We’ll punt on that one, and just make a widget that shows a color depending on the date.

To get started, initialize your project and install a couple dependencies.

npm init -y # set up a package.json, accepting all default
# (drop the '-y' if you want to choose a name, license, etc, by hand)
npm install --save-dev parcel parcel-plugin-web-extension

Next, borrow a couple of scripts we’ll need for building the release: scripts/remove-evals.js and scripts/build-zip.js. I originally got these from another boilerplate, but made some changes to remove dependencies. It looks like remove-evals.js is used because chrome extensions ship with a content-security-policy that disallows eval. build-zip.js is used to package up a production build of your extension.

Next, we’ll make a manifest file, describing the extension’s entry points. Our extension is fairly simple with only a popup, but yours might have content scripts, a background script, or an options page.

{
  // src/manifest.json
  "browser_action": {
    "default_popup": "./popup.html"
  },
  "description": "Color of the Day",
  "manifest_version": 2,
  "name": "color-of-the-day",
  "permissions": [],
  "version": "1.0.0",
  "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"
}

Next, make a small file matching your default_popup’s name: popup.html. This will be used as a parcel entry point, so parcel will bundle any resources it finds in this file.

<!-- src/popup.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
  </head>

  <body>
    <div id="app"></div>
    <script src="./popup.js"></script>
  </body>
</html>

When parcel sees <script src="./popup.js"></script>, it will visit that file to see what other code needs to be bundled. That will be our Vue entry point.

// src/popup.js
import Vue from "vue";
import App from "./App.vue";

new Vue({
  el: "#app",
  render: (h) => h(App),
});

Similarly, parcel will now pull in both vue and App.vue.

<template>
  <div
    class="color-of-the-day"
    :style="{ 'background-color': backgroundColor }"
  ></div>
</template>
<script>
export default {
  computed: {
    hue() {
      return Math.floor(Math.random() * 360);
    },
    backgroundColor() {
      return `hsl(${this.hue}, 100%, 50%)`;
    },
  },
};
</script>
<style>
.color-of-the-day {
  width: 200px;
  height: 200px;
}
</style>

Finally, we’ll need to add a few items to the scripts section of our package.json:

{ // package.json
  // ...
  "scripts": {
    "build": "npm run pack && npm run remove-evals && npm run zip",
    "dev": "parcel watch src/manifest.json --out-dir dist --no-hmr",
    "pack": "parcel build src/manifest.json --out-dir dist",
    "remove-evals": "node scripts/remove-evals.js",
    "zip": "node scripts/build-zip.js"
  }
  // ...
}

Now run npm run dev to build your extension. The first time you run this, parcel will notice you’re working with Vue and download some packages you need (eg, vue, vue-template-compiler). Once it says “Built in 14.03s”, or however long, you can load the extension in your browser.

Visit chrome://extensions or about:debugging#/runtime/this-firefox, depending on your browser. You may need to turn on developer mode if this is the first time you’ve loaded an extension from a file on this computer. Load the dist folder (chrome) or the manifest.json file (firefox), and your extension is ready!

All the code from this post is available at https://github.com/bgschiller/vue-parcel-web-extension

More goodies

Parcel is smart enough to handle almost any web technology. If you want to use Typescript, just change popup.js to popup.ts and make a tsconfig.json file. Want to use scss? Add lang="scss" to the <style> tag in your Vue component. Reportedly, it can even handle Rust code, though I haven’t tried it.