Chrome Extension Svelte Setup — Developer Guide

6 min read

Svelte Setup for Chrome Extensions

Svelte provides an excellent developer experience for building Chrome extensions with minimal runtime overhead. Its compile-time approach produces tiny bundles—critical for fast popup loading.

Why Svelte for Extensions

Svelte offers compelling advantages for Chrome extension development. The compiled output has no virtual DOM overhead, resulting in runtime sizes around 2KB (gzipped) for the core framework. This is particularly important for extension popups where every millisecond counts during user interaction.

Unlike React or Vue, Svelte compiles away to efficient imperative code. Your extension loads faster, responds quicker, and users notice the difference. The scoped styling system works naturally within Chrome extension contexts without CSS leakage.

Project Setup with Vite

Initialize a new Svelte project using Vite:

npm create vite@latest my-extension -- --template svelte-ts
cd my-extension
npm install

Install the Chrome extension Vite plugin:

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

vite.config.ts Configuration

Configure Vite for multiple extension entry points:

import { defineConfig } from 'vite';
import { chromeExtension } from 'vite-plugin-chrome-extension';
import { svelte } from '@sveltejs/vite-plugin-svelte';

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

Multiple Entry Points

Organize your extension with separate directories for each entry point:

src/
  popup/
    main.ts        # Svelte app mount
    Popup.svelte   # Main popup component
  options/
    main.ts
    Options.svelte
  sidepanel/
    main.ts
    Sidepanel.svelte
  content/
    content.ts     # Content script entry
    App.svelte     # Svelte component mounted in page

Each entry point compiles to a separate JavaScript file automatically.

Svelte Stores for Chrome API

Create reactive stores backed by chrome.storage:

import { writable } from 'svelte/store';

function createChromeStorage<T>(key: string, initial: T) {
  const { subscribe, set } = writable<T>(initial);
  
  chrome.storage.local.get(key, (result) => {
    if (result[key]) set(result[key]);
  });
  
  chrome.storage.onChanged.addListener((changes, area) => {
    if (area === 'local' && changes[key]) {
      set(changes[key].newValue);
    }
  });
  
  return {
    subscribe,
    set: (value: T) => {
      chrome.storage.local.set({ [key]: value });
      set(value);
    }
  };
}

export const settings = createChromeStorage('settings', { theme: 'light' });

Svelte 5 Runes Integration

Use Svelte 5 runes for reactive Chrome API data:

import { browser } from '$app/environment';

let tabs = $state<chrome.tabs.Tab[]>([]);

if (browser) {
  chrome.tabs.query({}, (result) => {
    tabs = result;
  });
  
  chrome.tabs.onUpdated.addListener(() => {
    chrome.tabs.query({}, (result) => {
      tabs = result;
    });
  });
}

const activeCount = $derived(tabs.filter(t => t.active).length);

Content Scripts with Svelte

Mount Svelte components in content scripts using shadow DOM:

import { mount } from 'svelte';
import ContentApp from './ContentApp.svelte';

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);

mount(ContentApp, { target: mountPoint });

Svelte’s scoped styles automatically apply within the shadow DOM without affecting the host page.

SvelteKit for Extension Pages

Use SvelteKit with adapter-static for extension pages:

npm install -D @sveltejs/adapter-static

Configure svelte.config.js:

import adapter from '@sveltejs/adapter-static';

export default {
  kit: {
    adapter: adapter({
      pages: 'build',
      assets: 'build',
      fallback: 'index.html'
    })
  }
};

Set prerender = false in your root layout for Chrome extension pages.

TypeScript Integration

Install Svelte type checking:

npm install -D svelte-check tslib

Add to package.json scripts:

{
  "scripts": {
    "check": "svelte-check --tsconfig ./tsconfig.json"
  }
}

Styling Options

Svelte’s built-in scoped styles work perfectly for extensions:

<style>
  .button {
    background: #4a90d9;
    padding: 8px 16px;
  }
</style>

For Tailwind CSS, add PostCSS configuration:

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

Configure tailwind.config.js for your source files, then import in your components.

Testing

Set up Vitest with Svelte testing library:

npm install -D vitest @testing-library/svelte jsdom

Configure vitest.config.ts:

import { defineConfig } from 'vitest/config';
import { svelte } from '@sveltejs/vite-plugin-svelte';

export default defineConfig({
  plugins: [svelte({ hot: !process.env.VITEST })],
  test: {
    environment: 'jsdom',
    globals: true
  }
});

Write tests using @testing-library/svelte for component testing.

Bundle Size Advantage

Svelte’s compilation approach produces remarkably small bundles. A typical popup with multiple components might compile to 15-20KB (gzipped), compared to 40-60KB+ for equivalent React implementations. This speed matters: users expect popups to open instantly.

The small footprint also helps stay within Chrome’s service worker memory limits and improves overall extension performance.

Cross-References

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