Latr parses dates out of ordinary language - “next Friday at 9am”, “in two weeks”. That is chrono-node, and bundled it is about 198KB. The extension has no build step for ordinary work, so the bundle is committed as a single vendored file and every other source file is a hand-written ES module the browser loads directly.

198KB is a lot to parse on the cold start of a service worker that Chrome tears down every thirty seconds. So the obvious optimisation was made:

export async function parseWhen(text) {
  const chrono = await import('./vendor/chrono.js');
  // …
}

Load it when a parse is actually requested, and never on the cold start of a worker that woke up to redraw a badge. It worked. It was tested. It shipped.

The symptom

Two of the four ways to save a reminder stopped working, and only those two.

Highlighting a date and right-clicking failed. Typing rem next tuesday 5pm in the address bar failed. Both surfaced the same message, which was the browser’s own text about a module that could not be loaded, rendered into a UI that expected a friendly explanation of why a phrase had no time in it.

Clicking the toolbar icon and typing the same phrase into the composer worked perfectly.

Why exactly those two

The popup is an ordinary web page. It has a document, a window, and a full module loader. The right-click menu and the omnibox are handled by the service worker, which is none of those things.

The HTML specification disallows dynamic import() on ServiceWorkerGlobalScope. It is not a Chrome limitation and not a bug: a service worker’s module graph must be fully known when the worker is registered, because that is what lets the browser install a coherent version of it and swap it atomically. A module that can appear later would break that guarantee. So import() inside a worker rejects, always, by design.

Every parse in the worker had been throwing since the optimisation landed. Every parse in the popup had been fine. And because the popup is where a developer types the test cases, the failure survived review.

The fix, and its price

import * as chrono from './vendor/chrono.js';

A static import, at the top, unconditionally. Which means the 198KB bundle is parsed on every worker cold start - the exact cost the change was made to avoid.

That cost turns out to be smaller than it looks. V8 caches compiled code across worker restarts, so the second and subsequent cold starts are dominated by cache validation rather than by parsing. And a service worker that wakes to fire a reminder is doing something the user is about to see; a few milliseconds there are invisible next to creating a tab and drawing a notification.

The real lesson is not about bundle size.

What we changed beyond the fix

The bug was not “we used the wrong import”. It was that a rule the platform enforces was being obeyed by luck in one context and violated in another, and nothing in the repository knew the difference.

So the test suite now fails on a dynamic import() anywhere the service worker can reach - not just in the file where it happened, and not by grepping the worker alone, but across the whole import graph the worker pulls in. A future refactor that moves a parser behind a lazily-loaded helper trips the same wire.

Both call sites also kept something that turned out to matter: they distinguish the parser failed to load from this text contains no time. Those are different errors with different fixes, and collapsing them is why the original failure read as “Latr can’t understand this selection” rather than as “Latr is broken”.

The general shape

Extension code runs in at least three environments that look like JavaScript and are not the same JavaScript: a page, a service worker, and an offscreen document. A worker has no window, no document, no Audio, no AudioContext and no dynamic import. A page has all of them.

Anything shared between those environments is only as portable as its least capable host, and the only reliable way to know is to run it there. If a code path can be reached by the worker, test it in the worker - not in the popup, where everything works.