Live code demos, straight from markdown
Every code fence on this blog can run. Mark it `live` and the markdown itself becomes the demo β no CodePen, no embeds, versioned with the post.
Code on a developer blog should do two things: read well and run. Most blogs outsource the second half to CodePen. We wanted the code to live in the markdown file, in git, next to the words.
So on this blog, a fence is just a fence:
const grains = new Uint8Array(cols * rows)
const below = i + cols // fall straight down
β¦until you mark it live. Then the same fence renders and runs, sandboxed
in an iframe:
<canvas id="c" width="400" height="240"></canvas>
<style>
body { margin: 0; display: grid; place-items: center; min-height: 100vh; background: #111; }
canvas { image-rendering: pixelated; width: min(100%, 400px); }
</style>
<script>
// A falling-sand toy in ~30 lines. Click to pour.
const ctx = c.getContext('2d')
const W = 100, H = 60, S = 4
let g = new Uint8Array(W * H)
let pour = {x: 50, y: 5, on: true}
c.addEventListener('pointermove', e => {
const r = c.getBoundingClientRect()
pour.x = Math.floor((e.clientX - r.left) / r.width * W)
pour.y = Math.floor((e.clientY - r.top) / r.height * H)
})
function step() {
if (pour.on) g[pour.y * W + pour.x] = 1 + (Math.random() * 3 | 0)
for (let y = H - 2; y >= 0; y--)
for (let x = 0; x < W; x++) {
const i = y * W + x, b = i + W
if (!g[i]) continue
if (!g[b]) { g[b] = g[i]; g[i] = 0; continue }
const d = Math.random() < 0.5 ? -1 : 1
if (x + d >= 0 && x + d < W && !g[b + d]) { g[b + d] = g[i]; g[i] = 0 }
}
ctx.fillStyle = '#111'
ctx.fillRect(0, 0, 400, 240)
const colors = ['', '#d4a24e', '#c8923a', '#e0b366']
for (let i = 0; i < g.length; i++)
if (g[i]) {
ctx.fillStyle = colors[g[i]]
ctx.fillRect(i % W * S, (i / W | 0) * S, S, S)
}
requestAnimationFrame(step)
}
step()
</script>
Move your pointer over the canvas β the pour follows it.
How it works
Nuxt Content lets you replace any prose component. ProsePre.vue receives
every fence's code, language, and meta string. When meta contains
live and the language is html, the component renders the highlighted code
as usual, then feeds the same string into a sandboxed <iframe srcdoc>:
<iframe v-if="live" :srcdoc="code" sandbox="allow-scripts" />
sandbox="allow-scripts" (without allow-same-origin) means the demo can run
scripts but can't touch cookies, storage, or the parent page. The code is
versioned with the article β edit the markdown, the demo updates.
No build step, no dependency, ~90 lines total.