You will install shiki and rehype-pretty-code, then assemble a minimal unified pipeline that turns Markdown into HTML with syntax highlighting. At the end of the episode there is a first code block ready to use in your project.

The architecture theory in episode 2 is enough. Now you'll get your hands dirty with real code. Episode 3 is the setup and hello world: installing dependencies, assembling a minimal pipeline, and seeing the first highlighted HTML produced from plain Markdown.
The goal of this episode is simple but important: a Node.js script that converts a Markdown string containing a code block into full HTML with syntax highlighting. From this point you can test every option discussed in the following episodes.
Start from an empty folder. Initialize the project with npm or bun, then install the two main packages. Both are ESM-only, so make sure the value "type": "module" is present in package.json.
mkdir learn-shiki
cd learn-shiki
npm init -y
npm i shiki rehype-pretty-codeThe rehype-pretty-code package pulls shiki in as a dependency, but installing it explicitly makes it easier to manage versions and use the Shiki API directly when needed. It's best to always install both so the versions in use are clear.
Besides the two main packages, the unified pipeline needs the official plugins from the unified ecosystem:
npm i unified remark-parse remark-rehype rehype-stringifyunified is the pipeline core, remark-parse turns Markdown into an MDAST tree, remark-rehype converts it to HAST, and rehype-stringify produces the final HTML string. Without these four, rehype-pretty-code has no stage to work on.
Create a highlight.mjs file with the following contents. This pipeline accepts Markdown text and returns highlighted HTML.
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import rehypePrettyCode from "rehype-pretty-code";
import rehypeStringify from "rehype-stringify";
const md = [
"```ts",
'const pesan: string = "Halo Shiki";',
"console.log(pesan);",
"```",
].join("\n");
const html = await unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypePrettyCode, { theme: "github-dark-default" })
.use(rehypeStringify)
.process(md);
console.log(html.toString());Notice the .use() order that must be exact: parse, then conversion to HAST, then rehype-pretty-code, finally stringify. rehypePrettyCode(options) is installed right before rehypeStringify.
Run the script to see the generated HTML:
node highlight.mjsThe output will contain <pre> and <code> with many color-styled <span> elements. This is where the magic happens: the TypeScript code has been tokenized and colored without a single piece of JavaScript running in the browser.
To see the visual result, modify the script to write the output to an output.html file and open it in the browser:
import { writeFile } from "node:fs/promises";
const hasil = await unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypePrettyCode, { theme: "github-dark-default" })
.use(rehypeStringify)
.process(md);
await writeFile("output.html", hasil.toString());node highlight.mjs
xdg-open output.htmlIn the browser, you'll see code colored according to the github-dark-default theme. Open DevTools and inspect the element structure: every token is wrapped in a <span> with an inline color. This structure will change slightly later when rehype-pretty-code adds data attributes in episode 4.
If you see text without colors, it's likely that the pipeline isn't running rehype-pretty-code at all, or the theme name used is wrong. Re-check the plugin order and the spelling of the theme name.
Key takeaways:
rehypePrettyCode is installed after remarkRehype and before rehypeStringify.In episode 4 you'll dissect the basic options of rehype-pretty-code: theme, keepBackground, defaultLang, grid, and bypassInlineCode, plus understand the <figure>, <pre>, and <code> structure along with the data attributes generated. All these options will shape the way you present code.