Metafetch drops its last runtime dependency — and dodges a GPL relicense doing it
typed.js quietly went GPL-3.0 in v3. Instead of freezing on the old version, we rebuilt the typing animation in ~100 lines of our own TypeScript — and the plugin bundle shrank 18%.
Why Care?
Every dependency you ship is a little contract with a stranger. Metafetch had
exactly one runtime dependency — typed.js, the library animating the playful
"Consulting the oracle of structured data..." messages while your metadata
fetches. In January, typed.js v3 changed its license from MIT to GPL-3.0.
Nothing about the code changed; everything about what it means to bundle it
did. GPL code compiled into a plugin's main.js would obligate the whole
plugin — and arguably anything built on it — to carry GPL terms.
We'd rather our plugins stay frictionless to fork, study, and build on. So instead of pinning the old MIT version forever and hoping, we removed the dependency entirely.
What's New?
Zero runtime dependencies. typed.js is gone from
package.json.The animation stayed. A new in-repo
Typewriterclass (~100 lines,src/utils/typewriter.ts) reproduces exactly what we used: type/backspace cycling, smart backspacing to the common prefix of the next message, and the blinking cursor — which was already styled by our own CSS.The bundle shrank 18% — 47.6 KB → 39.1 KB.
Dev toolchain refreshed while we were in there: typescript-eslint 8.65, eslint 10.7, esbuild 0.28.1 — and
@types/nodedeliberately pinned down to^22, because Obsidian's desktop app ships Electron 39, which runs Node 22. Types for Node 25 describe APIs the plugin's runtime doesn't have; matching the types to the real runtime means the compiler tells the truth.
How it Works
The rebuilt animation is a small recursive setTimeout chain: type
characters at 25 ms, hold a full message for a second, backspace at 30 ms —
but only as far as the common prefix with the next message (typed.js
called this smart backspace), then type the remainder of the new one. The
cursor is a sibling <span class="typed-cursor"> reusing the blink
keyframes already in styles.css — the old integration ran with
autoInsertCss: false, so the visuals were always ours. destroy() clears
the pending timer and removes the cursor, same contract the modals already
called.
TypeScript 6's stricter defaults earned their keep immediately: its
no-unchecked-indexed-access rule flagged two spots where an out-of-range
string index would have produced undefined — fixed before the code ever
ran.
Part of the family-wide dependency-upgrade campaign: [[content-farm/context-v/plans/Dependency-Upgrades-Across-Plugin-Family]].