@podlite/to-jsx
Version:
Render Podlite markup as React JSX components
147 lines • 4.33 kB
JavaScript
const loadShiki = () => import('shiki');
// Configuration
const CONFIG = {
themes: {
light: 'one-light',
dark: 'github-dark',
},
initialLanguages: ['shell', 'txt'],
};
// Language aliases mapping
const languageAliases = {
text: 'txt',
plaintext: 'txt',
plain: 'txt',
sh: 'shell',
bash: 'shell',
zsh: 'shell',
shellscript: 'shell',
'shell-script': 'shell',
console: 'shell',
terminal: 'shell',
js: 'javascript',
node: 'javascript',
nodejs: 'javascript',
ts: 'typescript',
py: 'python',
python3: 'python',
py3: 'python',
rb: 'ruby',
md: 'markdown',
cpp: 'c++',
cc: 'c++',
cs: 'c#',
csharp: 'c#',
htm: 'html',
yml: 'yaml',
dockerfile: 'docker',
styles: 'css',
style: 'css',
jsonc: 'json',
json5: 'json',
xaml: 'xml',
xhtml: 'xml',
svg: 'xml',
mysql: 'sql',
postgresql: 'sql',
postgres: 'sql',
pgsql: 'sql',
plsql: 'sql',
oracle: 'sql',
};
// Singleton state
const state = {
instance: null,
initPromise: null,
loadedLanguages: new Set(['txt']),
pendingLoads: new Map(),
warnedLanguages: new Set(),
};
// Normalize language to valid Shiki language
export async function normalizeLanguage(language) {
if (!language)
return 'txt';
const normalized = language.toLowerCase();
const { bundledLanguages } = await loadShiki();
if (normalized in bundledLanguages) {
return normalized;
}
if (normalized in languageAliases) {
return languageAliases[normalized];
}
if (!state.warnedLanguages.has(language)) {
console.warn(`[podlite] shiki unrecognized language '${language}', defaulting to [txt]`);
state.warnedLanguages.add(language);
}
return 'txt';
}
// Get or create highlighter instance
async function getHighlighterInstance() {
if (!state.initPromise) {
state.initPromise = (async () => {
const { createHighlighter } = await loadShiki();
const instance = await createHighlighter({
themes: Object.values(CONFIG.themes),
langs: CONFIG.initialLanguages,
});
state.instance = instance;
CONFIG.initialLanguages.forEach(lang => state.loadedLanguages.add(lang));
return instance;
})();
}
return state.initPromise;
}
// Load a language if not already loaded
async function ensureLanguageLoaded(instance, lang) {
if (state.loadedLanguages.has(lang))
return;
let pending = state.pendingLoads.get(lang);
if (!pending) {
pending = instance
.loadLanguage(lang)
.then(() => {
state.loadedLanguages.add(lang);
})
.finally(() => state.pendingLoads.delete(lang));
state.pendingLoads.set(lang, pending);
}
await pending;
}
// Main API: get highlighter with language loaded
export async function getHighlighter(language) {
const lang = await normalizeLanguage(language);
const instance = await getHighlighterInstance();
await ensureLanguageLoaded(instance, lang);
return instance;
}
// Convenience: convert code to HTML with dual themes (CSS-based switching)
export async function codeToHtml({ code, language }) {
const lang = await normalizeLanguage(language);
const highlighter = await getHighlighter(language);
return highlighter.codeToHtml(code, {
lang,
themes: CONFIG.themes,
});
}
// Convert code to HTML with explicit theme selection (for runtime theme detection)
export async function codeToThemedHtml({ code, language, theme, decorations, }) {
const lang = await normalizeLanguage(language);
const highlighter = await getHighlighter(language);
if (theme === 'light' || theme === 'dark') {
return highlighter.codeToHtml(code, {
lang,
theme: CONFIG.themes[theme],
decorations,
});
}
return highlighter.codeToHtml(code, {
lang,
themes: CONFIG.themes,
decorations,
});
}
// Check if a language is loaded (sync — only meaningful after a prior async normalize)
export const isLanguageLoaded = (language) => {
return state.loadedLanguages.has(language);
};
//# sourceMappingURL=shiki.js.map