Asia/Jakarta
ProjectsJuly 13, 2026

Code Playground

View project
React
NextJs
Tailwindcss
shacdn/ui
Golang
image
Code Playground is an in-browser sandbox for trying out snippets in six languages — JavaScript, TypeScript, Python, Ruby, Go, and PHP — each on its own route (/js, /ts, /py, /rb, /go, /php), all sharing one Monaco-based editor component. Everything runs entirely client-side: there is no backend code execution at all, only WebAssembly runtimes and a sandboxed iframe doing the work in the visitor's own browser. The project grew from a two-language JS/TS playground into a six-language one by keeping the core editor component completely language-agnostic — every per-language behavior (Monaco's language id, an optional compile-to-JS step, and how the sandboxed srcDoc is built) is looked up from a single registry, so adding a language is a matter of adding a config entry and a sandbox builder, not touching the editor itself.
  • Six live playgrounds: JavaScript and TypeScript (type-checked via Monaco, transpiled with the real typescript package), Python via Pyodide, Ruby via ruby.wasm, Go via a Yaegi interpreter compiled to WebAssembly, and PHP via php-wasm — all on one shared editor component.
  • Fully client-side execution: no server ever runs user code; each language's runtime (Pyodide, CRuby wasm, a custom Yaegi WASI binary, or an Emscripten-compiled PHP build) is loaded and executed inside a sandboxed <iframe sandbox="allow-scripts">, never with allow-same-origin.
  • Unified console output: a shared bridge script overrides console.* and window.onerror/onunhandledrejection inside every sandbox and streams output back to the parent via postMessage, so stdout/stderr from all six languages lands in the same on-page console panel.
  • Real Go execution, not a mock: Go source is interpreted at runtime by Yaegi (compiled with GOOS=wasip1 GOARCH=wasm), giving a fresh WASI instance per run rather than the classic wasm_exec.js long-lived-instance approach.
  • Auto-saving editor state: each playground's code is persisted to localStorage per language and restored on load.
  • Light/dark theming: Monaco's own theme is synced to next-themes's resolved theme, on top of Tailwind's dark: variant for the rest of the UI.
  • Next.js 16 (App Router): routing and app shell for the landing page and all six playground routes.
  • React 19 + TypeScript: the editor, console panel, and surrounding UI.
  • Tailwind CSS v4: utility-first styling, configured via @theme inline in globals.css.
  • shadcn/ui + @base-ui/react: accessible UI primitives (this registry builds on base-ui, not Radix).
  • @monaco-editor/react: the shared code editor, loaded from a CDN with no bundler plugin.
  • Pyodide, @ruby/wasm-wasi + CRuby wasm, @bjorn3/browser_wasi_shim, and php-wasm: the four non-JS/TS language runtimes, each loaded from a CDN only inside its own sandbox srcDoc so /js and /ts never pay for the others' bundle size.
  • Go + Yaegi: wasm/yaegi-runner is a standalone Go module compiled to WebAssembly, embedding the full Go standard library as an interpreter target for the /go playground.
  • next-themes: light/dark mode switching, synced manually into Monaco's own theme system.
  • ESLint (flat config) + eslint-plugin-simple-import-sort, Prettier: linting, import ordering, and formatting.
Running six different languages safely in the browser meant every runtime had to execute inside a sandboxed srcDoc iframe with allow-scripts only — never allow-same-origin, since that would make the sandbox document same-origin with the app itself and expose localStorage/cookies/DOM to arbitrary user code. Where a runtime's init path fought the null-origin sandbox (like php-wasm's use of the Web Locks API, which throws under an opaque origin), the fix was to work around it inside the sandbox doc rather than loosen the iframe. Go doesn't have a practical in-browser compiler, so Go support meant interpreting source with Yaegi instead of compiling it — compiled to GOOS=wasip1 GOARCH=wasm rather than the classic GOOS=js + wasm_exec.js pairing, since a WASI module's _start runs main() to completion in one shot instead of requiring a long-lived instance polled for a JS global. Keeping /js and /ts lightweight while adding four much heavier runtimes (Pyodide and the Ruby/PHP wasm builds are tens of megabytes) required strict discipline: every CDN-loaded runtime is referenced only via URL string inside its own sandbox builder function, never imported at the top level of any shared module, so the four extra languages cost nothing for visitors who only ever touch JavaScript or TypeScript. Getting a real TypeScript compile step working reliably meant abandoning Monaco's getTypeScriptWorker() for emitting JS — it reliably raced against the worker's async model sync — in favor of reading diagnostics from the already-computed editor markers and doing the actual transpile with the real typescript package's ts.transpileModule(), dynamically imported only on the TypeScript path. Code Playground now offers six fully client-side language sandboxes behind one consistent editor experience — JavaScript, TypeScript, Python, Ruby, Go, and PHP — each with live console output, persistent per-language state, and zero backend code execution. The language-agnostic editor architecture means the project can keep growing to new languages without the core app ever needing to change.
This project demonstrates expertise in building high-performance frontend applications, designing developer-focused user experiences, and implementing browser-native solutions that balance functionality, security, and maintainability.

Related projects

Mermaid Playground

Mermaid Playground

React
NextJs
Tailwind CSS
shadcn/ui
A live browser editor for writing and previewing Mermaid diagrams
Mock API

Mock API

React
NextJs
Tailwindcss
A fake REST API for testing and prototyping frontend applications