Skip to content

Commit

Permalink
feat: the first version of rehypeMomiji
Browse files Browse the repository at this point in the history
  • Loading branch information
taga3s committed Sep 7, 2024
1 parent c63bcec commit 195b457
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 1 deletion.
7 changes: 7 additions & 0 deletions app/modules/rehype-momiji/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# rehype-momiji (in development)

[rehype](https://github.com/rehypejs/rehype) plugin powered by [shiki](https://shiki.matsu.io/).

## features

- Highlighting Code
23 changes: 23 additions & 0 deletions app/modules/rehype-momiji/highlighter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {
type BundledLanguage,
type BundledTheme,
type DynamicImportLanguageRegistration,
type DynamicImportThemeRegistration,
getSingletonHighlighter,
} from "shiki";

const getHighlighter = async ({
themes,
langs,
}: {
themes: Record<BundledTheme, DynamicImportThemeRegistration>;
langs: Record<BundledLanguage, DynamicImportLanguageRegistration>;
}) => {
const newHighlighter = await getSingletonHighlighter({
themes: [...Object.keys(themes)],
langs: [...Object.keys(langs)],
});
return newHighlighter;
};

export { getHighlighter };
6 changes: 6 additions & 0 deletions app/modules/rehype-momiji/parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { unified } from "unified";
import rehypeParse from "rehype-parse";

const parser = unified().use(rehypeParse, { fragment: true });

export { parser };
81 changes: 81 additions & 0 deletions app/modules/rehype-momiji/rehypeMomiji.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { bundledLanguages, bundledThemes, type BundledLanguage, type BundledTheme } from "shiki";
import type { Plugin } from "unified";
import type { Text, Element, Root } from "hast";
import { visit } from "unist-util-visit";
import { getHighlighter } from "./highlighter";
import { parser } from "./parser";
import { isArray, isObject, isString } from "./utils/checkTypeOfOperandValue";

type Options = { theme?: BundledTheme; fallbackLang?: BundledLanguage };

const defaultHighlighter = await getHighlighter({ themes: bundledThemes, langs: bundledLanguages });

const rehypeMomiji: Plugin = (options: Options = { theme: "github-dark-default", fallbackLang: "c" }) => {
const langs = defaultHighlighter.getLoadedLanguages();
const { theme, fallbackLang } = options;

const parseLanguage = (classNames: string[]): string | undefined => {
for (const className of classNames) {
if (className.startsWith("language-")) {
return className.replace("language-", "");
}
}
return;
};

const checkLanguage = (lang?: string): string | undefined => {
if (lang && langs.includes(lang)) return lang;
return fallbackLang;
};

return (node) => {
visit(node, "element", (node: Element) => {
// Check if the node is a pre tag with a single child
if (!(node.tagName === "pre" && isArray(node.children) && node.children.length === 1)) {
return;
}

// Check if the child is a code tag
const codeEle = node.children[0] as Element;
if (!(isObject(codeEle) && codeEle.tagName === "code")) {
return;
}

// Check if the code tag has a className property
const classNames = codeEle.properties?.className;
if (!(isArray(classNames) && classNames.length > 0 && classNames.every(isString))) {
return;
}

// Check if the code tag has a text child
const textNode = codeEle.children[0] as Text;
if (!isString(textNode.value)) {
return;
}

const rawCode = textNode.value.trim();

// Parse the language from the class names and check if it is supported
const lang = parseLanguage(classNames);
const checkedLang = checkLanguage(lang);
if (!checkedLang) {
return;
}

const highlightCode = defaultHighlighter.codeToHtml(rawCode, {
lang: checkedLang,
theme: theme ?? "github-dark",
});

const container = `
<div>${highlightCode}</div>
`;

const parsedRoot = parser.parse(container) as Root;
node.tagName = "div";
node.children = parsedRoot.children as Element[];
});
};
};

export default rehypeMomiji;
16 changes: 16 additions & 0 deletions app/modules/rehype-momiji/utils/checkTypeOfOperandValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
const isObject = (value: any) => {
return value !== null && typeof value === "object";
};

// biome-ignore lint/suspicious/noExplicitAny: <explanation>
const isString = (value: any) => {
return typeof value === "string";
};

// biome-ignore lint/suspicious/noExplicitAny: <explanation>
const isArray = (value: any) => {
return Array.isArray(value);
};

export { isObject, isString, isArray };
5 changes: 4 additions & 1 deletion vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import mdx from '@mdx-js/rollup';
import remarkFrontmatter from 'remark-frontmatter';
import remarkMdxFrontmatter from 'remark-mdx-frontmatter';
import remarkGfm from 'remark-gfm'
import rehypeMomiji from './app/modules/rehype-momiji/rehypeMomiji'

export default defineConfig(() => {
return {
Expand All @@ -15,10 +16,12 @@ export default defineConfig(() => {
},
plugins: [
honox({ devServer: { adapter } }),
pages(), ssg({ entry: "./app/server.ts" }),
pages(),
ssg({ entry: "./app/server.ts" }),
mdx({
jsxImportSource: 'hono/jsx',
remarkPlugins: [remarkGfm, remarkFrontmatter, remarkMdxFrontmatter],
rehypePlugins: [rehypeMomiji]
})
],
}
Expand Down

0 comments on commit 195b457

Please sign in to comment.