Building a Chrome Extension with Modern JavaScript
Complete guide to building the F1 Schedule Chrome Extension with real-time Formula 1 data, modern JavaScript, and professional UI design. From concept to Chrome Web Store publication.
Photo by Fatos Bytyqi on Unsplash
🏎️ Building a Chrome Extension with Modern JavaScript
🚀 Project Showcase: Learn how I built the F1 Schedule Chrome Extension that brings real-time Formula 1 race information directly to your browser. Complete with API integration, modern UI, and professional deployment.
Creating browser extensions is an excellent way to solve real-world problems while learning new technologies. Today, I'll walk you through the complete journey of building the F1 Schedule Chrome Extension - from initial concept to Chrome Web Store publication.
📋 Project Overview
🎯 The Problem
As a Formula 1 fan and developer, I found myself constantly switching between multiple websites to check:
🔍 Pain Points:
- • **Multiple tabs** for race schedules
- • **Time zone confusion** for race times
- • **Scattered information** across different sites
- • **No quick access** to current standings
🎯 The Solution
✅ F1 Schedule Extension Features:
🛠️ Technical Implementation
Project Structure
f1-schedule-extension/
├── manifest.json # Extension configuration
├── popup.html # Main UI
├── popup.js # Logic and API calls
├── styles.css # Styling
├── background.js # Background scripts
└── icons/ # Extension icons
├── icon16.png
├── icon48.png
└── icon128.png
Manifest Configuration
The manifest.json file defines the extension's capabilities:
{
"manifest_version": 3,
"name": "F1 Schedule",
"version": "1.0.0",
"description": "Get latest F1 race events, standings, and news",
"permissions": [
"storage",
"activeTab"
],
"host_permissions": [
"https://api.formula1.com/*",
"https://ergast.com/*"
],
"action": {
"default_popup": "popup.html",
"default_title": "F1 Schedule"
},
"background": {
"service_worker": "background.js"
},
"icons": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
}
API Integration
I used the Ergast API for F1 data, which provides comprehensive racing information:
class F1API {
constructor() {
this.baseURL = 'https://ergast.com/api/f1';
this.currentSeason = new Date().getFullYear();
}
async fetchRaceSchedule() {
try {
const response = await fetch(
`${this.baseURL}/${this.currentSeason}.json`
);
const data = await response.json();
return data.MRData.RaceTable.Races;
} catch (error) {
console.error('Error fetching race schedule:', error);
return [];
}
}
async fetchDriverStandings() {
try {
const response = await fetch(
`${this.baseURL}/${this.currentSeason}/driverStandings.json`
);
const data = await response.json();
return data.MRData.StandingsTable.StandingsLists[0].DriverStandings;
} catch (error) {
console.error('Error fetching driver standings:', error);
return [];
}
}
async fetchConstructorStandings() {
try {
const response = await fetch(
`${this.baseURL}/${this.currentSeason}/constructorStandings.json`
);
const data = await response.json();
return data.MRData.StandingsTable.StandingsLists[0].ConstructorStandings;
} catch (error) {
console.error('Error fetching constructor standings:', error);
return [];
}
}
}
UI Components
The popup interface uses modern JavaScript and CSS for a clean, responsive design:
class F1Popup {
constructor() {
this.api = new F1API();
this.currentView = 'schedule';
this.init();
}
async init() {
this.setupEventListeners();
await this.loadData();
}
setupEventListeners() {
document.querySelectorAll('.tab-button').forEach(button => {
button.addEventListener('click', (e) => {
this.switchTab(e.target.dataset.tab);
});
});
document.getElementById('refresh-button').addEventListener('click', () => {
this.loadData();
});
}
async loadData() {
this.showLoading();
try {
switch (this.currentView) {
case 'schedule':
await this.loadRaceSchedule();
break;
case 'drivers':
await this.loadDriverStandings();
break;
case 'constructors':
await this.loadConstructorStandings();
break;
}
} catch (error) {
this.showError('Failed to load data');
} finally {
this.hideLoading();
}
}
async loadRaceSchedule() {
const races = await this.api.fetchRaceSchedule();
const container = document.getElementById('schedule-container');
container.innerHTML = races.map(race => `
<div class="race-card">
<div class="race-header">
<h3>${race.raceName}</h3>
<span class="race-round">Round ${race.round}</span>
</div>
<div class="race-details">
<p><strong>Circuit:</strong> ${race.Circuit.circuitName}</p>
<p><strong>Location:</strong> ${race.Circuit.Location.locality}, ${race.Circuit.Location.country}</p>
<p><strong>Date:</strong> ${this.formatDate(race.date, race.time)}</p>
</div>
</div>
`).join('');
}
formatDate(date, time) {
const raceDate = new Date(`${date}T${time}`);
const options = {
weekday: 'short',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
timeZoneName: 'short'
};
return raceDate.toLocaleDateString('en-US', options);
}
switchTab(tabName) {
this.currentView = tabName;
// Update tab buttons
document.querySelectorAll('.tab-button').forEach(button => {
button.classList.toggle('active', button.dataset.tab === tabName);
});
// Update content containers
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.toggle('active', content.id === `${tabName}-container`);
});
this.loadData();
}
}
// Initialize the popup when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
new F1Popup();
});
🎨 Styling and UX
The extension uses a modern, F1-inspired design with:
:root {
--f1-red: #e10600;
--f1-black: #15151e;
--f1-white: #ffffff;
--f1-gray: #949498;
}
.popup-container {
width: 400px;
min-height: 500px;
background: var(--f1-white);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.header {
background: linear-gradient(135deg, var(--f1-red), #ff3e30);
color: var(--f1-white);
padding: 16px;
text-align: center;
}
.tabs {
display: flex;
background: var(--f1-black);
}
.tab-button {
flex: 1;
padding: 12px;
background: transparent;
color: var(--f1-gray);
border: none;
cursor: pointer;
transition: all 0.3s ease;
}
.tab-button.active {
background: var(--f1-red);
color: var(--f1-white);
}
.race-card {
border: 1px solid #eee;
border-radius: 8px;
padding: 16px;
margin: 8px 0;
transition: transform 0.2s ease;
}
.race-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
🚀 Key Features Implemented
1. Caching Strategy
async fetchWithCache(url, cacheKey, ttl = 3600000) { // 1 hour TTL
const cached = await this.getFromStorage(cacheKey);
if (cached && Date.now() - cached.timestamp < ttl) {
return cached.data;
}
const data = await fetch(url).then(r => r.json());
await this.saveToStorage(cacheKey, {
data,
timestamp: Date.now()
});
return data;
}
2. Error Handling
async safeApiCall(apiFunction, fallbackData = []) {
try {
return await apiFunction();
} catch (error) {
console.error('API call failed:', error);
this.showNotification('Using cached data', 'warning');
return fallbackData;
}
}
3. Local Storage Management
async saveToStorage(key, data) {
return chrome.storage.local.set({ [key]: data });
}
async getFromStorage(key) {
const result = await chrome.storage.local.get(key);
return result[key];
}
📦 Publishing to Chrome Web Store
The publishing process involves:
- Package the extension
- Create developer account
- Upload and configure
- Submit for review
# Create a zip file with all extension files
zip -r f1-schedule-extension.zip . -x "*.git*" "node_modules/*" "*.md"
🔮 Future Enhancements
🚀 Roadmap Features:
💡 Key Learnings & Best Practices
| Aspect | Learning | Implementation |
|---|---|---|
| Manifest V3 | New service worker model | Background scripts optimization |
| API Management | Rate limiting strategies | Smart caching with TTL |
| UX Design | Quick, focused interactions | Minimal popup interface |
| Testing | Cross-browser compatibility | Chrome, Edge, Firefox support |
⚡ Pro Tips:
- **Cache API responses** to reduce load times
- **Handle offline scenarios** gracefully
- **Optimize bundle size** for faster loading
- **Test across browsers** early and often
🎯 Try It Yourself
📚 Resources & Links:
• **GitHub Repository**: [F1 Schedule Extension](https://github.com/leoodz/f1schedule)
• **Chrome Web Store**: [Install Extension](https://chrome.google.com/webstore)
• **Documentation**: [Chrome Extension API](https://developer.chrome.com/docs/extensions/)
• **F1 API**: [Ergast Developer API](http://ergast.com/mrd/)
The extension demonstrates how modern web technologies can create useful, performance-focused browser tools that solve real problems for users.
💬 Join the Discussion:
Have you built any Chrome extensions? What challenges did you face? Share your experiences and let's learn together!
Follow me for more web development tutorials and project breakdowns!
This tutorial demonstrates real-world Chrome extension development with modern JavaScript. All code is open source and available for learning and contribution.