Skip to content

Commit

Permalink
feat: rehypeMermaid
Browse files Browse the repository at this point in the history
  • Loading branch information
taga3s committed Nov 17, 2024
1 parent 5403569 commit 0b1b911
Show file tree
Hide file tree
Showing 7 changed files with 1,912 additions and 58 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/build-and-deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ jobs:
- name: Install deps
run: pnpm install

- name: Install Puppeteer
run: pnpm install-puppeteer

- name: Build
run: pnpm build

Expand Down
6 changes: 6 additions & 0 deletions app/packages/rehype-mermaid/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 };
96 changes: 96 additions & 0 deletions app/packages/rehype-mermaid/rehypeMermaid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import type { Plugin } from "unified";
import type { Text, Element, Parent } from "hast";
import { visit } from "unist-util-visit";
import { renderMermaid } from "@mermaid-js/mermaid-cli";
import puppeteer from "puppeteer";
import { parser } from "./parser";

interface MermaidCodeBlock {
textNode: Text;
index: number;
parent: Parent;
}

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

const checkIsMermaid = (lang: string): boolean => lang === "mermaid";

return async (node) => {
const mermaidCodeBlocks: MermaidCodeBlock[] = [];

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

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

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

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

// Parse the language from the class names and check if it is supported
const lang = parseLanguage(classNames);
const isMermaid = checkIsMermaid(lang);

if (isMermaid && index !== null && parent !== null) {
mermaidCodeBlocks.push({ textNode, index, parent });
}
});

if (mermaidCodeBlocks.length === 0) {
return;
}

const browser = await puppeteer.launch({
headless: true,
});

const decoder = new TextDecoder();

await Promise.all(
mermaidCodeBlocks.map(async ({ textNode, index, parent }, blockIndex) => {
const { data: svgBuffer } = await renderMermaid(browser, textNode.value, "svg");

const svgElem = decoder.decode(svgBuffer).replaceAll("my-svg", `my-svg-${blockIndex}`);
const parsedRoot = parser.parse(svgElem);

const content = parsedRoot.children[0] as Element;
content.properties.style = "width: 100%; background-color: white;";

parent.children[index] = content;
}),
);

await browser.close();
};
};

export default rehypeMermaid;
19 changes: 19 additions & 0 deletions app/routes/posts/md-test.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,22 @@ publishedAt: 2024/09/14
```ts title="sample.ts"
console.log("Hello, TypeScript!");
```

- こちらは、Mermaid のサンプルです。

```mermaid
classDiagram
Class01 <|-- AveryLongClass : Cool
Class03 *-- Class04
Class05 o-- Class06
Class07 .. Class08
Class09 --> C2 : Where am i?
Class09 --* C3
Class09 --|> Class07
Class07 : equals()
Class07 : Object[] elementData
Class01 : size()
Class01 : int chimp
Class01 : int gorilla
Class08 <--> C2: Cool label
```
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"lint": "biome lint --write --unsafe ./app",
"format": "biome format --write ./app package.json biome.json tsconfig.json",
"article": "speedymd",
"prepare": "husky"
"prepare": "husky",
"install-puppeteer": "puppeteer browsers install chrome"
},
"dependencies": {
"hono": "^4.5.11",
Expand All @@ -24,6 +25,7 @@
"@hono/vite-dev-server": "^0.14.0",
"@hono/vite-ssg": "^0.1.0",
"@mdx-js/rollup": "^3.0.1",
"@mermaid-js/mermaid-cli": "^11.4.0",
"@types/hast": "^3.0.4",
"@types/mdast": "^4.0.4",
"@types/unist": "^3.0.3",
Expand All @@ -32,6 +34,7 @@
"husky": "^9.1.1",
"lint-staged": "^15.2.7",
"mdast": "^3.0.0",
"puppeteer": "^23.8.0",
"rehype-parse": "^9.0.1",
"remark-breaks": "^4.0.0",
"remark-frontmatter": "^5.0.0",
Expand Down
Loading

0 comments on commit 0b1b911

Please sign in to comment.