Custom markdown theme in VitePress
WARNING
vitepress
migrated to shiki
from shikiji
since 1.0.0-rc41, this approach may have been deprecated.
What are textmate rules
TextMate grammars are for syntax highlighting and language support. The rules are defined in JSON format.
How VitePress manage markdown themes
VitePress
uses shikiji
to perform highlighting(built-in themes are also from shikiji
). To figure out how to create an theme instance with correct type, let's check source code of VitePress, shiki
and shikiji
.
// from vitepress source
import { IThemeRegistration } from 'shiki';
type ThemeOptions = IThemeRegistration | {
light: IThemeRegistration;
dark: IThemeRegistration;
};
interface MarkdownOptions extends MarkdownIt.Options {
// ...
theme?: ThemeOptions;
// ...
}
2
3
4
5
6
7
8
9
10
11
12
// from shiki source
type IThemeRegistration = IShikiTheme | StringLiteralUnion<Theme>;
interface IShikiTheme extends IRawTheme { ... }
interface ThemeRegistrationRaw extends IRawTheme { }
interface ThemeRegistration extends ThemeRegistrationRaw { ... }
2
3
4
5
6
7
8
Luckily we have these methods from shikiji
so we can perform registration for themes. And for those abstraction relationships, we can pass ThemeRegistration
instance to theme?: ThemeOptions
// from shikiji
declare const getSingletonHighlighter: () => Promise<HighlighterGeneric< /* very long union type */ >;
2
// from shikiji
interface HighlighterGeneric<BundledLangKeys extends string, BundledThemeKeys extends string> {
/**
* Load a theme to the highlighter, so later it can be used synchronously.
*/
loadTheme(...themes: (ThemeInput | BundledThemeKeys)[]): Promise<void>;
/**
* Get the theme registration object
*/
getTheme(name: string | ThemeRegistration | ThemeRegistrationRaw): ThemeRegistration;
/**
* Get the names of loaded themes
*/
getLoadedThemes(): string[];
// ...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Parse json into shikiji.ThemeRegistration
First, get the theme represented as json you desired. If you are using built-in themes from
shikiji
you can find them innode_modules/shikiji/dist/themes/*.mjs
. The anon object in the module is the presentation of the theme. No matter what method you take, make it a json and include it in your project.Next, we find a way to register the modified theme.
import * as shikiji from 'shikiji';
import * as fs from 'fs';
// assuming that we have these theme available in somewhere. Then find its path by name.
type CustomMarkdownTheme = 'Eva-Dark' | 'Eva-Light' | 'Rider-Dark' | 'Darcula' | 'vscode-dark-plus';
export async function getRegisteredMarkdownTheme(theme: CustomMarkdownTheme): Promise<shikiji.ThemeRegistration> {
let isThemeRegistered = (await shikiji.getSingletonHighlighter())
.getLoadedThemes() // this method returns names of loaded themes, name is specified in each textmate rule json.
.find(x => x === theme);
if (!isThemeRegistered) {
const myTheme = JSON.parse(
fs.readFileSync(/* find the path of your json */), 'utf8')
);
(await shikiji.getSingletonHighlighter()).loadTheme(myTheme);
}
return (await shikiji.getSingletonHighlighter()).getTheme(theme);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Specify themes in config.mts
Finally we can specify the theme in config, but this solution does not support hot reload for the theme json. If you need to reload the theme, please rebuild.
// config.mts
export default defineConfig({
markdown: {
lineNumbers: true,
theme: {
light: await getRegisteredMarkdownTheme('Eva-Light'),
dark: await getRegisteredMarkdownTheme('vscode-dark-plus'),
},
},...
}
2
3
4
5
6
7
8
9
10