Skip to content

Commit d58b5a6

Browse files
Cleanup code + constants
1 parent 1e76030 commit d58b5a6

File tree

3 files changed

+112
-85
lines changed

3 files changed

+112
-85
lines changed
Lines changed: 29 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,40 @@
11
import type { Element, Root } from 'hast';
22
import { toString } from 'hast-util-to-string';
3-
import { createHighlighter, type Highlighter, type BuiltinTheme, type BundledTheme } from 'shiki';
3+
import { createHighlighter, type Highlighter } from 'shiki';
44
import type { Plugin } from 'unified';
55
import { visit } from 'unist-util-visit';
66

77
import {
8+
type ShikiLang,
9+
type ShikiTheme,
10+
shikiColorReplacements,
811
DEFAULT_LANG_ALIASES,
912
SHIKI_THEMES,
1013
UNIQUE_LANGS,
1114
DEFAULT_LANG,
12-
type ShikiLang,
13-
type ShikiTheme,
15+
DEFAULT_DARK_THEME,
16+
DEFAULT_LIGHT_THEME,
17+
DEFAULT_THEMES,
1418
} from './shiki-constants.js';
15-
16-
const shikiColorReplacements: Partial<Record<BundledTheme, string | Record<string, string>>> = {
17-
'dark-plus': {
18-
'#1e1e1e': 'transparent',
19-
'#569cd6': '#9cdcfe',
20-
'#c8c8c8': '#f3f7f6',
21-
'#d4d4d4': '#f3f7f6',
22-
},
23-
'github-light-default': {
24-
'#fff': 'transparent',
25-
'#ffffff': 'transparent',
26-
},
27-
};
19+
import {
20+
getLanguage,
21+
getLinesToHighlight,
22+
lineHighlightPattern,
23+
LINE_HIGHLIGHT_CLASS,
24+
} from './utils.js';
2825

2926
export type RehypeSyntaxHighlightingOptions = {
30-
theme?: BuiltinTheme;
31-
themes?: Record<'light' | 'dark', BuiltinTheme>;
27+
theme?: ShikiTheme;
28+
themes?: Record<'light' | 'dark', ShikiTheme>;
3229
codeStyling?: 'dark' | 'system';
3330
};
3431

35-
const lineHighlightPattern = /\{(.*?)\}/;
36-
37-
function classNameOrEmptyArray(element: Element): string[] {
38-
const className = element.properties.className;
39-
if (Array.isArray(className) && className.every((el) => typeof el === 'string')) return className;
40-
return [];
41-
}
42-
4332
let highlighterPromise: Promise<Highlighter> | null = null;
4433

4534
async function getHighlighter(): Promise<Highlighter> {
4635
if (!highlighterPromise) {
4736
highlighterPromise = createHighlighter({
48-
themes: ['github-light-default', 'dark-plus'],
37+
themes: DEFAULT_THEMES,
4938
langs: UNIQUE_LANGS,
5039
});
5140
}
@@ -57,14 +46,18 @@ export const rehypeSyntaxHighlighting: Plugin<[RehypeSyntaxHighlightingOptions?]
5746
) => {
5847
return async (tree) => {
5948
const highlighter = await getHighlighter();
60-
if (options.theme) {
49+
if (options.theme && !DEFAULT_THEMES.includes(options.theme)) {
6150
await highlighter.loadTheme(options.theme);
6251
}
6352
if (options.themes) {
64-
await Promise.all([
65-
highlighter.loadTheme(options.themes.dark),
66-
highlighter.loadTheme(options.themes.light),
67-
]);
53+
const promises: Promise<void>[] = [];
54+
if (!DEFAULT_THEMES.includes(options.themes.dark)) {
55+
promises.push(highlighter.loadTheme(options.themes.dark));
56+
}
57+
if (!DEFAULT_THEMES.includes(options.themes.light)) {
58+
promises.push(highlighter.loadTheme(options.themes.light));
59+
}
60+
await Promise.all(promises);
6861
}
6962

7063
visit(tree, 'element', (node, index, parent) => {
@@ -106,8 +99,8 @@ export const rehypeSyntaxHighlighting: Plugin<[RehypeSyntaxHighlightingOptions?]
10699
light:
107100
options.themes?.light ??
108101
options.theme ??
109-
(options.codeStyling === 'dark' ? 'dark-plus' : 'github-light-default'),
110-
dark: options.themes?.dark ?? options.theme ?? 'dark-plus',
102+
(options.codeStyling === 'dark' ? DEFAULT_DARK_THEME : DEFAULT_LIGHT_THEME),
103+
dark: options.themes?.dark ?? options.theme ?? DEFAULT_DARK_THEME,
111104
},
112105
colorReplacements: shikiColorReplacements,
113106
tabindex: false,
@@ -134,9 +127,9 @@ export const rehypeSyntaxHighlighting: Plugin<[RehypeSyntaxHighlightingOptions?]
134127
lineNumber++;
135128
if (linesToHighlight.includes(lineNumber)) {
136129
if (typeof span.properties.class === 'string') {
137-
span.properties.class += ' line-highlight';
130+
span.properties.class += ' ' + LINE_HIGHLIGHT_CLASS;
138131
} else {
139-
span.properties.class = [...span.properties.class, 'line-highlight'];
132+
span.properties.class = [...span.properties.class, LINE_HIGHLIGHT_CLASS];
140133
}
141134
}
142135
});
@@ -165,50 +158,4 @@ export const rehypeSyntaxHighlighting: Plugin<[RehypeSyntaxHighlightingOptions?]
165158
};
166159
};
167160

