Skip to content

Markdown

Markdown을(토큰 단위로 오는 AI 출력까지) 프레임워크에 매이지 않는 웹 컴포넌트로 그립니다. <r-markdown>은 Vercel의 Streamdown을 본떴습니다. 텍스트가 흘러드는 동안 쓰다 만 **bold, `code, 링크, $$ 수식을 즉석에서 닫고, 문서를 블록으로 쪼개 바뀐 블록만 다시 그립니다. 그래서 긴 답변이라도 토큰마다 처음부터 다시 파싱하지 않습니다.

```mermaid 펜스는 <r-mermaid>가 되고, 수식은 <r-math>가 되며, 코드는 shiki로 하이라이트할 수 있습니다. 이 모두는 내용이 실제로 필요로 한 첫 순간에 지연 로드됩니다. 출력은 DOMPurify로 정화됩니다.

이럴 때 씁니다. 온전히 통제하지 못하는 Markdown(채팅 답변, LLM 스트림, 사용자 댓글, 문서)을 보여 주면서, 파서·정화기·하이라이터를 직접 엮지 않고도 스트리밍, 코드·다이어그램·수식 지원, 안전한 HTML을 얻고 싶을 때.

빠른 시작

html
<r-markdown copy highlight content="# 안녕하세요 ..."></r-markdown>
js
import 'ranui'; // 또는 단독 진입점:
import 'ranui/markdown';

원본은 content 프로퍼티(권장. 어트리뷰트로 반영되지 않으므로 긴 답변을 스트리밍해도 DOM이 요동치지 않습니다), content 어트리뷰트, 또는 요소의 텍스트 내용에서 읽습니다.

js
const el = document.createElement('r-markdown');
el.setAttribute('caret', ''); // 스트리밍 중 깜박이는 캐럿을 보여 줍니다
for await (const chunk of stream) {
  el.content += chunk; // 마지막 블록만 다시 그려집니다
}
el.removeAttribute('caret');
container.append(el);

스트리밍

