<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Debabrata</title>
    <description>The latest articles on DEV Community by Debabrata (@debabrata100).</description>
    <link>https://dev.to/debabrata100</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4036605%2F5050b1c3-c851-4d0f-b54e-68ace95b4978.png</url>
      <title>DEV Community: Debabrata</title>
      <link>https://dev.to/debabrata100</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/debabrata100"/>
    <language>en</language>
    <item>
      <title>I built a live React playground inside VS Code — no dev server required</title>
      <dc:creator>Debabrata</dc:creator>
      <pubDate>Mon, 20 Jul 2026 03:31:49 +0000</pubDate>
      <link>https://dev.to/debabrata100/i-built-a-live-react-playground-inside-vs-code-no-dev-server-required-5f9f</link>
      <guid>https://dev.to/debabrata100/i-built-a-live-react-playground-inside-vs-code-no-dev-server-required-5f9f</guid>
      <description>&lt;p&gt;How many times have you wanted to see just one component render — and ended up starting a dev server, or pasting code into CodeSandbox? I did that once too often, so I built &lt;strong&gt;ReactCanvas&lt;/strong&gt;, a VS Code extension that turns any .jsx/.tsx file into a live playground. No terminal, no localhost, no leaving the editor.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: component preview friction
&lt;/h2&gt;

&lt;p&gt;The gap between "I wrote a component" and "I can see it" is embarrassingly wide for how routine that task is.&lt;br&gt;
Say you're building a &lt;code&gt;&amp;lt;PriceTag /&amp;gt;&lt;/code&gt; in isolation. Your options today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Spin up the dev server.&lt;/strong&gt; Now you're waiting on a cold Vite/webpack start, routing to the right page, and mounting the component inside your whole app just to look at one thing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reach for CodeSandbox / StackBlitz.&lt;/strong&gt; Great tools, but you've left your editor, lost your project's context, and you're copy-pasting code back and forth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write a throwaway story or scratch route&lt;/strong&gt; and delete it later. Everyone's done it. Nobody enjoys it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these is a context switch, and context switches are where flow goes to die. What I wanted was dumb and obvious: put my cursor in a component file, hit a shortcut, and see the thing — rendered, interactive, hot-reloading — right next to the code.&lt;br&gt;
That's the whole pitch. ReactCanvas opens a preview panel beside your file and renders whatever component it exports, live as you type.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxv7luh4naw0e565f0qjm.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxv7luh4naw0e565f0qjm.gif" alt="ReactCanvas extension preview" width="720" height="342"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;The core loop is: read the file → transpile it in memory → render it in a sandboxed iframe → repeat on change. No files hit disk, and nothing runs a Node build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transpile with&lt;/strong&gt; &lt;code&gt;esbuild-wasm&lt;/code&gt;. The heavy lifter is esbuild-wasm, running entirely in the browser context of the webview. When your file changes, ReactCanvas hands the source straight to esbuild's transform API — JSX and TypeScript in, plain JS out — in single-digit milliseconds. Because it's WASM, there's no separate Node process to spawn or keep warm, which keeps the extension light and the first render fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Babel as a fallback&lt;/strong&gt;. &lt;code&gt;esbuild-wasm&lt;/code&gt; is fast but deliberately narrow — it transforms, it doesn't do everything Babel does, and occasionally a file leans on syntax or a transform esbuild won't take. Rather than fail the preview, ReactCanvas falls back to &lt;code&gt;@babel/standalone&lt;/code&gt; for those cases. You get a slower but more forgiving path instead of a red screen. Most files never touch it; the ones that do still render.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Imports via&lt;/strong&gt; &lt;code&gt;esm.sh&lt;/code&gt; &lt;strong&gt;and an import map.&lt;/strong&gt; The transpiled code still says &lt;code&gt;import React from "react"&lt;/code&gt; — so where does React come from with no bundler and no &lt;code&gt;node_modules&lt;/code&gt; in the loop? An import map. ReactCanvas injects a map that points bare specifiers at esm.sh:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"importmap"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;imports&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://esm.sh/react@18&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-dom/client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://esm.sh/react-dom@18/client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Security: this runs arbitrary code in your editor
&lt;/h2&gt;

