Most teams I’ve worked with have a “clean up the CSS” script buried in a build step or someone’s terminal history. It usually works. It’s also hard to approach: if you’re not comfortable with CLIs and config files, you can’t easily tell what’s safe to remove or see the impact of removing it.
extract-css started as an attempt to make that process tangible. One page where you paste real HTML and CSS, run an extraction, and see the result in a browser‑style preview. Nothing to install, and your code doesn’t go to a third‑party service.
The shape that fell out of it is a full‑stack app: an edge‑hosted worker runs the extraction engine and serves the app, and a React front‑end talks to it through a typed API.
Why It Isn’t a CLI
A CLI would have been the familiar choice. I’ve written that script. The problem is the preview: run a command, then manually open a browser and wire up a test page. Every time. I wanted the preview built in, and I didn’t want people installing packages or starting a local server for a one‑off extraction.
An edge worker covers both sides. It serves the front‑end assets and exposes a small API that runs PurgeCSS, or a similar engine, on the provided HTML and CSS. Deployment stays simple: one worker, one build, no separate backend stack.
A Small, Typed API Over the Engine
The server’s public surface is a single procedure. It accepts an object with HTML and CSS strings, runs basic validation, strips <script> tags and obvious external references, and passes the cleaned data into the extraction engine with a configuration that preserves keyframes, font faces, and CSS variables.
type ExtractRequest = { html: string; css: string };
type ExtractResult = { css: string };
async function extractCss(req: ExtractRequest): Promise<ExtractResult> {
// validate input, sanitize HTML, invoke extraction engine
return { css: cleanedOutput };
}
Errors turn into simple, predictable responses the client can render. There’s no authentication or database in the core flow. If the tool ever needs per‑user features, the call shape doesn’t have to change.
On the client, a typed API client and a data‑fetching layer keep the edge call encapsulated. Components ask for “run the extraction” through a mutation that returns a result object, and that’s enough to drive the UI. None of the UI pieces know about PurgeCSS directly.
Two Editors and a Preview
The main experience is two editors and a preview pane. The interesting state question is what the preview is rendering versus what you’re typing, so instead of scattering state across hooks, a reducer tracks both: what’s currently in the HTML and CSS editors, and what the preview is currently rendering.
type WorkbenchState = {
htmlInput: string;
cssInput: string;
previewHtml: string;
previewCss: string;
};
When you run an extraction, the mutation updates the live HTML and CSS for the preview in one step, and the preview re‑renders. Your inputs don’t change unless you copy the result back into them, which makes before/after comparison and tweak‑and‑rerun both work without losing the original.
The preview sits in a resizable panel. Drag a handle or use the keyboard to adjust its height, and the preferred size is remembered locally. A small detail, but long sessions are more comfortable for it.
Pasted Content Is Untrusted Content
The common use case is pasting HTML and CSS straight from a production page. Which means the preview has to assume that content is hostile.
Before anything goes into the iframe, the app strips scripts, removes CSS that tries to pull in external resources, and wraps the result in a tightly scoped document. The iframe is sandboxed and uses an inline document string rather than pointing at a URL.
If something goes wrong while building that document, the UI shows an explicit error state instead of rendering half‑broken markup. The preview should feel like a real browser without executing arbitrary code.
One Place to Deploy
Outside the workbench there isn’t much app. A small landing page explains what the tool does and shows a before/after example, and there’s the app route itself. Both share the same API client and query layer, so loading and error states behave the same everywhere.
Locally it runs under a standard dev server. In production, a Vite build is bundled with the worker and deployed to the edge. One place to deploy, one API to think about, and a single page where most of the behavior lives.
The tool is modest by design: paste, extract, preview. The point is a clear, safe way to answer “what CSS is actually used here?” without becoming a build‑tool expert or trusting a black‑box service with your code.