마크다운이 작동하는 방식
마크다운이 어떻게 작동하는지 이해하면 이 강력한 도구를 더 잘 활용할 수 있습니다. 이 장에서는 마크다운이 일반 텍스트에서 아름답고 형식이 지정된 문서로 변환되는 방식을 자세히 살펴보겠습니다.
기본 워크플로우
마크다운 워크플로우는 다음 단계로 요약할 수 있습니다:
마크다운 소스 파일 (.md) → 마크다운 파서 → HTML 문서 → 브라우저 렌더링
1. 마크다운 소스 파일 작성
모든 텍스트 에디터를 사용하여 .md
파일을 만들고 마크다운 문법을 사용하여 콘텐츠를 작성합니다:
markdown
# 내 문서
이것은 **중요한** 단락입니다.
## 목록 예제
- 항목 1
- 항목 2
- 항목 3
2. 마크다운 파서 처리
파서는 마크다운 파일을 읽고, 문법 요소를 인식하고, 이를 해당하는 HTML로 변환합니다:
html
<h1>내 문서</h1>
<p>이것은 <strong>중요한</strong> 단락입니다.</p>
<h2>목록 예제</h2>
<ul>
<li>항목 1</li>
<li>항목 2</li>
<li>항목 3</li>
</ul>
3. 브라우저 렌더링
생성된 HTML이 브라우저에서 형식이 지정된 문서로 표시됩니다.
파서가 작동하는 방식
어휘 분석
파서는 먼저 어휘 분석을 수행하여 마크다운 텍스트를 토큰으로 분해합니다:
#
제목 토큰으로 인식**text**
굵은 텍스트 토큰으로 인식- item
목록 항목 토큰으로 인식
구문 파싱
그런 다음 구문 파싱을 수행하여 추상 구문 트리(AST)를 구축합니다:
문서
├── 제목 (레벨 1): "내 문서"
├── 단락
│ ├── 텍스트: "이것은"
│ ├── 굵은 텍스트: "중요한"
│ └── 텍스트: "단락입니다."
├── 제목 (레벨 2): "목록 예제"
└── 순서 없는 목록
├── 목록 항목: "항목 1"
├── 목록 항목: "항목 2"
└── 목록 항목: "항목 3"
HTML 생성
마지막으로 구문 트리를 순회하여 해당하는 HTML 출력을 생성합니다.
주류 파서
CommonMark
- 표준 명세 - 통일된 마크다운 파싱 표준 제공
- 엄격하게 정의됨 - 다양한 구현 간의 모호함 제거
- 광범위하게 지원됨 - 여러 파서에서 채택
GitHub Flavored Markdown (GFM)
CommonMark를 기반으로 하며 다음을 추가합니다:
- 표 지원
- 취소선
- 작업 목록
- 자동 링크 감지
- 구문 강조 코드 블록
기타 파서
파서 | 언어 | 기능 |
---|---|---|
marked | JavaScript | 빠름, 가벼움 |
markdown-it | JavaScript | 플러그인 가능, 고도로 확장 가능 |
Python-Markdown | Python | 기능이 풍부함, 플러그인 시스템 |
kramdown | Ruby | 여러 출력 형식 지원 |
Pandoc | Haskell | 범용 문서 변환기 |
렌더링 엔진
클라이언트 사이드 렌더링
브라우저에서 실시간으로 마크다운 파싱:
javascript
// marked.js 사용
const html = marked.parse('# Hello World');
document.body.innerHTML = html;
장점:
- 서버 처리 불필요
- 실시간 미리보기
- 서버 부하 감소
단점:
- JavaScript에 의존
- SEO에 불리함
- 초기 로딩이 느림
서버 사이드 렌더링
서버에서 HTML을 미리 생성:
javascript
// Node.js 예제
const fs = require('fs');
const marked = require('marked');
const markdown = fs.readFileSync('document.md', 'utf8');
const html = marked.parse(markdown);
장점:
- SEO 친화적
- 빠른 로딩
- 클라이언트 사이드 JavaScript에 의존하지 않음
단점:
- 서버 처리 오버헤드
- 복잡한 캐시 관리
정적 사이트 생성
빌드 시 모든 페이지를 미리 생성:
bash
# VitePress 사용
npm run build
장점:
- 가장 빠른 로딩 속도
- 최고의 SEO
- 높은 보안
- 쉬운 배포
단점:
- 동적 콘텐츠 지원 제한
- 더 긴 빌드 시간
확장 메커니즘
플러그인 시스템
많은 파서가 플러그인 확장을 지원합니다:
javascript
// markdown-it 플러그인 예제
const md = require('markdown-it')()
.use(require('markdown-it-footnote'))
.use(require('markdown-it-deflist'))
.use(require('markdown-it-abbr'));
사용자 정의 렌더러
javascript
// 사용자 정의 링크 렌더러
const renderer = new marked.Renderer();
renderer.link = function(href, title, text) {
return `<a href="${href}" target="_blank">${text}</a>`;
};
성능 최적화
캐싱 전략
javascript
const cache = new Map();
function parseMarkdown(content) {
const hash = generateHash(content);
if (cache.has(hash)) {
return cache.get(hash);
}
const result = marked.parse(content);
cache.set(hash, result);
return result;
}
지연 로딩
javascript
// 보이는 영역의 콘텐츠만 파싱
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
parseAndRender(entry.target);
}
});
});
스트리밍 처리
javascript
// 대용량 파일을 위한 스트림 파싱
const fs = require('fs');
const { Transform } = require('stream');
const markdownTransform = new Transform({
transform(chunk, encoding, callback) {
const html = marked.parse(chunk.toString());
callback(null, html);
}
});
fs.createReadStream('large-document.md')
.pipe(markdownTransform)
.pipe(fs.createWriteStream('output.html'));
일반적인 문제
1. 줄바꿈 문제
다른 파서가 줄바꿈을 다르게 처리할 수 있습니다:
markdown
줄 1
줄 2 ← 이것들이 같은 단락으로 파싱될 수 있음
줄 1
줄 2 ← 줄 끝의 두 공백이 줄바꿈을 강제함
줄 1
줄 2 ← 빈 줄이 단락을 분리함
2. HTML 혼합
markdown
이것은 **마크다운**과 <em>HTML</em>의 혼합입니다.
HTML 태그의 올바른 닫기와 중첩에 주의하세요.
3. 특수 문자 이스케이핑
markdown
여기서는 \*와 \_ 문자를 이스케이프해야 합니다.
실제 응용 시나리오
1. 블로그 시스템
마크다운 기사 → 정적 사이트 생성기 → HTML 웹사이트
2. 문서 사이트
.md 문서 → VitePress/Docusaurus → 온라인 문서
3. README 파일
README.md → GitHub/GitLab → 프로젝트 홈페이지
4. 노트 앱
마크다운 노트 → 실시간 렌더링 → 리치 텍스트 표시
다음 단계
이제 마크다운이 어떻게 작동하는지 이해했으므로 다음을 할 수 있습니다: