DEV Community

Weather Clock Dash
Weather Clock Dash

Posted on

I Built a Firefox Extension with Zero Dependencies: Here's What I Learned

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:

  1. 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.

  2. No update anxiety. Framework-based extensions can break when dependencies update. With vanilla JS, there's nothing to update.

  3. 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": []
}
Enter fullscreen mode Exit fullscreen mode

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
  };
}
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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()
  }));
}
Enter fullscreen mode Exit fullscreen mode

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'"
}
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

What I'd Do Differently

If I were starting over, I'd:

  1. Add TypeScript — not for a framework, but for type safety on the API responses. I made several bugs where I accessed data.main.temp on an error response. Types would have caught those.

  2. Use ES modules — modern browsers support <script type="module">, which enables cleaner imports even without a bundler.

  3. 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:

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)