vitesse-webext

repository·main·Indexed 25 days ago

https://github.com/antfu-collective/vitesse-webext

A Vite-powered starter template for building WebExtensions (Chrome, Firefox, etc.) using a modern Vue 3 stack. It comes pre-configured with webextension-polyfill, webext-bridge, UnoCSS, VueUse, and various unplugin tools for auto-imports and icons. The template includes built-in support for content scripts with Shadow DOM injection, background service workers, and E2E testing via Playwright.

Tokens
4.5K
Snippets
11
Records
23
Agent score
84%

What's inside vitesse-webext

  1. Project directory structure

    main

    Understanding the folder layout is essential for organizing your extension code:

    • src - The main source directory.
      • contentScript - Scripts and components to be injected as content_script.
      • background - Scripts for the background context.
      • components - Auto-imported Vue components shared in the popup and options page.
      • styles - Styles shared in the popup and options page.
      • assets - Assets used in Vue components.
      • manifest.ts - The extension manifest configuration.
    • extension - The extension package root.
      • assets - Static assets (primarily for manifest.json).
      • dist - Built files (also serves as the stub entry for Vite during development).
    • scripts - Helper scripts for development and bundling.
  2. Clone the WebExtension Vite Starter template

    main

    You can use this template to start a new WebExtension project. You can either use the GitHub template feature or clone it manually using degit to ensure a clean git history.

    If you do not have pnpm installed, install it globally first:

    npm install -g pnpm

    Then, clone and install the project:

    npx degit antfu/vitesse-webext my-webext
    cd my-webext
    pnpm i
  3. Develop your WebExtension

    main

    To start the development server, run the following command:

    pnpm dev

    After running the command, load the extension in your browser using the extension/ folder.

    For Firefox development, use:

    pnpm dev-firefox

    web-ext will automatically reload the extension when files in the extension/ folder change. While Vite handles HMR, using the Extensions Reloader Chrome extension is recommended for cleaner hard reloads.

  4. Build the WebExtension for production

    main

    To create a production build of your extension, run:

    pnpm build

    The built files will be located in the extension folder. You can pack these files into .crx (for Chrome) or .xpi (for Firefox) files to upload them to the respective extension stores.

  5. Configure Side Panel behavior in background script

    main

    To enable the side panel to open automatically when the extension's action button is clicked in Chromium-based browsers, set USE_SIDE_PANEL to true and use the browser.sidePanel.setPanelBehavior API. Note that this requires the browser.sidePanel API to be available.

    const USE_SIDE_PANEL = true
    
    if (USE_SIDE_PANEL) {
      // @ts-expect-error missing types
      browser.sidePanel
        .setPanelBehavior({ openPanelOnActionClick: true })
        .catch((error: unknown) => console.error(error))
    }
  6. Initialize the Options page

    main

    The options page is initialized by creating a Vue application instance from the Options.vue component and applying common application setup logic via setupApp. This ensures that the options page has access to the same core logic and plugins as the rest of the extension.

    import { createApp } from 'vue'
    import App from './Options.vue'
    import { setupApp } from '~/logic/common-setup'
    import '../styles'
    
    const app = createApp(App)
    setupApp(app)
    app.mount('#app')
  7. Implement a content script entrypoint

    main

    The content script entrypoint is used to inject logic and UI components into web pages. It typically performs three main tasks:

    1. Message Listening: Use onMessage from webext-bridge/content-script to listen for messages sent from background scripts or other parts of the extension.
    2. UI Injection via Shadow DOM: To prevent style bleeding from the host website, create a container element, attach a Shadow DOM, and inject the extension's styles using browser.runtime.getURL('dist/contentScripts/style.css').
    3. Vue App Mounting: Create a Vue application instance, run common setup logic via setupApp, and mount it to the Shadow DOM root.

    Note: When targeting Firefox, ensure the script returns a primitive value to satisfy browser.tabs.executeScript() requirements.

    import { onMessage } from 'webext-bridge/content-script'
    import { createApp } from 'vue'
    import App from './views/App.vue'
    import { setupApp } from '~/logic/common-setup'
    
    (() => {
      // 1. Listen for messages
      onMessage('tab-prev', ({ data }) => {
        console.log(`[vitesse-webext] Navigate from page "${data.title}"`)
      })
    
      // 2. Prepare Shadow DOM container
      const container = document.createElement('div')
      container.id = __NAME__ // Uses the build-time name
      const root = document.createElement('div')
      const styleEl = document.createElement('link')
      
      const shadowDOM = container.attachShadow?.({ mode: __DEV__ ? 'open' : 'closed' }) || container
      
      styleEl.setAttribute('rel', 'stylesheet')
      styleEl.setAttribute('href', browser.runtime.getURL('dist/contentScripts/style.css'))
      
      shadowDOM.appendChild(styleEl)
      shadowDOM.appendChild(root)
      document.body.appendChild(container)
    
      // 3. Mount Vue App
      const app = createApp(App)
      setupApp(app)
      app.mount(root)
    })()
  8. Initialize the Popup UI

    main

    The popup entrypoint initializes a Vue application using createApp, mounts it to the #app element, and applies common application logic via setupApp. This setup ensures that shared logic (such as state management or global plugins) is correctly configured for the popup context.

    import { createApp } from 'vue'
    import App from './Popup.vue'
    import { setupApp } from '~/logic/common-setup'
    import '../styles'
    
    const app = createApp(App)
    setupApp(app)
    app.mount('#app')
  9. Configure Playwright for WebExtension E2E testing

    main

    The project uses Playwright for end-to-end (E2E) testing. The configuration is located in playwright.config.ts and is optimized for testing Chrome extensions.

    Key configuration settings:

    • testDir: Set to ./e2e to specify the directory containing E2E tests.
    • retries: Configured to 2 to allow for automatic test retries.
    • webServer: Automatically manages the development server during testing:
      • command: Runs npm run dev to start the Vite server.
      • url: Monitors http://localhost:3303/popup/main.ts to ensure the Vite server is fully prepared before starting tests.
      • reuseExistingServer: Set to true to avoid conflicts if a server is already running.
    import { defineConfig } from '@playwright/test'
    
    export default defineConfig({
      testDir: './e2e',
      retries: 2,
      webServer: {
        command: 'npm run dev',
        url: 'http://localhost:3303/popup/main.ts',
        reuseExistingServer: true,
      },
    })