mode="streaming"(기본값)은 텍스트를 먼저 remend에 통과시킵니다. Streamdown에서 뽑아낸, 미완성 markdown을 마무리해 주는 조각입니다. 덕분에 절반만 받은 **bold는 별표가 그대로 보이는 대신 굵게 그려지고, [text](https://exa는 URL이 닫힐 때까지 평범한 텍스트로 남으며, - 하나가 앞 문단을 제목으로 바꿔 버리는 일도 없습니다. 이미 완성된 문서라면 mode="static"으로 이 과정을 건너뛰고 한 번에 그리세요.

html
<r-markdown caret content="쓰다 만 *강조*, `인라인 코드`, 그리고 **아직 도착 중인 굵은 글씨"></r-markdown>
  • 캐럿: caret은 깜박이는 를, caret="circle"를 마지막 블록 뒤에 보여 줍니다. 코드 펜스가 아직 열려 있거나 마지막 블록이 표일 때는 알아서 숨습니다.
  • 미완성 코드 펜스는 닫는 펜스가 도착할 때까지 평범한 텍스트로 남습니다(하이라이트가 번쩍이지도, 다이어그램이 반쯤 그려지지도 않습니다). 그동안 컨테이너에는 data-incomplete가 붙습니다.

코드 블록

모든 코드 블록에는 언어 이름이 담긴 헤더가 붙고, 원한다면 복사·다운로드 버튼도 켤 수 있습니다. shiki로 문법을 강조하려면 highlight를 더하세요(지연 로드되며, 언어도 필요할 때 불러옵니다. 기본 테마는 github-light / github-dark로 페이지 테마를 따릅니다).

html
<r-markdown copy download line-numbers highlight></r-markdown>
<!-- 테마 고르기: 라이트 다크 -->
<r-markdown highlight="vitesse-light vitesse-dark"></r-markdown>

Mermaid와 수식

  • ```mermaid<r-mermaid>(전체 화면 지원. copy / download는 그대로 전달됩니다).
  • $$…$$, \[…\], ```math → 블록 <r-math>. \(…\) → 인라인. 달러 하나짜리 $…$는 통화 표기와 헷갈리므로 inline-math직접 켜야 합니다.

API 레퍼런스

어트리뷰트

어트리뷰트타입기본값설명
contentstringMarkdown 원본. content 프로퍼티가 우선하며 어트리뷰트로 반영되지 않습니다. 둘 다 없으면 요소의 텍스트를 씁니다.
mode'streaming' | 'static''streaming'streaming은 미완성 markdown을 닫고 블록 단위로 비교합니다. static은 전체 텍스트를 그대로 한 번에 그립니다.
caret불리언 / 'circle'꺼짐마지막 블록 뒤의 깜박이는 캐럿(, circle이면 ).
copy불리언꺼짐코드 블록의 복사 버튼(품고 있는 <r-mermaid>에도 전달됩니다).
download불리언꺼짐코드 블록의 다운로드 버튼(언어에 따른 code.<ext>).
line-numbers불리언꺼짐코드 블록의 줄 번호.
highlight불리언 / "light dark" 테마 이름꺼짐shiki 문법 강조. 값 없이 쓰면 github-light github-dark, 하나면 둘 다에, 둘이면 라이트 / 다크에 씁니다.
inline-math불리언꺼짐$…$를 인라인 수식으로 다룹니다(\(…\)는 언제나 인라인입니다).
link-targetstring'_blank'외부 링크의 target(rel="noopener noreferrer"도 붙습니다). _self면 링크를 건드리지 않습니다. 페이지 안 #앵커에는 붙지 않습니다.
theme'auto' | 'light' | 'dark''auto'하이라이트와 다이어그램의 테마. auto는 페이지를 따릅니다(.dark, [data-ran-theme], 없으면 prefers-color-scheme).
sheetstring섀도 루트에 주입할 추가 CSS.
label-*string영어컨트롤 레이블을 덮어씁니다: label-copy, label-download.

프로퍼티 별칭: content, mode, caret, copyable, downloadable, lineNumbers, highlight, inlineMath, linkTarget, theme, sheet.

이벤트

모든 이벤트는 버블링되며 섀도 경계를 넘습니다(composed).

이벤트detail언제 발생하는가
render{ blocks: number, changed: number }한 번의 렌더에서 블록이 하나 이상 바뀌었을 때
copied{ kind: 'code', language, code }코드 블록이 복사되었을 때
download{ kind: 'code', language, filename }코드 블록이 다운로드되었을 때
error{ message: string }파싱·렌더링이 실패했을 때(그 자리에도 표시됩니다)

CSS Part

Part설명
markdown바깥 래퍼.
body블록 컨테이너.
block그려진 각 블록.
code코드 블록 컨테이너.
code-header코드 블록의 언어·동작 막대.
code-lang언어 레이블.
code-actions동작 버튼 묶음.
button복사·다운로드 각 버튼.
table가로로 스크롤되는 표 래퍼.
error오류 상자(렌더 실패 시).
css
r-markdown::part(code) {
  border-radius: 8px;
}

CSS 변수

요소에서 덮어쓸 수 있습니다(각각 시맨틱 토큰으로, 다시 리터럴 값으로 물러납니다): --ran-markdown-color, --ran-markdown-font-size, --ran-markdown-line-height, --ran-markdown-gap, --ran-markdown-heading-color, --ran-markdown-link-color, --ran-markdown-inline-code-bg, --ran-markdown-code-bg, --ran-markdown-code-border, --ran-markdown-code-radius, --ran-markdown-code-font-size, --ran-markdown-mono-font, --ran-markdown-blockquote-border, --ran-markdown-table-border, --ran-markdown-table-header-bg, --ran-markdown-caret, --ran-markdown-caret-color, --ran-markdown-button-color, --ran-markdown-error-color.

참고

  • 지연 로드: 파서 청크(marked + DOMPurify + remend)는 첫 렌더 때 로드되고, shiki·mermaid·Temml은 내용이 실제로 쓸 때만 각각 로드됩니다. markdown을 전혀 그리지 않는 앱은 비용을 치르지 않습니다.
  • 정화됨: markdown 안의 날 HTML은 DOMPurify를 거칩니다. 스크립트, 이벤트 핸들러, javascript: URL, <style>, 폼, iframe이 제거됩니다. 할 일 목록의 체크박스는 살아남습니다.
  • 블록 비교는 블록을 위치로 식별하므로, 손대지 않은 블록 안의 DOM 상태(전체 화면으로 열린 다이어그램, 스크롤해 둔 표)는 스트리밍 갱신에도 살아남습니다. 문서는 한 번만 어휘 분석되고 각 블록은 자기 토큰으로 그려지므로, 링크 참조 정의도 블록을 넘어 해결됩니다([text][id]가 한 블록에, [id]: url이 다른 블록에 있어도 됩니다).
  • GFM 각주([^1])는 지원하지 않습니다. marked에 각주 토크나이저가 없어서 표시가 글자 그대로 그려집니다.
  • shiki는 여러분의 설치본에서 해결됩니다. ES 빌드는 import('shiki')를 그대로 두므로, 번들러가 코드를 쪼개고 코드 펜스가 실제로 쓰는 문법만 내려받습니다. shiki는 ranui의 평범한 의존성이라 npm i ranui만으로 이미 들어옵니다. 따로 더할 것은 없습니다.
  • 단독 IIFE: dist/iife/markdown.iife.js에는 해석기가 없어서, 대신 mermaid, Temml, 그리고 shiki의 web 언어 번들(흔한 50여 개 언어)을 인라인합니다. 언어를 두루 지원하고 다운로드도 줄이려면 ES 진입점(ranui/markdown)을 쓰세요.

MIT 라이선스로 배포됩니다.