Flutter Flame 게시글을 쓰던 중 발견했다. Dart 코드는 하이라이팅이 제대로 안 되고 있었던 것.
아무래도 Dart는 신생언어다보니, highlight.js에서 지원하는 기본적인 언어 케이스에 들어가지 않았다. 그래서 Dart 언어를 지원하기 위한 수많은 삽질 끝에 하이라이팅을 완성했다.
원래는 react-markdown을 이용해서 마크다운을 HTML로 변환하고 있었다. 문제는, ```dart ``` 로 되어 있는 코드블럭을 react-markdown이 인식하지 못하고 있었다. react-markdown 에 rehype-highlight 플러그인을 사용해서 코드에 하이라이팅을 해주고 있었는데, highlight에서 지원하는 common 언어 중 dart는 포함되지 않았던 것.
사실 문법이나 스타일이 비슷하다면 ```js ''' 이런 식으로 한 후 dart 코드를 작성해도 하이라이팅은 어느 정도 된다. 하지만 dart 언어에 대한 하이라이팅이 안된다는 것, 그 근본적인 문제를 고치고 싶었다.
우선 react-markdown을 이용해서 dart 하이라이팅을 추가하는 것은 실패했다. highlight.js에 dart 언어를 register 하면 된다고 했는데 rehype-highlight 내부에서 사용하는 highlight.js 에 언어를 추가하는 법을 몰라서 이 부분은 실패했다.
최종적으로는, marked와 marked-highlight를 이용하여 코드를 하이라이팅하기로 했다. 서버에서 받은 markdown 내용물을 marked로 파싱하고, 이 결과물을 sanitize-html로 필터링해서 dangerouslySetInnerHTML을 사용해서 화면을 렌더했다. dangerouslySetInnerHTML를 사용한다는 것에서 XSS의 위험이 있을 수 있지만, 제 3자의 컨텐츠를 불러오는 것이 아니기 때문에 sanitize-html로 충분하다고 판단하고 진행했다.
대략적인 코드는 대략 다음과 같다.
import { Marked } from 'marked'; // Marked 클래스를 직접 임포트
import { markedHighlight } from "marked-highlight"; // 확장 라이브러리 임포트
const marked = new Marked(
markedHighlight({
langPrefix: 'hljs language-', // CSS 클래스 접두사
highlight(code, lang) {
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
return hljs.highlight(code, { language }).value;
}
})
);
export default async function Post({ params }: Props) {
const dirtyHtml = await marked.parse(post.content || '');
const cleanHtml = sanitizeHtml(dirtyHtml.toString(), {
allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img', 'h1', 'h2', 'pre', 'code']),
allowedAttributes: {
...sanitizeHtml.defaults.allowedAttributes,
code: ['class'],
// 외 등등
},
allowedClasses: {
'*': [ 'hljs-*', 'language-*', 'hljs' ]
}
});
return (
<section dangerouslySetInnerHTML={{ __html: cleanHtml }} />
);
}
이후 dart 코드블럭을 사용한 페이지로 가보니 위외 같이 하이라이팅이 적용된 모습을 확인할 수 있었다.