Most Firefox extension tutorials start with a framework. React, Vue, Webpack, TypeScript — before you've written a single line of extension-specific code, you're configuring build pipelines.
I took the opposite approach when building Weather & Clock Dashboard: zero dependencies, zero build step, pure HTML/CSS/JS. Here's what I learned.
Why No Framework?
I had three reasons:
Smaller footprint. Users care about extension size. A 2MB bundle for a new tab page is excessive. With pure HTML/CSS/JS, the entire extension is under 100KB.
No update anxiety. Framework-based extensions can break when dependencies update. With vanilla JS, there's nothing to update.
Easier contribution. If you want contributors from the broader web dev community, vanilla JS has zero barrier to entry. Any front-end developer can open the file and understand it immediately.
Extension Anatomy
A Firefox extension at minimum needs:
-
manifest.json— metadata and permissions - One or more HTML/CSS/JS files
- Optional: icons
That's it. No build process required.
For a new tab replacement:
{
"manifest_version": 2,
"name": "My Extension",
"version": "1.0",
"chrome_url_overrides": {
"newtab": "newtab.html"
},
"permissions": []
}
The Weather API Integration
For weather, I used OpenWeatherMap's free tier. The API call is straightforward:
async function fetchWeather(city) {
const apiKey = 'YOUR_API_KEY';
const url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=metric`;
const response = await fetch(url);
const data = await response.json();
return {
temp: Math.round(data.main.temp),
description: data.weather[0].description,
icon: data.weather[0].icon
};
}
No library needed. Native fetch handles the request; destructuring extracts the data.
World Clocks Without a Library
The built-in Intl.DateTimeFormat API handles time zone conversion perfectly:
function getTimeInZone(timezone) {
return new Intl.DateTimeFormat('en-US', {
hour: '2-digit',
minute: '2-digit',
timeZone: timezone,
hour12: true
}).format(new Date());
}
// Usage
getTimeInZone('America/New_York') // "2:30 PM"
getTimeInZone('Europe/London') // "7:30 PM"
getTimeInZone('Asia/Tokyo') // "3:30 AM"
No moment.js, no date-fns, no luxon. Just the native API.
Dark/Light Mode
:root {
--bg: #ffffff;
--text: #1a1a1a;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #1a1a2e;
--text: #e0e0e0;
}
}
System preference detection for free. No JavaScript required.
Caching Weather Data
To avoid hammering the API and to make the extension feel instant:
const CACHE_DURATION = 30 * 60 * 1000; // 30 minutes
function getCachedWeather() {
const cached = localStorage.getItem('weather');
if (!cached) return null;
const { data, timestamp } = JSON.parse(cached);
if (Date.now() - timestamp > CACHE_DURATION) return null;
return data;
}
function setCachedWeather(data) {
localStorage.setItem('weather', JSON.stringify({
data,
timestamp: Date.now()
}));
}
localStorage in browser extensions works like any web page — simple key/value storage that persists between tab opens.
The Permission Model
This is where extensions differ from web pages. Permission declarations in manifest.json control what APIs are available. For a new tab replacement with weather and world clocks:
{
"permissions": [
"storage"
],
"content_security_policy": "script-src 'self'; object-src 'self'"
}
No special permissions needed for fetch calls to external APIs — that's allowed by default.
Performance: Load Time
Opening a new tab should be fast. Two practices that help:
1. Show cached content immediately:
// On page load, check cache first
const cached = getCachedWeather();
if (cached) {
displayWeather(cached); // Show immediately
}
// Then refresh in background
fetchWeather(city).then(data => {
setCachedWeather(data);
displayWeather(data);
});
2. Minimize DOM operations:
Instead of updating the DOM in a loop, build the HTML string first, then set innerHTML once:
function renderClocks(clocks) {
const html = clocks.map(clock => `
<div class="clock">
<div class="city">${clock.city}</div>
<div class="time">${getTimeInZone(clock.timezone)}</div>
</div>
`).join('');
document.getElementById('clocks').innerHTML = html;
}
What I'd Do Differently
If I were starting over, I'd:
Add TypeScript — not for a framework, but for type safety on the API responses. I made several bugs where I accessed
data.main.tempon an error response. Types would have caught those.Use ES modules — modern browsers support
<script type="module">, which enables cleaner imports even without a bundler.Extract the config UI earlier — The settings panel (city name, API key, clock timezones) was an afterthought. Starting with the settings model first would have led to cleaner state management.
The Extension
Weather & Clock Dashboard is live on Firefox Add-Ons and fully open source:
- Install: addons.mozilla.org
- Source: github.com/oren-sys/weather-clock-dashboard
The entire codebase is vanilla JS — no framework, no build step, no node_modules. If you're building a similar extension, the code might be a useful reference.
Questions about Firefox extension development? Happy to answer in the comments.
Top comments (0)