증분 빌드
증분 빌드는 파일 체크섬과 의존성 변경을 추적해 변경되지 않은 페이지를 건너뜁니다. 페이지가 많은 사이트에서 재빌드 시간이 크게 줄어듭니다.
hwaro build --cache
사용 시점
증분 빌드는 다음 상황에서 효과가 큽니다.
- 사이트에 페이지는 많지만 한 번에 몇 개만 수정하는 경우
- 수정-빌드-미리보기 사이클을 빠르게 돌리고 싶은 경우
- 템플릿이나 설정이 아니라 콘텐츠 위주로 작업하는 경우
사용 방법
캐시 활성화
hwaro build --cache
첫 실행에서는 모든 페이지를 빌드하고 체크섬을 .hwaro_cache.json에 저장합니다. 이후 빌드부터는 변경된 파일만 다시 처리합니다.
전체 재빌드 강제
hwaro build --cache --full
--full 플래그는 캐시를 비우고 모든 페이지를 처음부터 다시 빌드합니다. 빌드가 끝나면 새 캐시가 저장되므로, 다음에 --full 없이 빌드하면 다시 증분 빌드로 동작합니다.
동작 방식
변경 감지
캐시 항목마다 다음을 추적합니다.
- 파일 수정 시각(mtime) — 빠른 1차 확인
- 콘텐츠 체크섬(MD5) — mtime이 달라졌을 때 실제 변경 여부를 검증해, 파일을 touch만 한 경우 같은 오탐을 걸러냅니다
mtime이 그대로면 내용을 읽지 않고 변경 없음으로 판단합니다. mtime이 다르면 콘텐츠 해시를 계산해 비교하고, 실제로 변경됐는지 확인합니다.
의존성 무효화
파일별 체크섬 외에도, 각 페이지가 실제로 무엇에 의존하는지 추적합니다.
- 템플릿 클로저 — 페이지의 템플릿과 그 템플릿이
extends,include,import로 전이적으로 참조하는 모든 템플릿, 그리고 페이지 콘텐츠가 호출하는 숏코드 템플릿까지 포함합니다. 각 페이지의 캐시 항목에 이 클로저의 핑거프린트가 저장되므로,partials/footer.html을 수정하면 해당 파셜을 렌더링하는 템플릿 체인을 가진 페이지만 재빌드됩니다. - 캐스케이드 핑거프린트 — 페이지에 적용된 섹션
[cascade]병합값. 상위_index.md의 캐스케이드를 수정하면 하위 페이지가 재빌드됩니다. - 페이지가 렌더링하는 다른 페이지 — 템플릿이 읽는 이전/다음 페이지, 시리즈
목록, 관련 글, 번역, 브레드크럼 상위 섹션, 상위 섹션의 제목·설명·에셋·정렬 순서
(
section.title,section.pages,section_title등), 리터럴get_page(path="…")로 가져오는 페이지, 그리고@/링크가 가리키는 URL. 글 제목을 바꾸면 그 글을 "다음 글"로 표시하는 페이지가, 페이지의slug를 바꾸면@/로 그 페이지에 링크한 모든 페이지가 다시 렌더링됩니다. - 목록 — 템플릿 클로저가 페이지·섹션 집합을 순회하는 페이지(
site.pages,paginate,get_section, 경로를 계산해서 넘기는get_page등)는 그 집합이 바뀌면 다시 렌더링됩니다. 클로저에는 페이지 콘텐츠가 호출하는 숏코드 템플릿과 출력 포맷 템플릿도 포함되므로, 글 목록을 출력하는{{ recent() }}숏코드도 해당합니다. - 설정 체크섬 — 최종 병합된 설정의 해시. 설정이 바뀌면 모든 항목이 무효화됩니다.
- 렌더 훅 — 설정된 모든
templates/hooks/render-*템플릿의 핑거프린트(렌더 훅 참고)가 모든 페이지의 템플릿 클로저에 포함됩니다. 훅은 페이지의{% include %}/{% extends %}그래프로 도달하는 대상이 아니므로, 훅을 수정하면 좁혀진 집합이 아니라 모든 페이지가 다시 렌더링됩니다. - 템플릿 입력 — 템플릿이 추적 대상 파일 밖에서 읽는 값: 핑거프린트가 붙은
asset()번들 이름,[auto_includes]태그와?v=다이제스트,env()값,data/밖의load_data()파일,resize_image()의 원본 이미지,now()와current_year/current_date/current_datetime변수가 읽는 시계. 이 중 하나라도 바뀌면 모든 페이지가 다시 렌더링됩니다({{ current_year }}를 쓰는 푸터는 1년에 한 번, 초 단위 형식의now()는 빌드마다 다시 렌더링됩니다)..hwaro_cache.json에는 변수 이름과 파일 경로, 다이제스트만 저장되며 환경 변수의 값은 저장되지 않습니다.
템플릿 의존성 추적은 모든 템플릿 참조가 문자열 리터럴이어야 동작합니다.
동적 참조({% include some_var %})를 쓰는 템플릿이 하나라도 있으면
사이트 전체 무효화로 전환되어, 템플릿이 하나라도 바뀌면 모든 페이지를
재빌드합니다. 명시적으로 끌 수도 있습니다.
[build]
template_deps = false # 템플릿이 바뀌면 모든 페이지를 재빌드
건너뛰는 작업
페이지와 그 의존성이 모두 변경되지 않았다면:
- 콘텐츠 파싱은 여전히 수행합니다(내비게이션, 택소노미 등을 구성하기 위해)
- 렌더링과 파일 쓰기는 건너뜁니다 — 기존 출력 파일을 재사용합니다
- SEO 파일(사이트맵, 피드 등)은 항상 다시 생성합니다
serve 모드
개발 서버(hwaro serve)는 더 정밀한 증분 전략을 사용합니다.
| 변경 유형 | 전략 |
|---|---|
| 콘텐츠 파일만 | 영향받는 페이지와 인접 페이지, 그리고 이번 편집으로 내용이 바뀐 목록 페이지만 다시 파싱·렌더링 (아래 참고) |
| 템플릿 파일만 | 수정된 템플릿이 템플릿 클로저에 포함된 페이지만 다시 렌더링 (추적이 꺼져 있거나, 그래프에 동적 참조가 있거나, 수정된 파일이 templates/hooks/ 아래에 있으면 전체 페이지). 수정된 파일이 [[content.generate]]의 body_template이거나 그것이 포함하는 partial이면 전체 재빌드 |
| 설정 파일 | 전체 재빌드 |
| 정적 파일만 | 변경된 파일만 복사. 파일이 페이지나 생성 파일(robots.txt, 피드, 택소노미 페이지, 별칭 스텁 등)이 쓰이는 경로에 놓이거나, 캐시 버스팅 ?v= 해시에 반영되거나, 템플릿의 리터럴 load_data() 경로로 읽히면 전체 재빌드 |
목록 페이지는 템플릿 클로저가 전역 집합을 렌더링하는 페이지입니다. 홈페이지의
site.pages 최신 글 목록, 아카이브, 페이지네이션 인덱스, site.menus로 만든
내비게이션, get_taxonomy_url()로 만든 태그 링크, get_page(path="about.md").title을
출력하는 푸터, 섹션의 글을 순회하는 숏코드를 콘텐츠에서 호출하는 페이지가 여기에
해당합니다. 이런 페이지는
방금 수정한 문서를 소유하지 않으므로 위의 선택 규칙으로는 도달할 수 없습니다. 그래서
serve는 재빌드 전후로 해당 집합의 지문을 계산하고, 지문이 달라진 목록 페이지만 다시
렌더링합니다.
이때 사이트 전체를 하나의 지문으로 묶지 않고 목록 종류마다 자기 입력의 지문을
사용합니다. 공통 base 레이아웃에 들어 있는 내비게이션 파셜은 모든 페이지의 템플릿
클로저에 포함되기 때문에, 지문이 거칠면 메타데이터를 조금만 고쳐도 "전체 재렌더링"이
답이 되어 버립니다. 메뉴는 메뉴 항목의 제목이나 URL이 바뀔 때, 태그 링크는 용어
집합이 바뀔 때, site.pages 반복은 페이지 집합이 바뀔 때 갱신됩니다. 목록에 드러나지
않는 변경(요약을 표시하지 않는 사이트에서 본문만 고친 경우)은 여전히 수정한 페이지만
다시 렌더링합니다.
또한 static/ 밖에 있는 [assets] source_dir도 감시하므로, 별도 디렉터리에 둔 번들
소스도 저장할 때마다 다시 빌드됩니다. 템플릿이 리터럴 load_data() 경로로 읽는 파일과
[og.auto_image]의 로고·배경 이미지는 위치와 상관없이 감시하며, 저장하면 전체 재빌드합니다.
삭제된 페이지
콜드 빌드는 빈 출력 디렉터리에서 시작하므로 삭제한 페이지는 다음 빌드에서
public/에서도 사라집니다. --cache는 디렉터리를 그대로 두기 때문에(그게
목적입니다) 삭제는 캐시로 처리합니다. 모든 항목은 해당 페이지가 쓴 파일을
기록해 두고, 매 빌드마다 살아 있는 페이지가 주장하지 않는 항목의 출력물을
삭제합니다.
삭제된 페이지, 이름이 바뀐 페이지, URL이 옮겨진 페이지(slug, path,
퍼머링크 규칙), 초안으로 바뀐 페이지, expires 날짜가 지난 페이지,
render = false가 된 페이지가 모두 여기에 해당합니다.
제거된 페이지가 함께 만들어 낸 파일도 같이 사라집니다. aliases 리다이렉트
스텁, AMP 미러, 자동 생성된 OG 이미지, 더 이상 아무도 쓰지 않는 텀의 택소노미
페이지(그 텀의 피드 포함), 섹션이 더 이상 채우지 않는 페이지네이션 페이지가
모두 해당합니다. 기능 자체를 끄는 경우([amp] enabled = false,
[og.auto_image] enabled = false, [image_processing] enabled = false, 또는
--skip-og-image / --skip-image-processing 플래그)도 같은 이유로 다음
빌드에서 그동안 발행하던 파일이 정리되며, [image_processing] widths에서 뺀
너비의 이미지도 마찬가지입니다.
소스는 있지만 캐시 항목이 없는 파일도 같은 방식으로 정리됩니다. 각 빌드가
발행한 파일을 기록해 두고 다음 빌드가 더 이상 발행하지 않는 것을 지웁니다.
static/ 사본, [content.files]와 원본(.json/.xml) 사본, 페이지 번들
에셋, 핑거프린트가 붙는 에셋 번들이 모두 해당하므로, 스타일시트를 고치면
main.<hash>.css가 교체될 뿐 public/assets/에 과거 리비전이 쌓이지 않습니다.
이 모든 정리는 캐시가 자신이 쓴 파일을 기억하고 있어야 가능합니다. 출력
디렉터리에 대한 쓸 만한 기록이 없으면(아직 .hwaro_cache.json이 없어 일반
hwaro build로 만든 트리이거나, 캐시 파일이 손상됐거나, 마지막으로 다른 -o에
쓰인 캐시인 경우) --cache 빌드는 콜드 빌드와 똑같이(같은 소유권 규칙으로) 출력
디렉터리를 먼저 비운 뒤 기록을 시작합니다. 캐시는 한 번에 하나의 출력
디렉터리만 기억하므로, hwaro build --cache와 hwaro build --cache -o dist를
번갈아 실행하면 바꿀 때마다 해당 트리를 처음부터 다시 빌드합니다.
hwaro serve --cache는 별도의 캐시를 쓰므로(아래 참고), serve 세션 뒤의
hwaro build --cache가 public/에 대한 기록을 잃지 않습니다.
따라서 --cache 빌드의 출력물은 깨끗한 빌드와 바이트 단위로 같고, 무언가를
정리하려고 hwaro build --full을 쓸 필요가 없습니다. (--full은 캐시만
비우므로 --cache 없이는 아무 효과가 없습니다. 일반 hwaro build는 이미
전체를 다시 빌드합니다.)
캐시 파일
캐시는 프로젝트 루트의 .hwaro_cache.json에 저장됩니다(hwaro serve --cache는
자신이 다루는 .hwaro/serve/ 트리 옆의 .hwaro/serve_cache.json을 쓰며,
.hwaro/는 버전 관리에서 스스로 제외됩니다). 이 파일에는 다음이 들어 있습니다.
- 메타데이터 — 마지막 빌드의 템플릿·설정 체크섬
- 항목 — 파일별 레코드(경로, mtime, 콘텐츠 해시, 출력 경로). 출력 경로는 제거된 페이지의 오래된 파일을 찾는 데에도 쓰입니다
.hwaro_cache.json은 .gitignore에 추가합니다(hwaro init이 스캐폴드하는 .gitignore에는 이미 포함되어 있습니다).
.hwaro_cache.json
플래그 참조
| 플래그 | 설명 |
|---|---|
--cache |
증분 빌드 캐시 활성화 |
--full |
전체 재빌드 강제(캐시를 비우고 다시 채움) |
예시
# 첫 빌드 — 캐시 생성
hwaro build --cache
# 파일 몇 개 수정 후 재빌드 — 변경된 페이지만 다시 렌더링
hwaro build --cache
# 뭔가 이상할 때 — 깨끗하게 전체 재빌드
hwaro build --cache --full
# 다른 플래그와 조합
hwaro build --cache --minify