@plugdash/codeblock

codeblock

420 words·3min read

What it does

Renders the code blocks EmDash already stores using Shiki syntax highlighting. Highlighting runs server-side at render time, so the browser gets plain coloured HTML and no JavaScript. Nothing is rewritten on save - change the theme and every existing post picks it up on the next render.

codeblock is a Native plugin - it needs Shiki, which is a Node dependency, so it can't be sandboxed or published to the marketplace. It registers no new editor block type; it renders the codeblock EmDash already has.

Install

npm install @plugdash/codeblock

Register

Open astro.config.mjs at the root of your Astro project. Import the plugin at the top, then add it to the pluginsarray of the emdash() integration - the one that sits inside Astro's top-level integrations array:

astro.config.mjsjs
import { defineConfig } from "astro/config"
import emdash from "emdash/astro"
import { codeblockPlugin } from "@plugdash/codeblock"

export default defineConfig({
  integrations: [
    emdash({
      // native - must be in plugins, not sandboxed
      plugins: [
        codeblockPlugin({ theme: "github-dark", lineNumbers: true }),
      ],
    }),
  ],
})

Configuration

All options are optional. Defaults cover most sites out of the box.

OptionTypeDefaultDescription
themestringgithub-darkShiki theme name. Unknown names fall back to the default.
lightThemestring-Second theme for light mode, emitted as CSS variables.
langsstring[]common setLanguages loaded up front. Anything else loads on first use.
lineNumbersbooleanfalseRender line numbers in the gutter.

Preloaded by default: TypeScript, JavaScript, Python, Go, Rust, Shell, JSON, YAML, Markdown, HTML, CSS, SQL.

Add the component

The component is auto-wired into <PortableText>through componentsEntry - no manual block mapping needed. Once the plugin is registered, existing code blocks start rendering highlighted.

For direct usage outside Portable Text, import the component:

astro
---
import CodeBlock from "@plugdash/codeblock/CodeBlock.astro"
---
<CodeBlock code={source} language="python" filename="app.py" lineNumbers />

Live demo

Rendered by the real CodeBlock.astro on this page. The last one uses a language Shiki doesn't know, so it falls back to plaintext.

src/lib/posts.tstypescript
import { getEmDashCollection } from "emdash"

export async function latestPosts(limit = 5) {
  const { entries } = await getEmDashCollection("posts")
  return entries
    .filter((p) => p.data.status === "published")
    .slice(0, limit)
}
python
def reading_time(words: int, wpm: int = 238) -> int:
    return max(1, round(words / wpm))
made-up-lang
no grammar for this one, so it falls back to plaintext

Examples

A site that wants light and dark themes to switch with the reader's system preference passes both:

js
codeblockPlugin({
  theme: "github-dark",
  lightTheme: "github-light",
})

In the editor, an author picks a language on the code block and writes the source. Nothing about the stored content changes - EmDash keeps it as a plain code block in the Portable Text body:

json
{
  "_type": "code",
  "code": "def greet(name):\n    return f\"hi {name}\"",
  "language": "python",
  "filename": "greet.py"
}

On the site, that block renders as a header bar with the filename and language, followed by Shiki's highlighted markup - a<pre class="shiki github-dark"> wrapper with a coloured <span> per token. If lineNumbersis on, a numbered gutter runs down the left edge, built from a CSS counter so the numbers never end up in a reader's copy-paste.

Props

proptypedefaultdescription
codestringSource to highlight - required to render
languagestringLanguage name. Unknown ones render as plaintext.
filenamestringShown in the header bar
themestring"github-dark"Shiki theme name
lightThemestringSecond theme for light mode
lineNumbersbooleanfalseShow the line number gutter
classstringAdditional CSS class
nodeobjectBlock data from PortableText auto-wiring

CSS custom properties

tokendescription
--plugdash-codeblock-radiusBorder radius (default 6px)
--plugdash-codeblock-paddingPadding around the code (default 1rem)
--plugdash-codeblock-sizeFont size (default 0.875rem)
--plugdash-codeblock-line-heightLine height (default 1.6)
--plugdash-codeblock-fontFont family (default monospace stack)
--plugdash-codeblock-header-paddingHeader bar padding (default 0.5rem 1rem)
--plugdash-codeblock-header-bgHeader bar background (default #1a1a1a)
--plugdash-codeblock-header-colorHeader bar text (default #9ca3af)
--plugdash-codeblock-gutter-widthLine number column width (default 2rem)
--plugdash-codeblock-gutter-colorLine number colour (default #6b7280)

Edge cases

  • Unknown or missing language renders as plaintext instead of throwing.
  • text, txt, plain, plaintext, and none all mean plaintext.
  • Empty code renders an empty <pre> block.
  • Code over 10,000 lines is cut off with a trailing truncation notice.
  • Unknown theme names fall back to github-dark.

No copy button, line highlighting, or diff view. No client-side highlighting, so no runtime theme or language switching - pick a theme (or a light/dark pair) at registration time.

For agents

After installing and registering the plugin, no further setup is needed for rendering - block components are auto-wired into<PortableText> and existing codeblocks start rendering highlighted immediately. To verify, publish a post containing a code block with a language set, then view the page source: it should contain class="shiki github-dark"(or whatever theme was configured) with coloured styleattributes on the token spans. If nothing renders differently, confirm the plugin is in the plugins array, notsandboxed - native plugins cannot run sandboxed.

built with plugdash.dev