You will learn to import local grammars and themes, register only the languages actually used, and keep the bundle size down with selective langs and themes selection and lazy loading.

Shiki bundles hundreds of languages and dozens of themes. Loading all of them burdens the bundle, even though most projects only use a handful of languages. In this episode you learn to manage langs and themes so the bundle stays light without losing capability.
Episode 12 covers how to import local grammars and themes, register languages selectively, use language aliases, and lazy loading strategies. With proper management, Shiki's build time stays fast and your static bundle doesn't bloat.
Every grammar is a TextMate file containing hundreds of regex rules. Loading all grammars at once slows startup and adds to the bundle size. Shiki is aware of this and provides several control points: from a full bundle, a web bundle, to per-language imports.
Since v4, languages and themes are split into the @shikijs/langs and @shikijs/themes packages. This lets bundlers split files per language so only what's needed is downloaded.
shiki/bundle/full loads all languages and themes, suitable for generic tooling.shiki/bundle/web loads popular web languages like HTML, CSS, JavaScript, TypeScript, and JSON.shiki/core loads nothing; everything must be imported manually.For blogs or documentation, shiki/bundle/web is usually enough. Choose shiki/core only if bundle size is extremely critical.
Grammars don't only come from the bundle; you can load your own .tmLanguage.json. Read the file then put the grammar object into the langs array:
import { createHighlighter } from "shiki";
import { readFileSync } from "node:fs";
const grammar = JSON.parse(
readFileSync("./grammars/my-lang.tmLanguage.json", "utf8"),
);
const highlighter = await createHighlighter({
langs: [grammar],
themes: ["github-dark-default"],
});A grammar object may carry name, scopeName, and embeddedLangs. Once registered, you can use the grammar name as a language in codeToHtml.
A theme is a TextMate object with name, type, and settings properties. You can write it directly or read it from a JSON file:
const myTheme = {
name: "my-theme",
type: "dark",
settings: [
{
scope: ["comment"],
settings: { foreground: "#8b949e" },
},
],
};
const highlighter = await createHighlighter({
langs: ["typescript"],
themes: [myTheme],
});A custom theme is useful when your brand has its own palette. Its structure is standard TextMate, so tools like VSCode can produce these files.
In rehype-pretty-code, you register languages and themes via the langs and themes options:
const options = {
theme: "github-dark-default",
langs: ["ts", "tsx", "js", "css", "json", "bash"],
themes: ["github-dark-default", "github-light-default"],
};The langs option accepts a list of language ids to be highlighted across all content. Languages outside this list will be rendered without colors, so make sure the list covers all the languages appearing in your articles.
Some authors use short aliases like js or py. Shiki already recognizes common aliases, but for custom names you can register langAlias:
const highlighter = await createHighlighter({
langs: ["javascript", "typescript"],
langAlias: {
mylang: "typescript",
},
themes: ["github-dark-default"],
});With langAlias, you can write a fenced code block with the mylang language meta and Shiki maps it to TypeScript. This feature keeps authors comfortable with internal project terms.
Use dynamic import functions so the bundler splits languages into separate chunks loaded only when needed:
import { createHighlighterCore } from "shiki/core";
import { createOnigurumaEngine } from "shiki/engine/oniguruma";
const highlighter = await createHighlighterCore({
themes: [
import("@shikijs/themes/github-dark-default"),
],
langs: [
import("@shikijs/langs/typescript"),
() => import("@shikijs/langs/javascript"),
],
engine: createOnigurumaEngine(import("shiki/wasm")),
});Notice that shiki/core here loads no languages or themes at all. Each dynamic import becomes a separate chunk, so only the grammars actually used are downloaded.
Compare the bundle size before and after selection. The bun run build command will show a size report in the terminal. After being selective, the grammar chunk size usually drops drastically compared to a full bundle that can reach megabytes in uncompressed form.
Key takeaways:
shiki/bundle/full and shiki/bundle/web provide ready-to-use presets.langs and themes options.langAlias maps custom language names to already-registered grammars.shiki/core plus dynamic imports makes each language a separate chunk.In episode 13 you'll learn performance and bundle optimization: the Oniguruma WASM loading mechanism, choosing an engine like @shikijs/engine-oniguruma, the difference between build-time and client-side highlighting, and grammar caching to avoid expensive re-renders.