Chrome Extension Vue Setup — Developer Guide

6 min read

Setting Up Vue 3 for Chrome Extension Development

This guide covers building Chrome extensions with Vue 3, covering project initialization, architecture patterns, and best practices for modern extension development.

Project Initialization

Create a new Vue project using create-vue:

npm create vue@latest my-extension
cd my-extension
npm install

Install the Chrome extension Vite plugin:

npm install --save-dev vite-plugin-web-extension

Configure vite.config.ts:

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import webExtension from 'vite-plugin-web-extension';

export default defineConfig({
  plugins: [
    vue(),
    webExtension()
  ],
  build: {
    rollupOptions: {
      input: {
        popup: 'src/popup/index.html',
        options: 'src/options/index.html',
        sidepanel: 'src/sidepanel/index.html'
      }
    }
  }
});

Multiple Vue Entry Points

Chrome extensions have multiple entry points (popup, options page, side panel). Each should be a separate Vue app:

src/
├── popup/       # Browser action popup
│   ├── main.ts
│   └── Popup.vue
├── options/     # Extension options page
│   ├── main.ts
│   └── Options.vue
├── sidepanel/   # Side panel page
│   ├── main.ts
│   └── Sidepanel.vue
└── content/     # Content scripts
    └── content.ts

Composition API Composable Helpers

Create reusable composables for Chrome API integration:

// composables/useStorage.ts
import { ref, watch } from 'vue';

export function useStorage<T>(key: string, defaultValue: T) {
  const data = ref<T>(defaultValue);

  chrome.storage.local.get(key, (result) => {
    if (result[key]) data.value = result[key];
  });

  chrome.storage.onChanged.addListener((changes, area) => {
    if (area === 'local' && changes[key]) {
      data.value = changes[key].newValue;
    }
  });

  const update = (value: T) => {
    chrome.storage.local.set({ [key]: value });
    data.value = value;
  };

  return { data, update };
}

// composables/useMessage.ts
import { onMounted, onUnmounted } from 'vue';

export function useMessage(callback: (message: any) => void) {
  const listener = (message: any) => callback(message);

  onMounted(() => chrome.runtime.onMessage.addListener(listener));
  onUnmounted(() => chrome.runtime.onMessage.removeListener(listener));
}

// composables/useAlarm.ts
import { ref } from 'vue';

export function useAlarm(name: string) {
  const fired = ref(false);

  chrome.alarms.onAlarm.addListener((alarm) => {
    if (alarm.name === name) fired.value = true;
  });

  const create = (delayInMinutes: number) => {
    chrome.alarms.create(name, { delayInMinutes });
  };

  return { fired, create };
}

Pinia State Management

Use Pinia for state management across extension contexts:

// stores/extension.ts
import { defineStore } from 'pinia';

export const useExtensionStore = defineStore('extension', {
  state: () => ({
    settings: { theme: 'light', notifications: true },
    user: null
  }),
  actions: {
    updateSettings(settings: Partial<typeof this.settings>) {
      this.settings = { ...this.settings, ...settings };
      chrome.storage.local.set({ settings: this.settings });
    }
  }
});

Content Scripts with Vue

Mount Vue components in content scripts using shadow DOM for style isolation:

// content/main.ts
import { createApp } from 'vue';
import ContentApp from './ContentApp.vue';

const host = document.createElement('div');
host.id = 'my-extension-root';
document.body.appendChild(host);

const shadow = host.attachShadow({ mode: 'open' });
const mountPoint = document.createElement('div');
shadow.appendChild(mountPoint);

createApp(ContentApp).mount(mountPoint);

Vue DevTools

Vue DevTools works in extension pages. Open DevTools on popup, options, or side panel pages to inspect Vue component trees and state.

Routing

Use hash routing for extension pages since they run from chrome-extension://:

import { createRouter, createWebHashHistory } from 'vue-router';

const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    { path: '/', redirect: '/popup' },
    { path: '/popup', component: Popup },
    { path: '/options', component: Options }
  ]
});

Styling Options

Testing

Auto-Imports

Use unplugin-auto-import for automatic composable imports:

npm install -D unplugin-auto-import

Configure in vite.config.ts for Chrome API helpers.

Performance Optimization

Cross-References

Part of the Chrome Extension Guide by theluckystrike. Built at zovo.one.