213 lines
8.6 KiB
JavaScript
213 lines
8.6 KiB
JavaScript
import { getMdcConfigs } from '#mdc-configs'
|
|
import { createOnigurumaEngine } from 'shiki/engine/oniguruma'
|
|
import { createJavaScriptRegexEngine } from "shiki/engine/javascript";
|
|
export function createShikiHighlighter({
|
|
langs = [],
|
|
themes = [],
|
|
bundledLangs = {},
|
|
bundledThemes = {},
|
|
getMdcConfigs,
|
|
options: shikiOptions,
|
|
engine
|
|
} = {}) {
|
|
let shiki;
|
|
let configs;
|
|
async function _getShiki() {
|
|
const { createHighlighterCore, addClassToHast, isSpecialLang, isSpecialTheme } = await import("shiki/core");
|
|
const { transformerNotationDiff, transformerNotationErrorLevel, transformerNotationFocus, transformerNotationHighlight } = await import("@shikijs/transformers");
|
|
const shiki2 = await createHighlighterCore({
|
|
langs,
|
|
themes,
|
|
engine: engine || createJavaScriptRegexEngine()
|
|
});
|
|
for await (const config of await getConfigs()) {
|
|
await config.shiki?.setup?.(shiki2);
|
|
}
|
|
return {
|
|
shiki: shiki2,
|
|
addClassToHast,
|
|
isSpecialLang,
|
|
isSpecialTheme,
|
|
transformers: [
|
|
transformerNotationDiff(),
|
|
transformerNotationErrorLevel(),
|
|
transformerNotationFocus(),
|
|
transformerNotationHighlight()
|
|
]
|
|
};
|
|
}
|
|
async function getShiki() {
|
|
if (!shiki) {
|
|
shiki = _getShiki();
|
|
}
|
|
return shiki;
|
|
}
|
|
async function getConfigs() {
|
|
if (!configs) {
|
|
configs = Promise.resolve(getMdcConfigs?.() || []);
|
|
}
|
|
return configs;
|
|
}
|
|
const highlighter = async (code, lang, theme, options = {}) => {
|
|
const {
|
|
shiki: shiki2,
|
|
addClassToHast,
|
|
isSpecialLang,
|
|
isSpecialTheme,
|
|
transformers: baseTransformers
|
|
} = await getShiki();
|
|
const codeToHastOptions = {
|
|
defaultColor: false,
|
|
meta: {
|
|
__raw: options.meta
|
|
}
|
|
};
|
|
if (lang === "ts-type" || lang === "typescript-type") {
|
|
lang = "typescript";
|
|
codeToHastOptions.grammarContextCode = "let a:";
|
|
} else if (lang === "vue-html" || lang === "vue-template") {
|
|
lang = "vue";
|
|
codeToHastOptions.grammarContextCode = "<template>";
|
|
}
|
|
const themesObject = { ...typeof theme === "string" ? { default: theme } : theme || {} };
|
|
const loadedThemes = shiki2.getLoadedThemes();
|
|
const loadedLanguages = shiki2.getLoadedLanguages();
|
|
if (typeof lang === "string" && !loadedLanguages.includes(lang) && !isSpecialLang(lang)) {
|
|
if (bundledLangs[lang]) {
|
|
await shiki2.loadLanguage(bundledLangs[lang]);
|
|
} else {
|
|
if (process.dev) {
|
|
console.warn(`[@nuxtjs/mdc] Language "${lang}" is not loaded to the Shiki highlighter, fallback to plain text. Add the language to "mdc.highlight.langs" to fix this.`);
|
|
}
|
|
lang = "text";
|
|
}
|
|
}
|
|
for (const [color, theme2] of Object.entries(themesObject)) {
|
|
if (typeof theme2 === "string" && !loadedThemes.includes(theme2) && !isSpecialTheme(theme2)) {
|
|
if (bundledThemes[theme2]) {
|
|
await shiki2.loadTheme(bundledThemes[theme2]);
|
|
} else {
|
|
if (process.dev) {
|
|
console.warn(`[@nuxtjs/mdc] Theme "${theme2}" is not loaded to the Shiki highlighter. Add the theme to "mdc.highlight.themes" to fix this.`);
|
|
}
|
|
themesObject[color] = "none";
|
|
}
|
|
}
|
|
}
|
|
const transformersMap = /* @__PURE__ */ new Map();
|
|
for (const transformer of baseTransformers) {
|
|
transformersMap.set(transformer.name || `transformer:${Math.random()}-${transformer.constructor.name}`, transformer);
|
|
}
|
|
for (const config of await getConfigs()) {
|
|
const newTransformers = typeof config.shiki?.transformers === "function" ? await config.shiki?.transformers(code, lang, theme, options) : config.shiki?.transformers || [];
|
|
for (const transformer of newTransformers) {
|
|
transformersMap.set(transformer.name || `transformer:${Math.random()}-${transformer.constructor.name}`, transformer);
|
|
}
|
|
}
|
|
const root = shiki2.codeToHast(code.trimEnd(), {
|
|
lang,
|
|
...codeToHastOptions,
|
|
themes: themesObject,
|
|
transformers: [
|
|
...transformersMap.values(),
|
|
{
|
|
name: "mdc:highlight",
|
|
line(node, line) {
|
|
if (options.highlights?.includes(line))
|
|
addClassToHast(node, "highlight");
|
|
node.properties.line = line;
|
|
}
|
|
},
|
|
{
|
|
name: "mdc:newline",
|
|
line(node) {
|
|
if (code?.includes("\n")) {
|
|
if (node.children.length === 0 || node.children.length === 1 && node.children[0].type === "element" && node.children[0].children.length === 1 && node.children[0].children[0].type === "text" && node.children[0].children[0].value === "") {
|
|
node.children = [{
|
|
type: "element",
|
|
tagName: "span",
|
|
properties: {
|
|
emptyLinePlaceholder: true
|
|
},
|
|
children: [{ type: "text", value: "\n" }]
|
|
}];
|
|
return;
|
|
}
|
|
const last = node.children.at(-1);
|
|
if (last?.type === "element" && last.tagName === "span") {
|
|
const text = last.children.at(-1);
|
|
if (text?.type === "text")
|
|
text.value += "\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
]
|
|
});
|
|
const preEl = root.children[0];
|
|
const codeEl = preEl.children[0];
|
|
const wrapperStyle = shikiOptions?.wrapperStyle;
|
|
preEl.properties.style = wrapperStyle ? typeof wrapperStyle === "string" ? wrapperStyle : preEl.properties.style : "";
|
|
const styles = [];
|
|
Object.keys(themesObject).forEach((color) => {
|
|
const colorScheme = color !== "default" ? `.${color}` : "";
|
|
styles.push(
|
|
wrapperStyle ? `${colorScheme} .shiki,` : "",
|
|
`html .${color} .shiki span {`,
|
|
`color: var(--shiki-${color});`,
|
|
`background: var(--shiki-${color}-bg);`,
|
|
`font-style: var(--shiki-${color}-font-style);`,
|
|
`font-weight: var(--shiki-${color}-font-weight);`,
|
|
`text-decoration: var(--shiki-${color}-text-decoration);`,
|
|
"}"
|
|
);
|
|
styles.push(
|
|
`html${colorScheme} .shiki span {`,
|
|
`color: var(--shiki-${color});`,
|
|
`background: var(--shiki-${color}-bg);`,
|
|
`font-style: var(--shiki-${color}-font-style);`,
|
|
`font-weight: var(--shiki-${color}-font-weight);`,
|
|
`text-decoration: var(--shiki-${color}-text-decoration);`,
|
|
"}"
|
|
);
|
|
});
|
|
return {
|
|
tree: codeEl.children,
|
|
className: Array.isArray(preEl.properties.class) ? preEl.properties.class.join(" ") : preEl.properties.class,
|
|
inlineStyle: preEl.properties.style,
|
|
style: styles.join("")
|
|
};
|
|
};
|
|
return highlighter;
|
|
}
|
|
|
|
const bundledLangs = {
|
|
"javascript": () => import('@shikijs/langs/javascript').then(r => r.default || r),
|
|
"js": () => import('@shikijs/langs/javascript').then(r => r.default || r),
|
|
"jsx": () => import('@shikijs/langs/jsx').then(r => r.default || r),
|
|
"json": () => import('@shikijs/langs/json').then(r => r.default || r),
|
|
"typescript": () => import('@shikijs/langs/typescript').then(r => r.default || r),
|
|
"ts": () => import('@shikijs/langs/typescript').then(r => r.default || r),
|
|
"tsx": () => import('@shikijs/langs/tsx').then(r => r.default || r),
|
|
"vue": () => import('@shikijs/langs/vue').then(r => r.default || r),
|
|
"css": () => import('@shikijs/langs/css').then(r => r.default || r),
|
|
"html": () => import('@shikijs/langs/html').then(r => r.default || r),
|
|
"shellscript": () => import('@shikijs/langs/shellscript').then(r => r.default || r),
|
|
"bash": () => import('@shikijs/langs/shellscript').then(r => r.default || r),
|
|
"sh": () => import('@shikijs/langs/shellscript').then(r => r.default || r),
|
|
"shell": () => import('@shikijs/langs/shellscript').then(r => r.default || r),
|
|
"zsh": () => import('@shikijs/langs/shellscript').then(r => r.default || r),
|
|
"markdown": () => import('@shikijs/langs/markdown').then(r => r.default || r),
|
|
"md": () => import('@shikijs/langs/markdown').then(r => r.default || r),
|
|
"mdc": () => import('@shikijs/langs/mdc').then(r => r.default || r),
|
|
"yaml": () => import('@shikijs/langs/yaml').then(r => r.default || r),
|
|
"yml": () => import('@shikijs/langs/yaml').then(r => r.default || r),
|
|
}
|
|
const bundledThemes = {
|
|
"github-light": () => import('@shikijs/themes/github-light').then(r => r.default || r),
|
|
"github-dark": () => import('@shikijs/themes/github-dark').then(r => r.default || r),
|
|
}
|
|
const options = {"theme":{"default":"github-light","dark":"github-dark"}}
|
|
const engine = createOnigurumaEngine(() => import('shiki/wasm'))
|
|
const highlighter = createShikiHighlighter({ bundledLangs, bundledThemes, options, getMdcConfigs, engine })
|
|
export default highlighter |