sarthak bhardwaj.
0%
Skip to content
← work

my-ide · side quest · 2024

Rebuilding CodePen's live preview, to find out where the code actually runs

Three text areas, one iframe and about thirty lines of JavaScript: type HTML, CSS or JS and the output updates on every keystroke. It looks trivial — until you ask what happens when the code you're previewing throws.

Why build it

Live preview sounds easy until you ask where the code runs

CodePen renders your HTML, CSS and JavaScript live as you type. I wanted to know what that takes: where the preview lives, how the three languages get combined, and what stops a broken script from taking the editor down with it.

How it works

Three panes, one function, one iframe

How the live preview is assembledHTML panetextareaCSS panetextareaJS panetextarearun()on every keyupiframe documentbody = html + <style>iframe windowcontentWindow.eval(js)innerHTMLeval
Every keystroke in any pane re-runs the same function, which rebuilds the preview from all three.
  1. 1
    Each pane is a plain textarea with an onkeyup handler — no editor library, so nothing hides the mechanics.
  2. 2
    On every keystroke, run() reads all three panes and writes the HTML plus an inline <style> block into the iframe’s body.
  3. 3
    The JavaScript runs inside the iframe’s own window via contentWindow.eval, so its globals and DOM calls target the preview, not the editor.
script.js
function run() {
  const html = document.getElementById("html_code").value;
  const css  = document.getElementById("css_code").value;
  const js   = document.getElementById("js_code").value;
  const out  = document.getElementById("output");

  out.contentDocument.body.innerHTML = html + "<style>" + css + "</style>";
  out.contentWindow.eval(js);
}

Catching console output

A console panel, and the bug that taught me about windows

The second version added a console panel under the editor: console.log is wrapped so every call is also appended to the page, and a global error listener prints uncaught exceptions.

What I learned

Isolation is the real feature

The live-preview loop is ten lines. Everything hard is about boundaries: making sure the preview can’t break the editor, that its output comes back, and that re-running doesn’t leave state from the last run behind. A same-origin iframe gives you a separate DOM, but not a sandbox.

Where I'd take it

From toy to tool

  1. 1
    Render into a sandboxed iframe via srcdoc, rebuilt each run, so every preview starts clean and can’t touch the editor.
  2. 2
    Bridge the console with postMessage from inside the frame, so logs and errors land in the panel.
  3. 3
    Debounce runs, persist work across refreshes, and add the auto-completion I noted in the README.

Built with

  • HTML
  • CSS
  • JavaScript