168-
function getLanguage(node: Element, aliases: Record<string, ShikiLang>): ShikiLang | undefined {
169-
const className = classNameOrEmptyArray(node);
170-
171-
for (const classListItem of className) {
172-
if (classListItem.startsWith('language-')) {
173-
const lang = classListItem.slice(9).toLowerCase();
174-
if (lang) return aliases[lang];
175-
}
176-
}
177-
178-
return undefined;
179-
}
180-
181-
function getLinesToHighlight(node: Element, maxLines: number): number[] {
182-
const meta =
183-
typeof node.data?.meta === 'string'
184-
? node.data.meta
185-
: classNameOrEmptyArray(node).reduce((acc, item) => acc + ' ' + item, '');
186-
if (!meta) return [];
187-
188-
const content = meta.match(lineHighlightPattern)?.[1]?.trim();
189-
if (!content) return [];
190-
191-
const lineNumbers = new Set<number>();
192-
193-
content.split(',').forEach((part) => {
194-
const [start, end] = part.split('-').map((num) => {
195-
const trimmed = num.trim();
196-
if (!/^\d+$/.test(trimmed)) return undefined;
197-
const parsed = parseInt(trimmed, 10);
198-
return parsed > maxLines ? maxLines : parsed;
199-
});
200-
201-
if (!start) return;
202-
const endLine = end ?? start;
203-
204-
if (endLine < start) return;
205-
const max = Math.min(endLine, maxLines);
206-
for (let i = start; i <= max; i++) {
207-
lineNumbers.add(i);
208-
}
209-
});
210-
211-
return Array.from(lineNumbers).sort((a, b) => a - b);
212-
}
213-
214161
export { UNIQUE_LANGS, DEFAULT_LANG_ALIASES, SHIKI_THEMES, ShikiLang, ShikiTheme };

packages/mdx/src/plugins/rehype/shiki-constants.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,29 @@
1-
import type { BundledLanguage } from 'shiki';
1+
import type { BundledLanguage } from 'shiki/types';
22

33
export type ShikiLang = BundledLanguage | 'text';
4+
export type ShikiTheme = (typeof SHIKI_THEMES)[number];
45

56
export const DEFAULT_LANG = 'text' as const;
7+
export const DEFAULT_DARK_THEME: ShikiTheme = 'dark-plus' as const;
8+
export const DEFAULT_LIGHT_THEME: ShikiTheme = 'github-light-default' as const;
9+
export const DEFAULT_THEMES: [ShikiTheme, ShikiTheme] = [
10+
DEFAULT_LIGHT_THEME,
11+
DEFAULT_DARK_THEME,
12+
] as const;
13+
14+
export const shikiColorReplacements: Partial<Record<ShikiTheme, string | Record<string, string>>> =
15+
{
16+
'dark-plus': {
17+
'#1e1e1e': 'transparent',
18+
'#569cd6': '#9cdcfe',
19+
'#c8c8c8': '#f3f7f6',
20+
'#d4d4d4': '#f3f7f6',
21+
},
22+
'github-light-default': {
23+
'#fff': 'transparent',
24+
'#ffffff': 'transparent',
25+
},
26+
};
627

728
export const DEFAULT_LANG_ALIASES: Record<string, ShikiLang> = {
829
abap: 'abap',
@@ -383,5 +404,3 @@ export const SHIKI_THEMES = [
383404
'vitesse-dark',
384405
'vitesse-light',
385406
] as const;
386-
387-
export type ShikiTheme = (typeof SHIKI_THEMES)[number];
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import type { Element } from 'hast';
2+
3+
import { type ShikiLang } from './shiki-constants.js';
4+
5+
export const lineHighlightPattern = /\{(.*?)\}/;
6+
export const LINE_HIGHLIGHT_CLASS = 'line-highlight';
7+
8+
export function classNameOrEmptyArray(element: Element): string[] {
9+
const className = element.properties.className;
10+
if (Array.isArray(className) && className.every((el) => typeof el === 'string')) return className;
11+
return [];
12+
}
13+
14+
export function getLanguage(
15+
node: Element,
16+
aliases: Record<string, ShikiLang>
17+
): ShikiLang | undefined {
18+
const className = classNameOrEmptyArray(node);
19+
20+
for (const classListItem of className) {
21+
if (classListItem.startsWith('language-')) {
22+
const lang = classListItem.slice(9).toLowerCase();
23+
if (lang) return aliases[lang];
24+
}
25+
}
26+
27+
return undefined;
28+
}
29+
30+
export function getLinesToHighlight(node: Element, maxLines: number): number[] {
31+
const meta =
32+
typeof node.data?.meta === 'string'
33+
? node.data.meta
34+
: classNameOrEmptyArray(node).reduce((acc, item) => acc + ' ' + item, '');
35+
if (!meta) return [];
36+
37+
const content = meta.match(lineHighlightPattern)?.[1]?.trim();
38+
if (!content) return [];
39+
40+
const lineNumbers = new Set<number>();
41+
42+
content.split(',').forEach((part) => {
43+
const [start, end] = part.split('-').map((num) => {
44+
const trimmed = num.trim();
45+
if (!/^\d+$/.test(trimmed)) return undefined;
46+
const parsed = parseInt(trimmed, 10);
47+
return parsed > maxLines ? maxLines : parsed;
48+
});
49+
50+
if (!start) return;
51+
const endLine = end ?? start;
52+
53+
if (endLine < start) return;
54+
const max = Math.min(endLine, maxLines);
55+
for (let i = start; i <= max; i++) {
56+
lineNumbers.add(i);
57+
}
58+
});
59+
60+
return Array.from(lineNumbers).sort((a, b) => a - b);
61+
}

0 commit comments

Comments
 (0)