&lt;p&gt;This is the part I'd want to read about if someone else had built it, so I'll be direct: a live playground means executing whatever code is in the file, inside VS Code, on your machine. That deserves real isolation, not a shrug.&lt;br&gt;
Two layers do the work.&lt;br&gt;
&lt;strong&gt;A sandboxed iframe.&lt;/strong&gt; The rendered component never runs in the webview's own context. It runs in a nested &lt;code&gt;&amp;lt;iframe sandbox="allow-scripts"&amp;gt;&lt;/code&gt; — enough to execute React, nothing more. No same-origin access back to the webview, no top-level navigation, no popping windows. If your (or a dependency's) code does something nasty, it's boxed in.&lt;br&gt;
&lt;strong&gt;A strict CSP with per-load nonces.&lt;/strong&gt; VS Code webviews are already locked down, but the preview generates HTML on every reload, so the Content-Security-Policy is generated with a fresh nonce each time and only scripts carrying that nonce are allowed to run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;http-equiv=&lt;/span&gt;&lt;span class="s"&gt;"Content-Security-Policy"&lt;/span&gt;
      &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"default-src 'none';
               script-src 'nonce-{NONCE}';
               style-src {webview.cspSource} 'unsafe-inline';
               connect-src https://esm.sh;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;default-src 'none'&lt;/code&gt; means everything is denied unless explicitly re-allowed, &lt;code&gt;connect-src&lt;/code&gt; is narrowed to the CDN the import map actually needs, and the nonce makes injected &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags inert. For an extension that lives inside your editor, "assume the rendered code is hostile and contain it" felt like the only responsible default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interesting problems I hit
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Debouncing reloads without feeling laggy.&lt;/strong&gt; Transpiling on every keystroke means transpiling mid-token — you type &lt;code&gt;&amp;lt;div&lt;/code&gt;, esbuild sees invalid JSX, the preview flashes an error, and by the time you finish the tag it's gone. It looks broken even though nothing is. The fix was a trailing-edge debounce on file changes (300ms) so a burst of typing collapses into one rebuild after you pause. The subtlety: when a rebuild does fail, keep the last good render on screen instead of blanking it, so a transient syntax error while typing never wipes what you were looking at.&lt;br&gt;
&lt;strong&gt;2. Error overlays with the right line numbers.&lt;/strong&gt; When a runtime error throws, the stack trace points at the transpiled output, not your source — and JSX/TS transforms shift line numbers around, so "error on line 12" in the compiled code might be line 8 of what you wrote. Useless. The answer was to keep esbuild's sourcemap from the transform step and map the thrown error's position back through it before rendering the overlay. Now the error points at the line you'd actually go fix, which is the entire point of an error overlay.&lt;br&gt;
&lt;strong&gt;3. Theme-aware chrome&lt;/strong&gt;.A preview panel that's blinding white while your editor is dark is jarring. Rather than ship my own theme, the playground's UI is styled entirely with VS Code's own CSS variables — &lt;code&gt;var(--vscode-editor-background)&lt;/code&gt;, &lt;code&gt;var(--vscode-editor-foreground)&lt;/code&gt;, and friends. That means it inherits your theme automatically, including light, dark, and high-contrast, and it follows along the instant you switch themes without ReactCanvas knowing or caring which one you picked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations &amp;amp; roadmap
&lt;/h2&gt;

&lt;p&gt;Being upfront: today ReactCanvas previews a &lt;strong&gt;single file&lt;/strong&gt;. If your component imports a sibling ./Button or a local helper, v1 can't resolve it — bare npm specifiers go through the import map, but relative project imports don't yet.&lt;br&gt;
&lt;strong&gt;Multi-file imports are the headline feature for v2.&lt;/strong&gt; The plan is to walk the local import graph, transpile the dependencies alongside the entry file, and stitch them into the same in-memory module system so ./Button just works. That unlocks previewing real components instead of only self-contained ones, and it's the thing I most want for my own use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;ReactCanvas is on the VS Code Marketplace (publisher debabrata100) — search &lt;strong&gt;"ReactCanvas"&lt;/strong&gt; in the Extensions panel, or grab it here:&lt;/p&gt;

&lt;p&gt;Marketplace: &lt;a href="https://marketplace.visualstudio.com/items?itemName=debabrata100.reactcanvas" rel="noopener noreferrer"&gt;https://marketplace.visualstudio.com/items?itemName=debabrata100.reactcanvas&lt;/a&gt;&lt;br&gt;
Repo: &lt;a href="https://github.com/debabrata100/reactcanvas" rel="noopener noreferrer"&gt;https://github.com/debabrata100/reactcanvas&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you try it, I'd love feedback — especially on the transpile fallback and where multi-file resolution would help you most. What's the first component you'd point it at? Drop a comment, and a ⭐ on the repo helps me prioritize v2.&lt;/p&gt;

</description>
      <category>react</category>
      <category>vscode</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
