[블로그 만들기 #7] rehype-highlight 이용해서 코드 블럭을 예쁘게 하이라이팅 해보자 (feat. highlight.js)
![Cover Image for [블로그 만들기 #7] rehype-highlight 이용해서 코드 블럭을 예쁘게 하이라이팅 해보자 (feat. highlight.js)](/assets/blog/vercel-blog-07/cover.webp)
rehype-highlight 모듈을 이용하여 코드 블록에 스타일 적용해서 예쁘게 하이라이팅 하는 방법을 알아봅시다.
들어가며
이전 포스팅에서 개발자들의 포토샵인 FFmpeg을 이용해서 블로그에 올라가는 모든 이미지들을 통일감 있게 표준화하는 방법을 알아보았습니다.
명색이 개발 블로그인데 가장 중요한 코드 부분들이 밋밋한 기본 스타일로 나오면 새우젓 없이 순대국 먹는 기분이지 않을까요?
이번 포스팅에서는 rehype-highlight 모듈을 이용해서 개발 블로그에서 가장 중요한 코드 부분들에 스타일 넣어서 예쁘게 하이라이팅 하는 방법에 대해 알아보겠습니다.
rehype-highlight란?
rehype-highlight는 HTML 콘텐츠 안의 코드 블럭에 자동으로 하이라이팅(스타일)을 적용해주는 플러그인입니다.
내부적으로는 highlight.js를 사용해서, 코드의 문법을 인식하고 적절한 색상을 입혀주는데요, 개발자 답게 작동 원리 먼저 간단하게 살펴보겠습니다.
rehype-highlight 간단한 작동원리
작동 원리는 생각보다 간단합니다.
Markdown 텍스트 -> HTML 텍스트로 변환하는 과정에서 중간에 스타일(css) 적용 가능하도록 class를 넣어주는 원리인데요, 저희가 블로그 포스팅을 위해 작성한 Markdown 파일이 있다고 생각해봅시다.
# Hello Markdown!
```js
const x = 123;
```
blog-starter-kit에서 기본적으로 사용하는 src/lib/markdownToHtml.ts
모듈을 이용해서 Markdown 파일을 HTML 파일로 변환해주면
<h1>Hello Markdown!</h1>
<pre>
<code class="language-js">
const x = 123;
</code>
</pre>
이렇게 HTML 문법으로 나오는데요, 이 과정 중간에 rehype-highlight 모듈을 슬쩍 끼워넣어서
<h1>Hello Markdown!</h1>
<pre>
<code class="language-js">
<span class="hljs-keyword">const</span> x = <span class="hljs-number">123</span>
</code>
</pre>
이런식으로 코드 부분을
- span 태그로 쪼개고
- 코드 하이라이팅 적용할 수 있도록 class(hljs-*) 넣어준 다음
- CSS 붙여서(highlight.js) HTML 파일에서도 예쁘게 보여주는 원리입니다.
Typography 플러그인
건물 올리기 전에 기초 공사로 땅을 먼저 다듬어 주듯이, 저희도 블로그 텍스트들이 단순 메모장처럼 보이지 않게 기초 공사를 먼저 해줘야 합니다.
npm install @tailwindcss/typography
텍스트 스타일링 플러그인 먼저 설치해주고
// tailwind.config.ts
import type { Config } from "tailwindcss";
const config: Config = {
darkMode: "class",
content: [
...
],
theme: {
extend: {
backgroundImage: {
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
"gradient-conic": "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
},
...
},
},
plugins: [require("@tailwindcss/typography")], // typography 플러그인 추가
};
export default config;
메모장을 탈출해서 에디터 느낌 나도록 tailwind.config.ts
파일에 방금 설치한 typography 플러그인 추가해준 후
// src/app/_components/post-body.tsx 수정 전
type Props = {
content: string;
};
export function PostBody({ content }: Props) {
return (
<div className="max-w-2xl mx-auto">
<div
className={markdownStyles["markdown"]} // 기존 스타일
dangerouslySetInnerHTML={{ __html: content }}
/>
</div>
);
}
포스팅 본문을 보여주는 src/app/_components/post-body.tsx
파일에서 기존 className 부분에
// src/app/_components/post-body.tsx 수정 후
type Props = {
content: string;
};
export function PostBody({ content }: Props) {
return (
<div className="max-w-2xl mx-auto">
<div
className="prose prose-lg dark:prose-invert" // 새로운 스타일
dangerouslySetInnerHTML={{ __html: content }}
/>
</div>
);
}
prose, prose-lg(반응형 사이즈 조절), dark:prose-invert(다크모드) class 적용해주시면
이렇게 메모장 느낌에서
뭔가 코드 에디터 느낌이 날 수 있도록 기초공사 완료입니다.
rehype-highlight 적용하기
기초 지식 (AST란?)
하이라이팅 적용 흐름 이해하는데 필요한 기초 지식인 AST가 무엇인지 먼저 정리해보겠습니다.
AST = Abstract Syntax Tree = 추상 구문 트리
문법 구조를 분석해서 계층 구조(트리 구조)로 만든 데이터인데요, 한 줄로 요약하면 “텍스트로 된 문서를, 컴퓨터가 이해할 수 있게 구조화한 버전” 이라고 생각하시면 됩니다.
마크다운 AST (MDAST)
# Hello
- First
- Second
이런 구조의 마크다운 텍스트를 파싱하면
{
"type": "root",
"children": [
{
"type": "heading",
"depth": 1,
"children": [{ "type": "text", "value": "Hello" }]
},
{
"type": "list",
"ordered": false,
"children": [
{
"type": "listItem",
"children": [{ "type": "paragraph", "children": [{ "type": "text", "value": "First" }] }]
},
{
"type": "listItem",
"children": [{ "type": "paragraph", "children": [{ "type": "text", "value": "Second" }] }]
}
]
}
]
}
이렇게 프로그래밍 가능하게 JSON 구조로 트리처럼 분해해 줍니다.
HTML AST (HAST)
위에서는 마크다운을 분해한걸 MDAST라고 설명드렸죠? 동일하게 HTML을 분해해서 프로그래밍 가능한 구조로 바꿔주면 HAST 데이터가 됩니다.
<h1>Hello</h1>
<ul>
<li>First</li>
<li>Second</li>
</ul>
이런 구조의 HTML 텍스트를 파싱하면
{
"type": "element",
"tagName": "h1",
"children": [{ "type": "text", "value": "Hello" }]
},
{
"type": "element",
"tagName": "ul",
"children": [
{
"type": "element",
"tagName": "li",
"children": [{ "type": "text", "value": "First" }]
},
{
"type": "element",
"tagName": "li",
"children": [{ "type": "text", "value": "Second" }]
}
]
}
이렇게 노드, 자식 노드, 태그 이름, 텍스트 값 같은 정보로 분해가 가능합니다.
작동 흐름 파악하기
기초적인 용어 정리가 끝났으니 저희가 사용중인 blog-starter-kit에서 어떻게 MD 파일을 읽어서 HTML 파일로 변환 중인지 작동 흐름 확인해보겠습니다.
// src/lib/markdownToHtml.ts
import { remark } from "remark";
import html from "remark-html";
export default async function markdownToHtml(markdown: string) {
const result = await remark().use(html).process(markdown);
return result.toString();
}
이름부터 마크다운을 HTML으로 변환해준다는 src/lib/markdownToHtml.ts 파일을 보시면
- remark: 마크다운 파서의 핵심. (마크다운 텍스트를 MDAST 데이터로 바꿔주는 친구)
- remark-html: MDAST 데이터를 HTML 문자열로 바꿔주는 친구
이렇게 마크다운 텍스트를 입력받아서 -> MDAST 데이터로 파싱 -> HTML 텍스트로 변환해주고 있습니다.
이 과정을 저희는
- 마크다운 텍스트 입력 -> MDAST 데이터로 변환 -> GitHub 스타일 문법 적용 (ex. 체크박스, 테이블, 취소선)
- MDAST 데이터를 HAST 데이터로 변환 -> 코드 블록에 하이라이팅 추가(span 태그로 쪼개고, hljs-* 클래스 추가) -> HAST 데이터를 HTML 텍스트로 변환
이렇게 진행하도록 바꿔줘야합니다.
필요 모듈 설치
npm 명령어를 이용하여 필요한 모듈 설치해주시고
npm install remark-gfm remark-rehype rehype-highlight rehype-stringify highlight.js
각 모듈이 뭐하는 친구들인지 설명하면
- remark-gfm: GitHub 스타일 문법 지원 (ex. 체크박스, 테이블, 취소선 등)
- remark-rehype: MDAST 데이터를 HAST 데이터로 바꿔주는 친구
- rehype-highlight: 코드 블록에 하이라이팅 가능하게 만들어줌 (highlight.js 의존, span 태그로 쪼개고, hljs-* 클래스 추가해주는 친구)
- rehype-stringify: HAST 데이터를 HTML 텍스트로 바꿔줌
- highlight.js: 테마 적용할 CSS 스타일 모아놓은 파일
이렇게 정리할 수 있습니다.
적용하기
이제 본격적으로 Markdown 텍스트를 코드 하이라이팅 적용 가능한 HTML 문법으로 바꿔보겠습니다.
// src/lib/markdownToHtml.ts 수정 전
import { remark } from "remark";
import html from "remark-html";
export default async function markdownToHtml(markdown: string) {
const result = await remark().use(html).process(markdown);
return result.toString();
}
기존 src/lib/markdownToHtml.ts
파일을
// src/lib/markdownToHtml.ts 수정 후
import { remark } from "remark";
import remarkGfm from "remark-gfm";
import remarkRehype from "remark-rehype";
import rehypeHighlight from "rehype-highlight";
import rehypeStringify from "rehype-stringify";
export default async function markdownToHtml(markdown: string) {
const result = await remark() // 마크다운 텍스트 MDAST 데이터로 파싱
.use(remarkGfm) // Github 스타일 적용
.use(remarkRehype) // MDAST 데이터를 HAST 데이터로 바꿔주기
.use(rehypeHighlight) // 코드 하이라이팅 가능하게 span 태그로 쪼개고, hljs-* 클래스 추가
.use(rehypeStringify) // HAST 데이터를 HTML 텍스트로 바꿔주기
.process(markdown);
return result.toString();
}
이렇게 바꿔주시면 CSS(highlight.js) 적용 가능한 HTML 텍스트로 변환 준비 완료입니다.
마지막으로 페이지 로딩시 CSS(highlight.js) 파일을 불러올 수 있도록 src/app/globals.css
파일에 highlight.js 파일을 import 해주시면 이제 저희 블로그는 단순 메모장 수준에서, vscode 같이 예쁜 에디터 느낌으로 진화하는 것입니다.
/* src/app/globals.css */
@import "highlight.js/styles/github-dark.css";
@tailwind base;
@tailwind components;
@tailwind utilities;
하이라이팅 & gfm 적용 전, 후 비교해보겠습니다.
이렇게 적용 전에는 코드도 밋밋하고, 표도 지원 안되는 촌스러운 메모장 수준에서
코드 하이라이팅, 표 지원되는 깔끔한 에디터 스타일이 적용된 것을 확인할 수 있습니다.
마무리
이제 우리는 블로그에 스타일링을 적용해서, 방문자들에게 "와 이 자식 좀 치네?"라는 말을 들을 수 있게 되었습니다.(전문성 연출은 보너스입니다)
다음 포스팅에서는 첨단 인공지능 시대에 맞서 첨단 인간 지능으로 만들어보는 고전 추천 시스템, "이전글"과 "다음글" 기능을 만드는 방법을 알아보겠습니다.