Skip to content

Commit 3df41ac

Browse files
AVGVSTVS96claude
andcommitted
refactor: consolidate resolvers into domain-focused modules
Split resolvers.ts into theme.ts and language.ts for better organization: - theme.ts: resolveTheme, toShikiOptions, getMultiThemeOptions, DEFAULT_THEMES - language.ts: resolveLanguage, isPlaintext helper - options.ts: simplified to import from theme.ts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 8af3f37 commit 3df41ac

File tree

6 files changed

+201
-44
lines changed

6 files changed

+201
-44
lines changed

package/src/lib/component.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import './styles.css';
22
import { clsx } from 'clsx';
3-
import { resolveLanguage } from './resolvers';
3+
import { resolveLanguage } from './language';
44

55
import type {
66
HighlighterOptions,

package/src/lib/hook.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ import type {
2020
} from './types';
2121

2222
import { throttleHighlighting, useStableOptions } from './utils';
23-
import { resolveLanguage, resolveTheme } from './resolvers';
23+
import { resolveLanguage } from './language';
24+
import { resolveTheme } from './theme';
2425
import { buildShikiOptions } from './options';
2526
import { transformOutput } from './output';
2627

package/src/lib/language.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import type { Language } from './types';
2+
import type { LanguageRegistration } from './extended-types';
3+
4+
export type LanguageResolution = {
5+
languageId: string;
6+
displayLanguageId: string | null;
7+
langsToLoad: Language;
8+
};
9+
10+
export const resolveLanguage = (
11+
lang: Language,
12+
customLanguages?: LanguageRegistration | LanguageRegistration[],
13+
langAliases?: Record<string, string>
14+
): LanguageResolution => {
15+
const normalizedCustomLangs = customLanguages
16+
? Array.isArray(customLanguages)
17+
? customLanguages
18+
: [customLanguages]
19+
: [];
20+
21+
if (lang == null || (typeof lang === 'string' && !lang.trim())) {
22+
return {
23+
languageId: 'plaintext',
24+
displayLanguageId: 'plaintext',
25+
langsToLoad: undefined,
26+
};
27+
}
28+
29+
if (typeof lang === 'object') {
30+
return {
31+
languageId: lang.name,
32+
displayLanguageId: lang.name || null,
33+
langsToLoad: lang,
34+
};
35+
}
36+
37+
const lowerLang = lang.toLowerCase();
38+
const matches = (str: string | undefined): boolean =>
39+
str?.toLowerCase() === lowerLang;
40+
41+
const customMatch = normalizedCustomLangs.find(
42+
(cl) =>
43+
matches(cl.name) ||
44+
matches(cl.scopeName) ||
45+
matches(cl.scopeName?.split('.').pop()) ||
46+
cl.aliases?.some(matches) ||
47+
cl.fileTypes?.some(matches)
48+
);
49+
50+
if (customMatch) {
51+
return {
52+
languageId: customMatch.name || lang,
53+
displayLanguageId: lang,
54+
langsToLoad: customMatch,
55+
};
56+
}
57+
58+
if (langAliases?.[lang]) {
59+
return {
60+
languageId: langAliases[lang],
61+
displayLanguageId: lang,
62+
langsToLoad: langAliases[lang],
63+
};
64+
}
65+
66+
// Fallback handled in highlighter factories
67+
return {
68+
languageId: lang,
69+
displayLanguageId: lang,
70+
langsToLoad: lang,
71+
};
72+
};
73+
74+
const PLAINTEXT_LANGS = new Set(['text', 'plaintext', 'txt', 'plain']);
75+
76+
export const isPlaintext = (resolution: LanguageResolution): boolean =>
77+
PLAINTEXT_LANGS.has(resolution.languageId);

package/src/lib/options.ts

Lines changed: 11 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,13 @@
1-
import type {
2-
CodeToHastOptions,
3-
CodeOptionsSingleTheme,
4-
CodeOptionsMultipleThemes,
5-
BundledTheme,
6-
} from 'shiki';
1+
import type { CodeToHastOptions } from 'shiki';
72

8-
import type { HighlighterOptions, OutputFormat, Themes } from './types';
9-
import type { ThemeResult } from './resolvers';
3+
import type { HighlighterOptions, OutputFormat } from './types';
4+
import type { ThemeResolution } from './theme';
5+
import { toShikiOptions, getMultiThemeOptions } from './theme';
106
import { lineNumbersTransformer } from './transformers';
117

12-
const DEFAULT_THEMES: Themes = {
13-
light: 'github-light',
14-
dark: 'github-dark',
15-
};
16-
17-
const buildThemeOptions = (
18-
themeResult: ThemeResult
19-
):
20-
| CodeOptionsSingleTheme<BundledTheme>
21-
| CodeOptionsMultipleThemes<BundledTheme> => {
22-
const { isMultiTheme, multiTheme, singleTheme } = themeResult;
23-
24-
if (isMultiTheme) {
25-
return {
26-
themes: multiTheme || DEFAULT_THEMES,
27-
} as CodeOptionsMultipleThemes<BundledTheme>;
28-
}
29-
30-
return {
31-
theme: singleTheme || DEFAULT_THEMES.dark,
32-
} as CodeOptionsSingleTheme<BundledTheme>;
33-
};
34-
358
export const buildShikiOptions = <F extends OutputFormat>(
369
languageId: string,
37-
themeResult: ThemeResult,
10+
themeResolution: ThemeResolution,
3811
options: HighlighterOptions<F>
3912
): CodeToHastOptions => {
4013
const {
@@ -51,23 +24,20 @@ export const buildShikiOptions = <F extends OutputFormat>(
5124
...restOptions
5225
} = options;
5326

54-
const themeOptions = buildThemeOptions(themeResult);
55-
const multiThemeOptions = themeResult.isMultiTheme
56-
? { defaultColor, cssVariablePrefix }
57-
: {};
58-
5927
const transformers = [...(restOptions.transformers || [])];
6028
if (showLineNumbers && outputFormat !== 'tokens') {
6129
transformers.push(lineNumbersTransformer(startingLineNumber));
6230
}
6331

6432
return {
6533
lang: languageId,
66-
...themeOptions,
67-
...multiThemeOptions,
34+
...toShikiOptions(themeResolution),
35+
...getMultiThemeOptions(
36+
themeResolution,
37+
defaultColor,
38+
cssVariablePrefix
39+
),
6840
...restOptions,
6941
transformers,
7042
};
7143
};
72-
73-
export { DEFAULT_THEMES };

package/src/lib/theme.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import type {
2+
CodeOptionsSingleTheme,
3+
CodeOptionsMultipleThemes,
4+
BundledTheme,
5+
} from 'shiki';
6+
import type { ThemeRegistrationAny } from 'shiki/core';
7+
import type { Theme, Themes } from './types';
8+
9+
export const DEFAULT_THEMES: Themes = {
10+
light: 'github-light',
11+
dark: 'github-dark',
12+
};
13+
14+
export interface ThemeResolution {
15+
isMultiTheme: boolean;
16+
themeId: Theme;
17+
multiTheme?: Themes | ThemeRegistrationAny | null;
18+
singleTheme?: Theme | undefined;
19+
themesToLoad: Theme[];
20+
}
21+
22+
export type ThemeShikiOptions =
23+
| CodeOptionsSingleTheme<BundledTheme>
24+
| CodeOptionsMultipleThemes<BundledTheme>;
25+
26+
export function resolveTheme(
27+
themeInput: Theme | Themes
28+
): ThemeResolution {
29+
const isTextmateTheme =
30+
typeof themeInput === 'object' &&
31+
'tokenColors' in themeInput &&
32+
Array.isArray(themeInput.tokenColors);
33+
34+
// Non-textmate objects are assumed to be multi-theme configs
35+
const isMultiThemeConfig =
36+
typeof themeInput === 'object' &&
37+
themeInput !== null &&
38+
!isTextmateTheme;
39+
40+
const validMultiThemeObj =
41+
typeof themeInput === 'object' &&
42+
themeInput !== null &&
43+
!isTextmateTheme &&
44+
Object.entries(themeInput).some(
45+
([key, value]) =>
46+
key &&
47+
value &&
48+
key.trim() !== '' &&
49+
value !== '' &&
50+
(typeof value === 'string' || isTextmateTheme)
51+
);
52+
53+
if (isMultiThemeConfig) {
54+
const themeId = validMultiThemeObj
55+
? `multi-${Object.values(themeInput)
56+
.map(
57+
(theme) =>
58+
(typeof theme === 'string' ? theme : theme?.name) ||
59+
'custom'
60+
)
61+
.sort()
62+
.join('-')}`
63+
: 'multi-default';
64+
65+
// Invalid config returns null; fallback handled in buildShikiOptions
66+
return {
67+
isMultiTheme: true,
68+
themeId,
69+
multiTheme: validMultiThemeObj ? themeInput : null,
70+
themesToLoad: validMultiThemeObj ? Object.values(themeInput) : [],
71+
};
72+
}
73+
74+
return {
75+
isMultiTheme: false,
76+
themeId:
77+
typeof themeInput === 'string'
78+
? themeInput
79+
: themeInput?.name || 'custom',
80+
singleTheme: themeInput,
81+
themesToLoad: [themeInput],
82+
};
83+
}
84+
85+
export const toShikiOptions = (
86+
resolution: ThemeResolution
87+
): ThemeShikiOptions => {
88+
const { isMultiTheme, multiTheme, singleTheme } = resolution;
89+
90+
if (isMultiTheme) {
91+
return {
92+
themes: multiTheme || DEFAULT_THEMES,
93+
} as CodeOptionsMultipleThemes<BundledTheme>;
94+
}
95+
96+
return {
97+
theme: singleTheme || DEFAULT_THEMES.dark,
98+
} as CodeOptionsSingleTheme<BundledTheme>;
99+
};
100+
101+
export const getMultiThemeOptions = (
102+
resolution: ThemeResolution,
103+
defaultColor?: string | false,
104+
cssVariablePrefix?: string
105+
): Record<string, unknown> => {
106+
if (!resolution.isMultiTheme) return {};
107+
return { defaultColor, cssVariablePrefix };
108+
};

package/tests/performance.bench.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ import htmlReactParser from 'html-react-parser';
2222

2323
import type { Language, Theme, Themes } from '../src/lib/types';
2424

25-
import { resolveLanguage, resolveTheme } from '../src/lib/resolvers';
25+
import { resolveLanguage } from '../src/lib/language';
26+
import { resolveTheme } from '../src/lib/theme';
2627
// --- Test Data ---
2728

2829
// Small code sample (few lines)

0 commit comments

Comments
 (0)