VitePress
VitePress 사이트에 cudoc을 설정합니다. Markdown 문법 확장을 먼저 켜고, 그다음 문서 임베드를 더하며, 마지막의 HTML 내보내기는 선택 사항입니다.
먼저 보기: VitePress 데모는 실행 가능한 예제를 VitePress 1.6.4로 빌드한 사이트이며, 빌드에 쓴 Markdown 원문으로 가는 링크가 함께 있습니다. → 호스트별 데모
단계는 순서대로 따릅니다. 1–3단계는 Markdown 문법 확장을 켜고, 4–6단계는 한 문서가 다른 문서를 재사용하게 하는 문서 임베드를 더하며, 7단계는 선택 사항입니다.
1단계 — 설치
코어 패키지와 VitePress 어댑터를 설치합니다.
npm install @cudoment/cudoc cudoc-vitepress
cudoc은 npm install vitepress가 설치하는 VitePress 1을 지원합니다. vitepress.dev의 시작 안내가 설치하는 VitePress 2 알파(vitepress@next)는 아직 지원하지 않으며, npm이 그 옆에 cudoc-vitepress를 설치하기를 거부합니다. → 지원 버전
VitePress는 remark가 아니라 markdown-it을 쓰므로 cudoc-remark를 사용하지 않습니다. 공용 markdown-it 계층인 cudoc-markdown-it은 cudoc-vitepress와 함께 설치됩니다.
2단계 — Markdown 옵션을 공용 모듈에 두기
Markdown 옵션은 사이트와 수집기가 함께 가져오는 모듈 하나에 두어, 두 곳이 같은 옵션으로 Markdown을 렌더링하게 합니다. 두 번째 파일은 docs/.vitepress/config.mjs에 병합하고, 사이트의 제목, 테마 설정, 다른 설정은 그대로 둡니다.
// markdown.mjs
import cudoc from "cudoc-vitepress"
export const syntax = { headingAnchor: "both", callout: "both" }
/** The `markdown` options of the site, and of the collector's renderer. */
export const markdown = (library) => ({
config(md) {
md.use(cudoc, { syntax, library })
},
})
// docs/.vitepress/config.mjs
import { defineConfig } from "vitepress"
import { markdown } from "../../markdown.mjs"
export default defineConfig({
markdown: markdown(),
})
여기서는 both로 시작하기를 권합니다. VitePress 사이트에는 대개 {#id} 앵커와 ::: warning 컨테이너가 이미 들어 있는데, both로 두면 기존 문서를 고치지 않고도 cudoc이 그것들을 자체 문법과 함께 정규화합니다.
3단계 — 스타일시트 가져오기
스타일시트는 테마 진입점에서 가져옵니다. docs/.vitepress/theme/index.js를 만들거나 기존 파일을 확장합니다.
// docs/.vitepress/theme/index.js
import DefaultTheme from "vitepress/theme"
import "@cudoment/cudoc/styles.css"
export default DefaultTheme
문법 확장만 필요하면 여기서 멈추어도 됩니다. 사이트를 실행하면 Markdown 문법의 기능들이 동작합니다. 문서 임베드가 필요하면 계속 진행합니다.
4단계 — 수집기 추가
사이트 루트에 collect.mjs를 만듭니다. 같은 옵션으로 실제 VitePress 렌더러를 만들고, createDocumentCompiler(md)를 collectDocuments에 넘깁니다. collectDocuments는 라이브러리와 준비된 임베드를 함께 출력하므로, 실행이 실패하면 이전 두 결과가 그대로 남습니다. sourceRoot와 렌더러의 디렉터리는 vitepress build에 넘기는 디렉터리이며, 여기서는 docs입니다.
// collect.mjs
import path from "node:path"
import { createMarkdownRenderer, disposeMdItInstance } from "vitepress"
import { createDocumentCompiler } from "cudoc-vitepress"
import { collectDocuments } from "@cudoment/cudoc/node/watch"
import { markdown, syntax } from "./markdown.mjs"
// The renderer the site builds with. It needs no library: collection
// resolves embeds from the documents it collects.
const md = await createMarkdownRenderer(path.resolve("docs"), markdown())
await collectDocuments({
sourceRoot: "docs",
outDir: ".cudoc/documents",
host: "vitepress",
routeSuffix: ".html",
syntax,
compiler: createDocumentCompiler(md),
// Change it whenever markdown.mjs or the VitePress version changes.
compilerId: "vitepress-v1",
})
disposeMdItInstance()
5단계 — 렌더러에 라이브러리 연결
docs/.vitepress/config.mjs에서 loadLibrary(".cudoc/documents")를 markdown()에 넘기고, 사이트의 다른 설정은 그대로 둡니다.
// docs/.vitepress/config.mjs
import { defineConfig } from "vitepress"
import { loadLibrary } from "@cudoment/cudoc/node/library"
import { markdown } from "../../markdown.mjs"
export default defineConfig({
markdown: markdown(loadLibrary(".cudoc/documents")),
})
수집과 렌더링은 모든 Markdown 옵션에서 일치해야 합니다. 두 곳 모두 markdown.mjs에서 옵션을 가져오므로 구조상 자연히 일치합니다. 이 구조를 유지하고, 관련 설정이 바뀌면 compilerId를 바꿉니다.
6단계 — 빌드 전마다 수집 실행
package.json에 스크립트를 추가합니다.
{
"scripts": {
"collect": "node collect.mjs",
"check": "cudoc check --config cudoc.config.mjs",
"dev": "npm run collect && vitepress dev docs",
"build": "npm run collect && npm run check && vitepress build docs"
}
}
cudoc check는 collect 다음에 실행합니다. 수집기가 게시한 라이브러리를 읽기만 하고 수집은 하지 않으므로, 설정 파일에는 그 라이브러리의 위치와 사이트가 루트 기준 이미지를 제공하는 디렉터리, 즉 VitePress의 public 디렉터리만 적으면 됩니다.
// cudoc.config.mjs
export default {
sourceRoot: "docs",
outDir: ".cudoc/documents",
check: { assetDirs: ["docs/public"] },
}
원본 문서를 고친 뒤에는 수집을 다시 실행하고 개발 서버도 재시작해야 합니다. 수집기의 설정으로 호출한 watchDocuments는 변경마다 수집하며, compiler를 포함해 수집기의 설정 전체를 담은 설정 파일을 주면 cudoc collect --watch도 그렇게 합니다. 재시작이 필요한 이유는 플러그인이 설정을 평가할 때 불러온 라이브러리를 계속 들고 있어서, 다시 불러오기 전까지는 원문이 오래되었다고 보고하기 때문입니다. → 참조 검사
7단계 — HTML로도 내보내기 (선택)
cudoc-export를 설치하고 수집된 라이브러리를 HTML로 내보냅니다.
npm install cudoc-export
npx cudoc-export build docs --library .cudoc/documents --out-dir shared-html \
--links host --host-url https://docs.example.com/project/ --asset-dir docs/public
VitePress 빌드 결과와 수집 데이터는 변경되지 않습니다. → 내보내기
이제 쓸 수 있는 것
아래 표에서 문법 행은 3단계 뒤에, 임베드 행은 6단계 뒤에 동작합니다.
| 기능 | 예시 | 자세히 |
|---|---|---|
| 명시적 제목 앵커 | ## 한도 (#limits) |
문법 |
| 네이티브 제목 앵커 | ## 한도 {#limits} |
문법 |
| 제목 배지 | ## 한도 (#limits) (@New) |
문법 |
| 제목이 있는 콜아웃 | > [!NOTE] 시작하기 전에 |
문법 |
| 네이티브 컨테이너 | ::: warning 제목 |
문법 |
| 표 셀 안 중첩 목록 | - 계정<br />-- 이메일 인증 |
문법 |
| 표 셀 병합 | 셀에 ^^나 >>만 쓰기 |
문법 |
| 코드 제목과 강조 줄 | ```js title="retry.js" {2} |
문법 |
| 탭 | > [!TAB] npm |
문법 |
| 변수 | (=version) |
문법 |
| 문서 전체 임베드 | sources: [reference.md] |
임베드 |
| 한 절 임베드 | sources: [reference.md#limits] |
임베드 |
| 제목 요약 표 | select: { depth: 2 } |
임베드 |
| 문서 트리 | render: { type: tree } |
임베드 |
| 사본의 문구 치환 | replace: [{ find, replace }] |
임베드 |
details 컨테이너는 펼침 동작을 그대로 유지합니다.
VitePress에서 알아 둘 점
경로에 .html이 붙습니다. 수집기는 VitePress의 기본값 cleanUrls: false에 맞춰 routeSuffix: ".html"을 씁니다. URL 동작이나 base, 리라이트, 커스텀 경로를 바꾸면 수집 경로도 바꿔야 합니다.
리라이트한 페이지도 임베드를 유지합니다. 문서는 rewrites나 동적 라우트가 다른 경로로 제공하더라도 읽어 들인 파일 기준으로 식별되므로, 경로가 바뀐 페이지도 수집된 문서 기준으로 임베드를 해석합니다. 다만 그 문서로 가는 링크는 수집된 경로를 쓰므로, 리라이트하는 경로는 수집기의 routes로 알려야 합니다.
include하는 페이지에는 임베드를 넣지 않습니다. <!--@include: ...-->로 다른 파일을 끌어오는 페이지도 평소처럼 렌더링되지만, 임베드까지 있으면 그 사실을 알리는 오류와 함께 렌더링이 멈춥니다. 수집은 include한 내용 없이 파일을 읽으므로 임베드를 페이지와 대응시킬 수 없기 때문입니다. 임베드는 아무것도 include하지 않는 페이지에 둡니다.
VitePress의 렌더링은 그대로 유지됩니다. cudoc은 자신이 정규화하는 부분만 바꾸고, 나머지는 VitePress가 렌더링하는 그대로 나옵니다.
- 코드 블록의 문법 강조, 줄 번호, 복사 버튼과 스니펫 import가 그대로 동작합니다.
<script setup>과<style>블록은 여전히 페이지 컴포넌트로 들어갑니다.[[toc]],::: code-group,::: raw, 이모지는 VitePress가 렌더링하는 그대로 나옵니다. 다만syntax.tabs가 호스트 문법을 받으면 코드 그룹은 cudoc 탭 그룹이 됩니다.
컨테이너 안의 내용은 cudoc이 그대로 읽으므로, 코드 그룹이나 ::: raw 안의 제목 앵커와 임베드도 밖에서와 똑같이 동작합니다.
목차와 임베드된 제목. 기본 테마의 개요는 브라우저에서 렌더링된 제목으로 만들어지므로 임베드로 들어온 제목도 나열합니다. 페이지 안의 [[toc]]는 VitePress 자체 토큰으로 렌더링되므로 페이지 자신의 제목만 나열합니다.
빌드 중 경고. 등록하지 않은 콜아웃 타입이나 id가 같은 두 제목 같은 진단은 VitePress가 페이지를 렌더링할 때 페이지와 줄을 알리는 경고로 출력됩니다. 플러그인이 수집한 라이브러리를 가지고 있으면 줄은 파일 첫 줄부터, 그렇지 않으면 frontmatter 다음 줄부터 셉니다. 직접 처리하려면 플러그인 옵션에 onDiagnostic(diagnostic, documentId)를 넘깁니다. → markdown-it 내부 동작
Markdown만 다룹니다. .md를 작성합니다. React .mdx는 처리되지 않습니다. → .md와 .mdx 선택
정적 마크업은 정규화되고 동적 Vue는 되지 않습니다. syntax의 badge가 "host"나 "both"이면 정적 <Badge type="tip" text="1.0" />은 text 속성을 읽어 cudoc 배지가 됩니다. Vue 바인딩을 담은 배지는 컴포넌트가 실행되기 전까지 글자를 알 수 없으므로 원시 HTML로 남습니다. 임의의 Vue 표현식과 커스텀 플러그인 토큰이 임베드를 거쳐 옮겨 가는 것은 보장되지 않습니다.
Eleventy와 공유합니다. 이 어댑터와 Eleventy 어댑터는 모두 cudoc-markdown-it 위에 있습니다. 두 호스트가 같은 코드로 실제 네이티브 토큰 스트림을 변환하므로 제목과 목차 연동이 동일하게 동작합니다.
다음으로
이어서 읽을 문서는 임베드 가이드, markdown-it 내부 동작, 실행 가능한 예제입니다.
- 문서 임베드: 선택, 다중 소스, 갱신 규칙
- markdown-it 내부 동작: 토큰 변환과 호스트 정의
- 실행 가능한 예제: 동작하는 사이트