Chrome Extension Svelte Setup — Developer Guide
6 min readSvelte 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
docs/guides/vite-extension-setup.md— general Vite configurationdocs/guides/typescript-extensions.md— TypeScript best practices
Related Articles
Related Articles
Part of the Chrome Extension Guide by theluckystrike. Built at zovo.one.