Developer Tools
TypeScriptCodeMirrorLocal-FirstWeb Workers

Building Local-First Browser Tools with CodeMirror and Web Workers

Sathya Seelan
Sathya Seelan
Full-Stack Developer
September 05, 2024•7 min read

“How to architect zero-backend developer utilities that process large documents in the browser with responsive 60fps interaction.”

Modern web browsers are capable runtime engines with WebAssembly, Web Workers, and hardware-accelerated rendering. Yet many developer tools still transmit user data to remote servers for simple parsing, conversions, and transformations.

Building local-first web utilities (such as live Markdown to PDF/DOCX exporters or code formatters) ensures instant execution, total data privacy, and offline capability.


1. Offloading Heavy Parsing to Web Workers#

If a user pastes a 50,000-line Markdown document or large JSON file, parsing it on the main JavaScript thread causes perceptible UI jank and freezes typing in the editor.

By dispatching compute-intensive tokenization to a Web Worker, the main UI thread remains pinned at 60fps:

typescript
// workers/parserWorker.ts
self.onmessage = (e: MessageEvent<{ text: string; id: number }>) => {
  const { text, id } = e.data;
  
  // Execute compute-intensive AST transformations
  const ast = transformMarkdownAST(text);
  
  self.postMessage({ id, ast });
};
typescript
// hooks/useWorkerParser.ts
import { useEffect, useRef, useState } from "react";

export function useWorkerParser(rawText: string) {
  const [result, setResult] = useState<string>("");
  const workerRef = useRef<Worker | null>(null);

  useEffect(() => {
    workerRef.current = new Worker(
      new URL("../workers/parserWorker.ts", import.meta.url)
    );

    workerRef.current.onmessage = (event) => {
      setResult(event.data.ast);
    };

    return () => workerRef.current?.terminate();
  }, []);

  useEffect(() => {
    workerRef.current?.postMessage({ text: rawText, id: Date.now() });
  }, [rawText]);

  return result;
}

2. CodeMirror 6 Architecture#

CodeMirror 6 is built around an immutable state transaction model:

  • EditorState: Immutable snapshot of the document and selections.
  • Transaction: Dispatched modifications that generate a new EditorState.
  • ViewPlugin / Extension: Decoupled listeners that render decorations, line numbers, and syntax highlighting.

This design makes it simple to intercept keystrokes, provide custom linting decorations, and synchronize live previews without side effects.


3. Why Local-First Matters#

  • Zero Latency: No network round-trip overhead.
  • Zero Server Costs: Compute runs on the client's device.
  • Privacy by Default: Sensitive documents never leave the user's browser storage.