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
- 1Each pane is a plain
textareawith anonkeyuphandler — no editor library, so nothing hides the mechanics. - 2On every keystroke,
run()reads all three panes and writes the HTML plus an inline<style>block into the iframe’s body. - 3The JavaScript runs inside the iframe’s own window via
contentWindow.eval, so its globals and DOM calls target the preview, not the editor.
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
- 1Render into a sandboxed iframe via
srcdoc, rebuilt each run, so every preview starts clean and can’t touch the editor. - 2Bridge the console with
postMessagefrom inside the frame, so logs and errors land in the panel. - 3Debounce runs, persist work across refreshes, and add the auto-completion I noted in the README.
Built with
- HTML
- CSS
- JavaScript