Skip to content

Markdown Renderer

Markdown Renderer is a drop-in TypeScript library that converts Markdown into beautifully styled HTML right in the browser. Import it from a CDN with a single <script type="module"> tag, hand it a string or a URL, and it returns a fully rendered DOM element — complete with syntax-highlighted code blocks and Tailwind Typography prose styling. No bundler, no config, no framework required.

See it in action — the demo page renders its own README as a live preview: jadujoel.github.io/markdown-renderer

  • Single-Import Usage — Add one <script type="module"> tag pointing at the CDN-hosted bundle and you’re done. No npm install, no build step for consumers.
  • Three Render APIsrender(text) for raw Markdown strings, renderUrl(url) to fetch-and-render in one call, and renderUrlToElement(url, element) to inject directly into an existing DOM node.
  • Syntax Highlighting — Code blocks are highlighted via highlight.js with the Monokai Sublime theme, auto-detecting language when possible and falling back to plaintext.
  • Tailwind Typography Styling — Output is wrapped in Tailwind’s prose / dark:prose-invert classes via the @tailwindcss/typography plugin, giving Markdown content polished, readable formatting out of the box.
  • Build-Time CSS Inlining — A Bun macro (css.macro.ts) fetches the highlight.js theme CSS and runs tailwindcss at build time, then injects the combined stylesheet into the page head at runtime — no extra network requests for styling.
  • GitHub Pages CI/CD — A GitHub Actions workflow builds and deploys the library to GitHub Pages on every push to main, so the CDN bundle is always up to date.

The core module in src/index.ts wires together marked for Markdown parsing and highlight.js for code highlighting through a custom renderer:

const renderer = new marked.Renderer();
renderer.code = ({ text, lang }) => {
const language = hljs.getLanguage(lang ?? "plaintext")
=== undefined ? "plaintext" : lang ?? "plaintext";
const highlighted = hljs.highlight(text, { language }).value;
return `<pre><code class="hljs language-${language}">${highlighted}</code></pre>`;
};

The library exposes three functions — a synchronous render() for strings, and two async helpers that fetch remote Markdown first:

// Render a raw string
const { html, div } = render(markdownText);
document.body.appendChild(div);
// Fetch + render in one step
const result = await renderUrl("https://example.com/README.md");
document.body.appendChild(result.div);
// Fetch + inject into an existing element
await renderUrlToElement("https://example.com/README.md", myDiv);

One of the more interesting patterns in this project is css.macro.ts — a Bun macro that runs at build time rather than at runtime:

export async function style() {
const monokai = await fetch(
"https://cdnjs.cloudflare.com/.../monokai-sublime.min.css"
).then((res) => res.text());
await Bun.$`bunx tailwindcss -i src/tailwind.css -o dist/index.css`;
const tailwind = await Bun.file("dist/index.css").text();
return `<style>\n${monokai}\n${tailwind}</style>`;
}

This is imported with with { type: 'macro' }, so Bun evaluates the function during bundling and replaces the call site with the resulting string. The final bundle ships with all CSS inlined — zero additional HTTP requests for consumers.

LanguageTypeScript
RuntimeBrowser (ES Modules)
Markdown Parsermarked 14
Syntax Highlightinghighlight.js 11
StylingTailwind CSS 3 + @tailwindcss/typography
Build ToolBun (bundler + macros)
LintingBiome
DeploymentGitHub Pages via GitHub Actions

The source code is available on the project’s GitHub repository.