Building Modern Browser Extensions: A Developer's Guide to Vite, Vue 3, and Manifest V3
Browser extensions have become an integral part of our daily web experience, from password managers to productivity tools. However, developing them can be challenging, especially when you want to use modern web technologies and maintain compatibility across different browsers.
After building several extensions and facing the same setup challenges repeatedly, I created a comprehensive starter template that combines the power of Vue 3, Vite, and Manifest V3. Today, I'll share why this combination is game-changing and how you can use it to build your next extension.
The Evolution of Browser Extension Development
Before diving into the technical details, let's talk about why this matters. Browser extension development has evolved significantly over the past few years:
The Manifest V3 Migration
Google's introduction of Manifest V3 brought significant changes to how extensions work:
- Service Workers replace background scripts
- Stricter security policies limit code execution
- New APIs for better performance and security
- Enhanced privacy controls for users
While these changes improve security and performance, they also mean that older development approaches no longer work. Many existing templates and tutorials are outdated, leaving developers to figure out the migration path themselves.
Modern JavaScript Framework Challenges
Integrating modern frameworks like Vue.js or React with browser extensions isn't straightforward. You need to handle:
- Multiple entry points (popup, options, content scripts, background)
- Different execution contexts with varying capabilities
- Hot module replacement for development efficiency
- Production builds optimized for extension environments
Why I Created This Template
After struggling with these challenges across multiple projects, I realized there was a need for a modern, comprehensive starter template. Here's what drove me to create it:
1. Development Speed
Setting up a modern browser extension from scratch can take days. This template gets you started in minutes.
2. Best Practices Built-In
The template incorporates years of learnings about extension architecture, security, and performance optimization.
3. Modern Tooling
Leverage the latest development tools without spending time on configuration.
4. Cross-Browser Compatibility
Write once, deploy everywhere (Chrome, Firefox, Edge, and other Chromium-based browsers).
What Makes This Template Special
Let me walk you through the key features that make this template stand out:
Vue 3 with Composition API
The template uses Vue 3's Composition API with <script setup>
syntax, providing:
<script setup>
import { ref, onMounted } from 'vue'
import { sendMessage } from '@/utils/messaging'
const isActive = ref(false)
const userSettings = ref({})
onMounted(async () => {
const response = await sendMessage({ type: 'GET_SETTINGS' })
userSettings.value = response.data
})
</script>
This approach offers better TypeScript integration, improved performance, and a more intuitive development experience.
Hot Module Replacement (HMR)
One of the biggest pain points in extension development is the slow feedback loop. Traditional development requires:
- Making code changes
- Building the extension
- Reloading the extension in the browser
- Testing the changes
With HMR, changes in your popup, options page, and even content scripts are reflected instantly. This dramatically speeds up development and makes the process much more enjoyable.
Comprehensive TypeScript Support
The template includes full TypeScript support with:
- Type definitions for all browser APIs
- Proper typing for message passing between contexts
- IntelliSense support for extension-specific functionality
- Build-time type checking
// types/messages.ts
export interface GetSettingsMessage {
type: 'GET_SETTINGS'
}
export interface SetSettingsMessage {
type: 'SET_SETTINGS'
data: UserSettings
}
export type Message = GetSettingsMessage | SetSettingsMessage
Automated Build Process
The template includes GitHub Actions workflows for:
- Automated testing on pull requests
- Cross-browser builds for Chrome and Firefox
- Release automation with proper versioning
- Store submission preparation with proper packaging
Project Architecture Deep Dive
Let me show you how the template is structured and why each part matters:
Directory Structure
src/
├── background/ # Service worker scripts
│ ├── index.ts
│ └── messaging.ts
├── content-script/ # Content script functionality
│ ├── index.ts
│ ├── components/
│ └── styles/
├── popup/ # Extension popup
│ ├── main.ts
│ ├── App.vue
│ └── components/
├── options/ # Options/settings page
│ ├── main.ts
│ ├── App.vue
│ └── components/
├── setup/ # Install/update pages
│ ├── install.vue
│ └── update.vue
├── offscreen/ # Offscreen documents
│ └── index.ts
├── components/ # Shared components
├── utils/ # Utility functions
└── types/ # TypeScript definitions
Context Communication Made Simple
One of the most complex aspects of extension development is communication between different contexts. The template uses webext-bridge
to simplify this:
// In content script
import { sendMessage } from 'webext-bridge/content-script'
const response = await sendMessage('get-user-data', { userId: 123 })
// In background script
import { onMessage } from 'webext-bridge/background'
onMessage('get-user-data', async ({ data }) => {
const userData = await fetchUserData(data.userId)
return { success: true, userData }
})
This abstraction handles all the complexity of browser APIs while providing a clean, Promise-based interface.
Advanced Features
Offscreen Documents
For capabilities that require DOM access but can't run in the service worker:
// background/offscreen.ts
export async function createOffscreenDocument() {
if (await chrome.offscreen.hasDocument()) return
await chrome.offscreen.createDocument({
url: 'offscreen.html',
reasons: ['AUDIO_PLAYBACK'],
justification: 'Playing notification sounds'
})
}
Content Script Component Injection
Inject Vue components directly into web pages:
<!-- content-script/components/FloatingWidget.vue -->
<template>
<div class="floating-widget">
<UButton @click="handleAction">
Take Action
</UButton>
</div>
</template>
<script setup>
import { UButton } from '@/components/ui'
const handleAction = () => {
// Interact with the page
}
</script>
Getting Started: Your First Extension
Let's build a simple but practical extension together. We'll create a "Reading Time Calculator" that estimates how long it takes to read any article.
1. Setup
# Clone the template
pnpx degit mubaidr/vite-vue3-browser-extension-v3 reading-time-extension
cd reading-time-extension
npm install
# Start development
npm run dev
2. Configure the Extension
Update manifest.config.ts
:
export default {
name: 'Reading Time Calculator',
description: 'Calculate estimated reading time for any article',
version: '1.0.0',
permissions: ['activeTab', 'storage'],
content_scripts: [{
matches: ['<all_urls>'],
js: ['content-script/index.js']
}]
}
3. Build the Content Script
// content-script/index.ts
import { createApp } from 'vue'
import ReadingTimeWidget from './components/ReadingTimeWidget.vue'
// Calculate reading time
function calculateReadingTime(text: string): number {
const wordsPerMinute = 200
const words = text.split(/\s+/).length
return Math.ceil(words / wordsPerMinute)
}
// Inject the widget
function injectWidget() {
const articleText = document.body.innerText
const readingTime = calculateReadingTime(articleText)
const container = document.createElement('div')
container.id = 'reading-time-widget'
document.body.appendChild(container)
createApp(ReadingTimeWidget, { readingTime }).mount(container)
}
// Run when page is loaded
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', injectWidget)
} else {
injectWidget()
}
4. Create the Widget Component
<!-- content-script/components/ReadingTimeWidget.vue -->
<template>
<div class="reading-time-widget">
<div class="widget-content">
<Icon name="clock" />
<span>{{ readingTime }} min read</span>
</div>
</div>
</template>
<script setup>
interface Props {
readingTime: number
}
defineProps<Props>()
</script>
<style scoped>
.reading-time-widget {
position: fixed;
top: 20px;
right: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
padding: 12px 16px;
z-index: 10000;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
.widget-content {
display: flex;
align-items: center;
gap: 8px;
color: #374151;
font-size: 14px;
font-weight: 500;
}
</style>
5. Test and Build
# Development with HMR
npm run dev
# Build for production
npm run build
# Build for specific browsers
npm run build:chrome
npm run build:firefox
Advanced Development Patterns
State Management Across Contexts
For complex extensions, you'll need shared state management:
// utils/storage.ts
import { storage } from 'webext-bridge/background'
export interface ExtensionState {
settings: UserSettings
cache: CacheData
statistics: UsageStats
}
export const state = reactive<ExtensionState>({
settings: {},
cache: {},
statistics: {}
})
// Sync with browser storage
export async function loadState() {
const stored = await storage.get(['settings', 'cache', 'statistics'])
Object.assign(state, stored)
}
export async function saveState() {
await storage.set(toRaw(state))
}
Error Handling and Logging
// utils/logger.ts
export class ExtensionLogger {
private context: string
constructor(context: string) {
this.context = context
}
error(message: string, error?: Error) {
console.error(`[${this.context}] ${message}`, error)
// Send to analytics or error reporting service
}
info(message: string, data?: any) {
console.log(`[${this.context}] ${message}`, data)
}
}
// Usage in different contexts
const logger = new ExtensionLogger('content-script')
logger.info('Extension initialized')
Performance Optimization
// utils/performance.ts
export function debounce<T extends (...args: any[]) => any>(
func: T,
wait: number
): (...args: Parameters<T>) => void {
let timeout: NodeJS.Timeout
return (...args: Parameters<T>) => {
clearTimeout(timeout)
timeout = setTimeout(() => func(...args), wait)
}
}
// Usage for expensive operations
const debouncedAnalysis = debounce(analyzePageContent, 500)
Deployment and Distribution
Store Submission Preparation
The template includes scripts to prepare your extension for store submission:
# Generate store-ready packages
npm run build:submission
# This creates:
# - chrome-extension.zip (for Chrome Web Store)
# - firefox-extension.zip (for Firefox Add-ons)
# - edge-extension.zip (for Microsoft Edge Add-ons)
Automated Testing
// tests/extension.test.ts
import { describe, it, expect } from 'vitest'
import { calculateReadingTime } from '../src/utils/reading-time'
describe('Reading Time Calculator', () => {
it('calculates reading time correctly', () => {
const text = 'This is a sample text with exactly ten words here.'
const time = calculateReadingTime(text)
expect(time).toBe(1) // Should be 1 minute for 10 words
})
})
Continuous Integration
The template includes GitHub Actions for automated testing and building:
# .github/workflows/ci.yml
name: CI/CD
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm install
- run: npm run test
- run: npm run build
Real-World Examples and Use Cases
Let me share some successful extensions built with this template:
1. Productivity Tools
- Focus Timer: Pomodoro technique implementation with website blocking
- Tab Manager: Advanced tab organization and session management
- Note Taker: Quick note-taking with synchronized storage
2. Developer Tools
- API Testing: In-browser API testing and documentation
- Color Picker: Advanced color picking with format conversion
- JSON Formatter: Format and validate JSON on any page
3. Content Enhancement
- Reading Assistant: Text-to-speech and reading comprehension tools
- Translation Helper: Inline translation with multiple providers
- Accessibility Enhancer: Improve web accessibility on-the-fly
Contributing to the Ecosystem
This template is open source, and I encourage contributions from the community. Here's how you can get involved:
Feature Requests and Bug Reports
- Use the GitHub Issues for bug reports
- Join our Discord community for discussions
- Share your success stories and extensions built with the template
Contributing Code
- Check out the contribution guidelines
- Look for "good first issue" labels for beginner-friendly contributions
- Help improve documentation and examples
Future Roadmap
I'm continuously working on improving this template. Here's what's coming:
Short Term (Next 3 months)
- React version of the template
- Svelte support for those who prefer it
- Enhanced testing utilities with extension-specific helpers
- More comprehensive examples for common use cases
Long Term (6-12 months)
- Visual extension builder for non-developers
- Extension analytics dashboard for monitoring usage
- Automated store submission workflow
- Extension marketplace for template sharing
Getting Help and Support
If you run into issues or have questions:
- Check the documentation in the repository README
- Search existing issues on GitHub
- Join our community on Discord for real-time help
- Book a consultation if you need professional assistance
Final Thoughts: Building the Future of Browser Extensions
Browser extensions represent one of the most direct ways to improve people's daily web experience. With the right tools and approach, you can build extensions that genuinely make a difference in how people interact with the web.
This template removes the technical barriers that often prevent great ideas from becoming reality. Whether you're a seasoned developer or just starting out, you now have everything you need to build modern, performant, and maintainable browser extensions.
The web is constantly evolving, and so are the tools we use to extend it. By embracing modern development practices and staying current with browser capabilities, we can create extensions that not only work today but are built to adapt and grow with the platform.
What will you build? I can't wait to see the innovative extensions that emerge from this foundation. The future of browser extension development is bright, and I'm excited to be part of this journey with you.
Happy building, and remember—every great extension starts with a single idea and the courage to bring it to life!
Ready to start building? Clone the template today and join the community of developers creating the next generation of browser extensions.