본문으로 건너뛰기
5분 AI.
TRENDSHIFT MONTHLY #8 딥다이브 · 2026-10-23 분석

HowToLiveBetter 딥다이브
— Markdown 한 벌로 만드는 지식 출판 시스템

문서·출판 시스템

빠른 판단

무엇
중국어 생활 지식 Markdown을 검색 웹앱·전자책·복습 카드로 변환하는 정적 지식 출판 프로젝트
이럴 때
분야별 지식을 비용·근거·출처와 함께 구조화하고 같은 원고에서 검색·오프라인 배포·복습 도구를 만들 때
입력
등급·출처 등 필드를 갖춘 Markdown 원고
결과
검색용 데이터·정적 웹앱·전자책·복습 카드
생활 지식 767개를 모은 책이 어떻게 검색 웹앱, 전자책, 인쇄용 PDF, 오프라인 파일, 복습 카드가 될까요? HowToLiveBetter는 그 연결 과정을 작은 JavaScript 도구와 정적 HTML로 보여 줍니다. 이 글에서는 생활 조언의 정답을 고르는 대신, 글을 데이터로 다루고 여러 형태로 배포하는 기술을 배웁니다. 저장소: eternity4719/HowToLiveBetter · 스타 68,151 · 포크 5,899 · 주 언어 HTML · 본문 CC BY 4.0 / 명시된 코드 MIT. 수치는 2026-10-23 GitHub 기준, 소스 설명은 커밋 e2605d3 기준입니다.
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

사람이 읽는 원고를 기계도 읽게 만든, 작은 지식 서비스

HowToLiveBetter는 건강·시간·돈·법적 위험 등 생활 의사결정에 관한 중국어 지식 모음입니다. 항목마다 비용, 쉬운 요약, 기대 이득, 증거 등급, 출처, 비고를 적습니다. 기술적으로는 Markdown 원고를 공통 데이터로 삼아 검색 화면과 여러 출판물을 만드는 정적 지식 시스템입니다.

원본은 book/의 Markdown 34개입니다. index.html이 이 파일들을 읽고 JavaScript 객체로 바꾸어 검색·필터 화면을 만듭니다. 별도의 빌드 도구는 같은 원고를 EPUB, PDF, 오프라인 HTML, Anki 카드로 변환합니다. 따라서 이 저장소를 공부할 때는 원고 → 파싱 → 데이터 객체 → 화면 또는 파일의 흐름을 따라가면 됩니다.

용어부터
정적 웹사이트와 동적인 화면
서버가 미리 저장된 HTML·CSS·JavaScript·텍스트 파일을 전달하면 정적 호스팅입니다. 받은 JavaScript가 검색하고 화면을 바꾸는 것은 가능합니다. 여기서는 검색을 처리하는 애플리케이션 서버가 없고, 독자의 브라우저가 그 일을 맡습니다.
한 문장 비유

한 원고를 서점·도서관·복습 카드에 보내는 작은 출판사

편집자는 book/에서 원고를 고칩니다. 검색 웹페이지는 도서관의 검색대, EPUB은 휴대용 책, PDF는 인쇄본, Anki는 복습용 카드입니다. 매체마다 다시 집필하지 않고, 같은 원고를 각 매체에 맞게 포장합니다.

문제도 출판사와 닮았습니다. 원고 형식이 흐트러지면 변환기가 내용을 놓치고, 항목 번호가 바뀌면 다른 항목을 가리키던 링크나 복습 기록이 어긋납니다.

1-1. 사용자가 만나는 다섯 가지 결과

결과담당 파일주로 배우는 것
검색 웹앱index.html브라우저 파일 요청, 파서, 상태, DOM 갱신
EPUBtools/epub/build.mjsMarkdown → XHTML → 전자책 패키징
PDFtools/pdf/build.mjsPandoc 변환과 Typst 조판의 연결
오프라인 HTMLtools/offline/build.mjs데이터 내장과 분석 스크립트 제거
Anki 카드tools/anki/build.mjsSQLite 파일 생성, ID 설계, ZIP 포장

selfcheck.html에는 질문에 답하면 관련 항목을 모아 주는 자기 점검 화면도 있습니다. 이 화면은 원고 전체를 새로 쓰지 않고 기존 항목을 재사용합니다. skills/life-decision-guide/SKILL.md는 AI 도구가 원고를 찾고 출처를 붙여 답하도록 안내하는 문서입니다. 저장소가 자체 LLM 서버나 추론 엔진을 제공하는 구조는 아닙니다.

공부의 중심
작은 기능도 데이터의 일관성까지 책임져야 제품이 됩니다

검색 입력창 하나를 만드는 데서 끝내지 않습니다. 데이터가 어디서 오는지, 실패하면 어떻게 복구하는지, 다른 출력물과 내용이 같은지, 배포된 파일이 어느 커밋에서 만들어졌는지까지 연결해서 볼 수 있습니다.

2왜 주목받는가

큰 반응을 만든 주제와, 기술적으로 오래 남는 설계는 나누어 읽기

2-1. 생활 문제를 비용·이득·근거로 정리한다

2026-10-23 GitHub 기준 스타 68,151개, 포크 5,899개이며 TrendShift Monthly #8에 선정되었습니다. 2026-09-07 생성된 저장소가 10월 하순에 이 규모의 관심을 받았다는 사실은 확인할 수 있습니다. 다만 방문 경로나 소셜 확산 자료 없이 특정 사건이 유행을 만들었다고 단정할 수는 없습니다.

내용과 구현에서 읽히는 매력은 명확합니다. 폭넓은 생활 문제를 짧은 항목으로 정리하고, 비용·이득·출처를 같은 자리에 배치하며, 웹과 오프라인 파일 모두로 접근하게 합니다. 다음 비교는 이런 구성에서 추론한 강점입니다. 제품별 성능이나 실제 이용자 선호를 측정한 결과는 아닙니다.

비교 대상HowToLiveBetter의 선택그 대신 생기는 제약
일반 블로그항목마다 같은 필드와 등급을 유지해 여러 조건으로 필터링집필자가 엄격한 원고 형식을 지켜야 함
개인 노트 도구공개 원고·변경 이력·출처를 함께 배포공동 편집은 Git·PR 흐름을 익혀야 함
일반 위키비용과 기대 이득을 항목별로 비교하기 쉬움정해 둔 분류가 복잡한 맥락을 단순화함
챗봇 답변원고의 항목 번호와 출처로 결론을 추적책에 없는 질문까지 자동으로 해결하지 않음
PDF 단독 배포검색·필터·전자책·복습 카드를 함께 제공출력별 변환기와 일관성 검사가 필요함

주목할 기술은 새 프레임워크가 아니라, 같은 지식을 여러 사용 방식에 맞게 재사용하는 연결 구조입니다. 커밋 e2605d3의 본문 767개는 A등급 495개, B등급 220개, C등급 52개로 README의 표시와 같습니다. tools/sync-stats.mjs는 이런 숫자를 계산하고 소개 문구와 배지에 반영하는 역할까지 맡습니다.

2-2. 출처를 공개하는 일과 근거가 강한 일은 다르다

프로젝트 설명은 학술 논문과 공식 문서를 강조합니다. 그러나 README는 공식적으로 공개되지 않은 일부 사건에 기자 이름이 명시된 자체 취재 기사와 웹 아카이브를 예외로 사용한다고 밝힙니다. 따라서 모든 출처가 논문 또는 공식 문서라고 소개하면 실제 기준을 좁혀 전달하게 됩니다.

README의 읽기 안내는 A등급을 메타분석·대형 시험 중심으로 짧게 소개하지만, 자세한 등급 설명은 관찰 연구와 법령·공식 문서도 A에 포함될 수 있다고 설명합니다. A라는 글자만 보고 인과관계가 입증되었다고 해석하면 안 됩니다. 개발자는 이 차이를 데이터 모델에서도 보존해야 합니다. grade는 연구 설계 전체를 대신하지 못합니다.

비유

택배의 운송장 번호가 있으면 물건이 어디서 왔는지 확인할 수 있습니다. 그렇다고 내용물의 품질까지 자동으로 보장되지는 않습니다. 출처 추적, 증거 등급, 적용 조건은 서로 다른 확인 항목입니다.

2-3. 규칙이 보이므로 고치기도 쉽다

가성비 분류는 COST_W와 ratioOf()로 읽을 수 있고, 문장을 나누는 기준은 splitGain()에 공개되어 있습니다. 결과가 이상하면 입력 원고와 규칙을 대조할 수 있습니다. 초보자에게는 “화면 뒤에 숨어 있던 결정”이 짧은 함수로 드러나는 저장소라는 점이 장점입니다.

3기술 스택 전체 지도

독자에게 필요한 실행 환경과 편집자가 쓰는 빌드 환경 구별하기

3-1. 백엔드: 검색 서버 대신 정적 파일

원고를 저장하는 데이터베이스나 로그인 API는 없습니다. 정적 호스트가 index.html, README.md, book/*.md, updates.json을 전달합니다. 요청을 받은 뒤 검색하는 쪽은 브라우저입니다. 저장소에서 발견되는 SQLite는 Anki 배포 파일을 만드는 빌드 단계에 쓰이며, 서비스용 DB가 아닙니다.

용어부터
빌드 의존성과 실행 의존성
공장에서 책을 찍을 때 쓰는 기계와, 독자가 책을 읽을 때 필요한 물건은 다릅니다. Node.js·Pandoc·Typst는 제작 도구입니다. 독자가 검색 웹페이지를 읽는 데 그 도구들을 설치할 필요는 없습니다.

3-2. 프론트엔드: HTML·CSS·브라우저 기본 API

역할실제 기술소스에서 확인할 곳
화면과 스타일정적 HTML, 파일 내부 CSSindex.html, selfcheck.html
원고 읽기fetch(), Promise.all(), Map 캐시readText(), init()
검색과 필터문자열 포함 검사, Set, DOM의 hiddenparseReadme(), apply()
화면 밖 갱신 연기IntersectionObserver, WeakMapCARD_IO, renderCard()
상태 보존URLSearchParams, history, localStoragereadUrl(), writeUrl(), 자기 점검의 save()

React·Vue·프론트엔드 번들러에 대한 의존성 선언은 없습니다. EPUB에서 쓰는 marked를 웹 검색 파서에도 쓴다고 오해하기 쉽지만, 검색 화면은 parseReadme()와 자체 텍스트 렌더링 함수를 사용합니다.

3-3. 인프라·빌드: 실제 선언된 버전

도구버전·설정값근거 파일과 의미
Node.jsCI는 22.github/workflows/book.yml의 주 버전 지정. 패치 버전 고정은 아님
marked18.0.13tools/epub/package.json의 유일한 npm 의존성
lockfilelockfileVersion: 3tools/epub/package-lock.json도 같은 marked 버전 고정
marked의 Node 조건>=20lockfile의 패키지 engines.node
Pandoc3.11CI의 PANDOC_VERSION. PDF 도구 주석은 최소 3.1 안내
Typst0.15.1CI의 TYPST_VERSION. PDF 도구 주석은 최소 0.13 안내
Java / EPUBCheckTemurin 21 / 5.4.0생성한 EPUB의 형식 검증
Actionscheckout·setup-node·upload-artifact·setup-java 모두 @v4전자책 워크플로에서 쓰는 액션 참조
운영체제·폰트ubuntu-latest, fonts-noto-cjkPDF를 만드는 CI 환경. OS와 폰트 패키지는 완전 고정되지 않음

tools/epub/package.json에는 "type": "module"과 "build": "node build.mjs"가 있습니다. 루트 package.json, Cargo.toml, pyproject.toml, go.mod, Dockerfile은 없습니다. Python은 README의 간단한 정적 서버 예시에 등장하며 애플리케이션 백엔드 언어는 아닙니다.

“설치 없이 읽을 수 있다”는 말과 “모든 배포 파일을 도구 없이 만들 수 있다”는 말은 다릅니다. 특히 Anki 빌드는 node:sqlite의 DatabaseSync를 사용합니다. README는 Node 22.13 이상을 안내하며, Node 22.13.0 공식 문서에서도 이 API를 확인할 수 있습니다.

3-4. 의존성이 적어도 재현성은 따로 살핀다

lockfile의 marked 다운로드 주소는 registry.npmmirror.com입니다. npm ci는 lockfile을 따라 설치하지만, ubuntu-latest, Node의 주 버전, 액션의 버전 태그, 설치 시점의 시스템 폰트까지 모두 같은 바이트로 고정하지는 않습니다. 또 buildStamp()가 현재 시간을 넣으므로 같은 원고도 빌드 시각에 따라 결과 바이트가 달라질 수 있습니다.

4아키텍처 심화 분석

네 가지 설계 패턴으로 데이터의 이동 경로 따라가기
[편집하는 원본] README.md ── 목차·용어·소개 book/*.md ── 34개 장, 767개 항목 docs/*.md ── 장문 설명·참조표 │ ├─────────────────── [독자의 브라우저] │ readText() / __CORPUS__ │ ↓ │ parseReadme() → 항목 객체 │ ↓ │ build() → apply() → 검색·필터·화면 │ └─────────────────── [Node.js 제작 도구] 공통 파일 읽기: tools/lib/book.mjs ├─ EPUB: marked → XHTML → ZIP ├─ PDF: Pandoc → Typst → PDF ├─ offline: 원고 내장 → HTML 한 파일 └─ Anki: SQLite → ZIP → .apkg ↓ GitHub Actions → artifact ↓ PR 외 이벤트 고정 Release: epub-latest [보조 데이터] Git 이력 → tools/updates.mjs → updates.json [자기 점검] selfcheck.html → 기존 항목 참조 + 브라우저 답안 저장

4-1. 패턴 ① 문서에 스키마를 부여하는 작은 컴파일러

index.html의 parseReadme()는 줄을 순서대로 읽는 파서입니다. 장 제목을 만나면 sec, 항목 제목을 만나면 entry를 바꾸고, flush()로 직전 항목을 배열에 넣습니다. 함수 이름과 달리 실제 본문 입력은 여러 book/*.md를 합친 문자열입니다.

// parseReadme()에서 발췌: 제목의 모양이 데이터 경계를 결정한다.
const flush = () => {
  if (entry && sec) { sec.entries.push(entry); }
  entry = null;
};
// 장 제목: # 또는 ## 뒤에 번호와 마침표
/^#{1,2} (\d+)\. (.+)$/
// 항목 제목: ### 뒤에 번호와 마침표
/^### (\d+)\. (.+)$/

문서 속 비용 태그를 읽어 money, time, will, level, lens, who도 채웁니다. 마지막에는 dispute, todo, 비용 합계 cs, 가성비 ratio, 검색용 문자열 hay를 계산합니다. 사람이 읽는 원고가 기계가 읽는 레코드가 되는 지점입니다.

원고의 역할객체 필드설계상 의미
식별과 제목sec, n, title장·항목 번호로 링크와 맵의 키를 구성
사람이 읽는 설명cost, human, gain비용·쉬운 요약·상세 이득을 분리
검토할 정보grade, src, note등급·출처·적용 조건을 유지
기계용 분류money, time, will, level, lens문장 해석 대신 명시된 태그로 필터링
설계의 경계
일반 Markdown 전체를 이해하는 파서는 아닙니다

필드는 정해진 접두사와 한 줄 형식을 기대합니다. 설명을 임의로 다음 줄에 이어 쓰면 같은 필드에 자동으로 합쳐 주지 않습니다. 제목 단계나 구두점을 바꾸는 편집도 데이터 추출을 바꿀 수 있습니다. 이 프로젝트에서는 원고 형식 자체가 API 계약처럼 작동합니다.

4-2. 패턴 ② 온라인·오프라인 입력을 같은 처리 흐름에 연결

index.html의 init()은 window.__CORPUS__가 있으면 내장 원고를 쓰고, 없으면 readText()로 정적 파일을 요청합니다. 읽어 온 문자열 뒤의 파싱·화면 생성은 공통입니다. 입력 방식의 차이를 앞단에서 흡수하는 간단한 어댑터 구조입니다.

// init()의 실제 분기
const EMBED = window.__CORPUS__;
const md = EMBED ? EMBED.readme : await readText('README.md');
// 각 장의 내용도 EMBED.parts[f] 또는 readText(f)에서 읽는다.
const sections = parseReadme(parts.join('\n\n'));
build(sections);

tools/offline/build.mjs는 원고·장문·업데이트 정보를 하나의 객체로 묶어 페이지에 내장합니다. 광고 이미지와 후원 QR 이미지도 data URI로 바꾸며, 분석용 GA 구간은 제거합니다. Google Fonts 링크는 남아 있어 폰트를 불러오려는 외부 요청이 발생할 수 있습니다. 알려진 삽입 지점을 찾는 must()가 실패하면 빌드를 중단합니다. 단순 문자열 치환이지만, 페이지 구조가 바뀌었음을 조용히 무시하지 않습니다.

기본 검색 페이지는 로컬 파일 열기를 감지하면 오류를 안내합니다. 반면 원고가 내장된 오프라인 HTML은 파일 요청 없이 본문을 처리합니다. 다만 자기 점검 화면 링크는 온라인 주소로 바뀌므로 모든 연결 화면까지 오프라인에 포함된 것은 아닙니다.

4-3. 패턴 ③ 상태와 파생 결과를 나누어 화면 갱신 줄이기

state는 검색어와 필터별 Set을 갖고, apply()는 원본 항목을 바꾸지 않은 채 카드의 표시 여부를 계산합니다. 가성비·등급·비용 필터는 같은 축의 선택값 중 하나와 일치하면 통과하고, 서로 다른 축의 조건은 함께 만족해야 합니다. 검색어는 공백으로 나누며 terms.every()로 모든 단어의 포함 여부를 확인합니다.

// apply()에서 발췌: 단어가 모두 들어 있는지 검사
const terms = state.q.toLowerCase().split(/\s+/).filter(Boolean);
if (ok && terms.length && !terms.every(t => e.hay.includes(t))) ok = false;
if (el.hidden === ok) el.hidden = !ok;

검색용 hay는 미리 합쳐 두지만 역색인을 만들지는 않습니다. 필터를 적용할 때 카드들을 순회하는 선형 검색입니다. 입력은 120ms 지연해 처리하고, CARD_IO는 rootMargin: '600px 0px'로 화면 가까이 온 카드의 텍스트 갱신을 준비합니다.

카드의 DOM 뼈대 자체는 build()에서 전체 생성합니다. 따라서 이 방식은 화면에 필요한 요소만 남기는 완전한 가상 목록과 다릅니다. card.key와 현재 검색어 키가 달라졌을 때만 텍스트를 다시 만들고, 화면 밖 카드는 스크롤로 가까워졌을 때 갱신하는 방식입니다.

비유

도서관의 모든 책꽂이는 그대로 두고, 눈앞에 있는 책의 안내표만 새 검색어에 맞게 바꾸는 셈입니다. 책꽂이 수는 줄지 않지만 안내표를 전부 다시 쓰는 수고는 줄어듭니다. Intersection Observer 문서에서 화면 근처 진입을 관찰하는 원리를 이어 배울 수 있습니다.

4-4. 패턴 ④ 중복 구현을 계약 검사로 묶기

tools/lib/split-items.mjs와 index.html에는 긴 문장·출처를 나누는 규칙이 각각 있습니다. tools/check-split.mjs는 브라우저 쪽 함수 구간을 node:vm으로 읽어, 도구 쪽 splitGain()·splitSrc()와 결과를 비교하도록 작성되어 있습니다.

출력이 같은지만 보지 않습니다. 이득·비고를 나눈 뒤 다시 붙였을 때 원문과 같은지, 굵은 글씨 표시가 조각 사이에서 끊기지 않는지도 검사합니다. tools/sync-stats.mjs는 비용 규칙의 특정 소스 줄이 유지되는지도 확인합니다. 문자열 모양에 의존하는 검사는 빠르게 만들 수 있지만, 의미가 같아도 형식을 바꾸면 깨질 수 있습니다.

여러 출력물을 유지할 때 중요한 것은 파일을 많이 만드는 능력보다, 출력 사이의 의미가 어긋나지 않게 확인하는 능력입니다. 이 저장소의 중복 규칙과 검사는 “공통 모듈로 합칠지, 복사본을 두고 동등성을 검사할지”를 고민하기 좋은 사례입니다.

5디렉토리 구조 해부

원고, 독자 화면, 제작 도구, 검증 기록의 경계
HowToLiveBetter/ ├── README.md 소개·목차·등급·용어·사용법 ├── book/ 본문 34개 Markdown 파일 │ ├── 01-不要早死.md │ ├── 04-不要浪费时间.md │ └── ... 34번 장까지 ├── docs/ │ ├── *.md 장문 9편 + 인용 참조표 1개 │ └── 核实记录/ 출처 검증 기록 239개 ├── index.html 검색·필터·용어·교차 참조 화면 ├── selfcheck.html 자기 점검 화면 ├── updates.json Git 이력에서 만든 항목 변경 정보 ├── tools/ │ ├── lib/ 원고 읽기·긴 문장 분리·ZIP 공통 도구 │ ├── epub/ 전자책 빌더·npm 매니페스트·CSS │ ├── pdf/ PDF 빌더·Typst 템플릿 │ ├── offline/ 데이터 내장 HTML 빌더 │ ├── anki/ SQLite 기반 카드 빌더 │ ├── check-*.mjs 내용·참조·분리·링크 검사 │ ├── sync-stats.mjs 수치 동기화·부가 도구 호출 │ └── updates.mjs 항목 변경 추적 ├── skills/life-decision-guide/ 원고를 활용하는 AI 도구용 지침 ├── .github/workflows/ 전자책 제작·정기 링크 검사 ├── ads/ 광고·후원 이미지 ├── og.png 공유·전자책 표지 이미지 ├── LICENSE 본문용 CC BY 4.0 └── LICENSE-CODE 명시된 코드용 MIT

docs/核实记录/는 완성된 설명만 보관하는 곳이 아닙니다. 자료를 어떻게 확인하고 수정했는지 남기는 기록입니다. 반면 tools/lib/book.mjs의 readBook()은 README에 연결된 본문·장문 파일을 골라 출력 목록을 만듭니다. 검증 기록 239개가 모두 전자책에 자동으로 들어가는 구조는 아닙니다.

5-1. 파일 수와 물리적 줄 수

구분파일 수줄 수해석
Markdown29025,443본문·장문·검증 기록·개발 지침 포함
HTML52,310HTML 안의 CSS·JavaScript 포함
JavaScript 모듈141,846.mjs 도구. HTML 내부 JS와 별도
YAML6272워크플로·이슈 양식·후원 설정
JSON3435업데이트 데이터·의존성 선언·lockfile
Typst / CSS1 / 174 / 16PDF 조판 템플릿 / EPUB 스타일

위 수치는 커밋 e2605d3의 추적 파일 기준이며, 빈 줄·주석을 포함한 물리적 줄 수입니다. 이미지와 .git/은 줄 수에서 제외합니다. 본문만 보면 book/는 7,360줄, 검증 기록은 14,455줄입니다. index.html은 1,672줄이며 그 안의 인라인 JavaScript는 약 1,133줄입니다. HTML 줄 수와 내부 JS 줄 수를 더하면 중복 집계가 됩니다.

GitHub의 주 언어 HTML만 보고 이 저장소를 평가하면, 원고 데이터와 출판 자동화 도구라는 두 축을 놓칩니다. 전체 추적 파일은 331개, 이미지 등을 포함한 파일 내용은 약 6.06MiB입니다. 작은 코드로 비교적 큰 지식 자산을 다룬다는 점이 이 프로젝트의 규모를 읽는 기준입니다.

6학습 포인트 (기술별)

문자열 처리에서 배포까지, 각 기술을 실제 문제와 연결하기

6-1. 파서와 데이터 모델: 상태를 기억하며 줄 읽기

parseReadme()는 현재 어느 장과 항목을 읽는지 기억하는 작은 상태 기계입니다. “새 제목이 나오면 이전 항목을 닫는다”는 규칙은 로그 분석, 설정 파일 읽기, CSV 후처리에도 응용할 수 있습니다. 반대로 들여쓰기·여러 줄·인용문까지 허용하려면 정규식 몇 개로 충분한지 먼저 검토해야 합니다.

학습용 변형으로는 원고를 직접 바꾸기 전에 유효성 검사기를 설계해 보세요. 필수 필드가 없을 때 빈 문자열로 둘지 오류로 처리할지, 모르는 비용 태그를 어떻게 표시할지 정합니다. 현재 비용 계산의 ?? 0은 모르는 값을 0점으로 처리하므로, 잘못된 입력이 가장 낮은 비용처럼 보일 수 있다는 점을 생각해 볼 수 있습니다.

6-2. 순수 함수: 판단 기준을 표로 먼저 설명하기

tools/lib/book.mjs의 ratioOf(cost, level)는 입력 두 개로 분류를 돌려줍니다. 비용은 돈·시간·의지의 각 0~2점을 합한 0~6점입니다. 아래 표는 원문의 분류값을 한국어로 풀어 쓴 것입니다.

이득 규모비용 합계분류 결과
큼0매우 높음
큼1~2높음
큼3~6보통
중간0높음
중간 / 작음그 밖의 조합보통

증거 등급 A/B/C는 이 함수의 인자가 아닙니다. 근거의 신뢰도와 비용 대비 이득을 서로 다른 축으로 보관하기 때문입니다. 분류 기준은 저자의 판단을 코드화한 것이며, 사용자 개인에게 최적인 결정을 수학적으로 증명하는 공식은 아닙니다.

// ratioOf()를 이해하기 위한 학습용 표현: 출력값을 한국어로 바꿨다.
function classify(cost, level) {
  if (level === '큼') {
    return cost === 0 ? '매우 높음' : cost <= 2 ? '높음' : '보통';
  }
  return level === '중간' && cost === 0 ? '높음' : '보통';
}
// 실습: cost 0, 2, 3과 level 세 종류의 결과를 표와 대조한다.

6-3. 비동기 I/O: 재시도할 실패를 구별하기

index.html의 readText(path, tries = 3)는 성공한 텍스트를 메모리 Map에 저장합니다. 실패한 요청은 캐시에 남기지 않아 다시 읽을 수 있습니다. 파일이 정말 없는 404와 잠시 바쁜 서버의 5xx를 똑같이 재시도하지 않는 것도 배울 점입니다.

상황현재 처리학습 포인트
성공CORPUS_CACHE에 보관재시도 화면에서 이미 받은 장은 다시 받지 않음
5xx / 429재시도 대상일시적 장애와 요청 제한을 고려
기타 HTTP 실패즉시 오류 전달재시도해도 고쳐지지 않을 입력을 구별
네트워크·시간 초과최대 3회 시도, 대기 0.5초·2초각 요청의 실패 범위와 사용자 복구 흐름

지원 브라우저에서는 AbortSignal.timeout(20000)을 연결합니다. Promise.all()의 장 하나가 끝내 실패하면 전체 로딩도 실패하지만, 성공한 장은 캐시에 남습니다. 네트워크가 약한 상황에서 “전체를 처음부터 다시 받기”를 줄이는 구조입니다.

6-4. DOM 렌더링: 텍스트와 마크업을 구분하기

renderText()는 링크·굵은 글씨 같은 허용한 표현을 골라 DOM 노드로 만듭니다. mkLink()는 a.textContent에 표시 문구를 넣고, 일반 텍스트는 텍스트 노드와 하이라이트 처리로 전달합니다. 자기 점검의 esc()도 특수문자를 이스케이프합니다.

// mkLink()의 핵심: 표시할 문구를 HTML로 해석하지 않는다.
const a = document.createElement('a');
a.href = href;
a.target = '_blank';
a.rel = 'noopener';
a.textContent = text;

innerHTML을 쓰는 곳도 있으므로 파일 전체가 텍스트 API만 쓴다고 설명하면 틀립니다. 중요한 것은 어떤 값이 외부 입력이며 어느 출력 경로에서 이스케이프되는지를 따라가는 습관입니다. 임의 사용자가 원고를 업로드하는 서비스로 확장한다면 URL과 HTML의 신뢰 경계도 다시 설계해야 합니다.

6-5. 문서 변환과 바이너리: 책과 카드도 파일 포맷이다

tools/epub/build.mjs는 new Marked({ gfm: true })를 만들고, renderer.heading()으로 제목 ID와 목차 정보를 수집합니다. rewriteHref()는 포함된 문서의 상대 링크를 책 내부 링크로 바꿉니다. 원시 HTML과 이미지는 기본 본문 변환에서 제거하고, 표지는 별도로 추가합니다. Marked 확장 문서와 나란히 읽으면 렌더러를 바꾸는 지점을 찾기 쉽습니다.

tools/lib/zip.mjs의 zip()는 CRC32, 로컬 헤더, 중앙 디렉터리, 끝 레코드를 직접 만듭니다. EPUB의 mimetype은 첫 파일이면서 압축하지 않도록 store: true로 넣습니다. 이는 EPUB 표준의 컨테이너 규칙과 연결되는 작은 구현입니다. 직접 ZIP을 만드는 것은 학습 가치가 크지만 범용 압축 도구의 모든 기능을 구현한 것은 아닙니다.

Anki 빌더의 parseSection()은 제목·요약·비용 등을 읽고 DatabaseSync로 collection.anki2를 만듭니다. 이를 빈 미디어 목록과 ZIP으로 묶으면 .apkg가 됩니다. 상세 이득과 모든 출처 원문을 카드에 복제하지 않고, 요약과 온라인 원문 링크를 제공합니다.

꼭 짚을 개념
안정적인 식별자와 재가져오기
Anki 노트의 GUID는 hltb-장번호-항목번호입니다. 같은 항목으로 인식되면 내용 갱신과 복습 기록 보존을 연결할 수 있습니다. 그러나 앞 항목을 삭제해 뒤 번호가 당겨지면 다른 내용이 같은 ID를 차지합니다. 순번을 식별자로 쓸 때 생기는 데이터 마이그레이션 문제입니다.

6-6. CI와 콘텐츠 품질: 검사가 무엇을 보장하는지 읽기

tools/check-plain.mjs는 쉬운 요약의 길이를 공백 제외 120자로 제한하고, 통계 약어·연구 전문 표현·추상적인 상투어를 검사합니다. 새 숫자를 검사하는 --numbers는 오탐 때문에 기본 CI에서 제외됩니다. 문장 규칙 검사는 할 수 있지만 출처의 참됨을 증명하는 검사는 아닙니다.

.github/workflows/book.yml의 refs, plain, split, stats, selfcheck는 build와 독립된 job입니다. build에 이 검사들을 기다리는 needs가 없으므로, 일부 검사에 실패해 워크플로가 빨갛게 보여도 배포 단계는 별도로 진행될 수 있습니다. 반면 빌드 job 안의 EPUBCheck 실패는 그 뒤 업로드·릴리스 단계의 진행을 막습니다.

정기 링크 검사는 월요일 UTC 01:00, 한국 시간 10:00에 예약됩니다. tools/check-links.mjs는 동시 작업 수 8, 논리적 대기 제한 20초를 쓰고, 확정된 죽은 링크·접속 실패·TLS 오류를 구분해 보고합니다. Promise.race()로 대기 시간을 제한하므로 요청 자체를 취소하는 구현과는 구별해야 합니다. 링크 실패는 보고용이며 그 개수로 실패 종료하지 않습니다.

검사 통과 여부를 읽기 전에, 그 검사가 어떤 오류를 찾고 어떤 배포를 막는지부터 확인해야 합니다. tools/updates.mjs는 Git 이력에서 항목을 추적하므로 완전한 이력을 요구합니다. CI의 stats job에만 fetch-depth: 0이 있는 이유도 이 요구와 연결됩니다.

7하드웨어 / 시스템 요구사항

읽기, 정적 제공, 배포 파일 제작은 서로 다른 작업

저장소는 CPU 코어 수·RAM·디스크의 공식 최소 사양이나 처리 속도 벤치마크를 제시하지 않습니다. 검색은 모델 추론이나 벡터 DB를 사용하지 않으므로 GPU를 요구하는 구성 요소도 없습니다. 아래에서 환경 요구는 소스·CI 설정 기준이고, 자원 여유에 관한 설명은 학습 환경을 위한 추정입니다.

하려는 일필요한 환경자원·주의점
검색 웹페이지 읽기JavaScript·fetch·IntersectionObserver를 지원하는 브라우저데스크탑·모바일 브라우저에서 읽는 구조. 지원 버전별 실측 표는 없음
정적 웹페이지 제공정적 HTTP 호스트README는 GitHub Pages·Nginx·객체 저장소 등을 안내
오프라인 파일 읽기릴리스의 단일 HTML과 브라우저기본 index.html을 더블클릭하는 경우와 다름
EPUB 제작Node.js, 고정된 marked 의존성읽기에는 Node 불필요. 제작자는 npm 의존성 필요
PDF 제작Node.js + Pandoc + Typst + CJK 폰트폰트가 달라지면 글줄·페이지 나눔이 달라질 수 있음
Anki 제작README 기준 Node 22.13 이상node:sqlite 사용. 별도 SQLite 서버 불필요
CI 수준 EPUB 검사Java 21 + EPUBCheck 5.4.0EPUB 제작과 형식 검증의 도구를 구별

7-1. 파일 크기로 예상해 볼 수 있는 범위

본문 Markdown은 합계 2,385,074바이트, 약 2.27MiB입니다. README까지 합하면 약 2.33MiB이며 전송 압축 전 텍스트 크기입니다. 온라인 초기 로딩은 README에서 장 목록을 얻은 뒤 장 34개를 병렬로 읽고, 업데이트 JSON도 별도로 읽습니다. 이미지·폰트·전송 헤더까지 포함한 실제 다운로드 크기와는 다릅니다.

본문은 작은 편이지만 메모리 사용량이 원고 바이트와 같지는 않습니다. 원문 문자열, 항목 객체, 검색 문자열, 전체 카드 DOM이 함께 존재합니다. 저사양 기기에서는 최초 DOM 생성과 검색어에 따른 텍스트 갱신을 따로 관찰하는 것이 좋습니다. 실제 지연 시간은 측정 자료가 없으므로 숫자로 보장할 수 없습니다.

학습 환경의 출발점
정적 웹 실습부터 시작해 필요한 제작 도구를 더하세요

공식 최소 하드웨어 사양은 없으므로, 기본 웹 실습은 브라우저와 정적 서버의 실행 환경부터 확인하세요. 원본 파일 자체는 약 6.06MiB이며, 제작 도구·폰트·Java·생성물의 저장 공간은 별도로 확보해야 합니다. PDF 전체 제작을 위한 정확한 메모리·용량 수치는 별도 측정이 필요합니다.

8직접 해볼 수 있는 실습 과제

읽기 → 작은 재구현 → 경계 검사 → 설계 개선 순서

아래는 학습자가 별도 실습용 사본이나 새 예제로 진행할 과제입니다. 새 생활 조언을 추가하는 대신 파서·상태·ID 같은 소프트웨어 동작을 다룹니다. 각 과제의 완료 기준은 “화면이 보인다”보다 구체적으로 잡았습니다.

실습 1 · 원고를 데이터로 읽기

제목과 필드를 표로 옮기기 입문

읽을 곳: book/04-不要浪费时间.md와 index.html의 parseReadme().

할 일: 첫 항목 하나를 골라 각 줄이 title, human, cost, gain, grade, src, note 중 어디에 들어가는지 표로 적습니다. 그 뒤 가상의 한국어 학습 항목 세 개로 같은 형태의 작은 파서를 설계합니다.

완료 기준: 파일 끝에서도 마지막 항목이 빠지지 않아야 합니다. 제목 두 개가 연속으로 나오는 경우, 설명이 다음 줄로 넘어간 경우에 현재 파서가 무엇을 하는지도 설명합니다. 특히 마지막 flush()가 필요한 이유를 말할 수 있으면 성공입니다.

실습 2 · 규칙의 경계값 다루기

가성비 분류표와 입력 검증 만들기 입문

읽을 곳: tools/lib/book.mjs의 COST_W, ratioOf().

할 일: 돈·시간·의지의 각 0~2점과 이득 세 등급을 조합합니다. 원시 조합은 3 × 3 × 3 × 3 = 81개입니다. 비용 합계 0·2·3에서 결과가 어떻게 달라지는지 찾고, 범위를 벗어난 값의 처리도 정합니다.

완료 기준: 정상 입력의 결과가 분류표와 같고, 누락 태그는 “무료”와 구별되어야 합니다. 증거 등급을 바꿔도 현재 함수의 결과가 바뀌지 않는 이유를 설명합니다. 함수의 정확성과 정책 자체의 타당성은 별개라는 점까지 적어 보세요.

실습 3 · 검색 화면의 상태 설계

공유 가능한 작은 필터 만들기 중급

읽을 곳: index.html의 state, readUrl(), writeUrl(), apply().

할 일: 가상 항목 열 개로 검색어·분류 두 축을 구현합니다. 같은 축의 여러 값은 OR, 다른 축끼리는 AND로 조합하고, 상태를 URL 쿼리로 저장합니다. 공백으로 나눈 검색어가 모두 포함된 항목만 남기도록 합니다.

완료 기준: 새로고침 후 같은 필터가 복원되어야 합니다. 결과 0개, 한글·공백이 든 검색어, 현재 필터에 가려진 항목으로의 직접 링크도 다룹니다. 소스의 gotoItem()이 필터를 풀고 이동하는 이유와 자신의 정책을 비교합니다.

실습 4 · 변경에 견디는 데이터

자기 점검 답안과 카드 ID 마이그레이션 설계 중급

읽을 곳: selfcheck.html의 qid(), profileDone(), tools/check-selfcheck.mjs, Anki 빌더의 GUID 생성 부분.

할 일: 질문의 뜻이 바뀌었을 때 기존 답을 재사용할지 결정합니다. 자기 점검은 q.rev를 ID에 붙일 수 있고, 나이 구간 대신 실제 나이를 받도록 ageYears라는 새 필드를 사용합니다. 이를 참고해 새 데이터와 옛 데이터를 구별합니다.

완료 기준: 오래된 나이 구간을 실제 나이로 오인하지 않고, 뜻이 바뀌지 않은 다른 답안은 보존해야 합니다. 별도로 항목 삽입·삭제 시 Anki 순번 ID가 어떻게 어긋나는지 표를 만든 뒤, 순번과 독립된 영구 ID를 제안합니다.

실습 5 · 여러 출력물의 계약

공통 스키마와 배포 게이트 설계 고급

읽을 곳: tools/lib/split-items.mjs, tools/check-split.mjs, .github/workflows/book.yml.

할 일: 웹·자기 점검·Anki의 파서 차이를 목록으로 정리하고, 공통 중간 JSON을 만들 경우의 장단점을 설계합니다. 원문 보존·필수 필드·안정적인 ID를 검사하는 단계와, 실패 시 배포를 막을 검사도 선정합니다.

완료 기준: 이득·비고는 분리 후 이어 붙이면 원문과 같아야 합니다. 출처는 분리용 구분자를 제외한 내용이 보존되어야 합니다. 쉼표·괄호 속 구분자, 굵은 글씨 경계, 마지막 항목을 담은 예제로 설계의 허점을 찾습니다.

좋은 실습은 기능 하나를 더하는 것에서 끝나지 않고, 잘못된 입력과 기존 사용자의 데이터까지 설명할 수 있어야 합니다. 원본 규칙을 그대로 복사하는 과제보다, 그 규칙이 어떤 상황에서 깨지는지 찾는 과제가 더 오래 남습니다.

9관련 기술 심화 학습 로드맵

주 3~5시간을 가정한 6주 학습 계획

다음 일정은 공식 학습 기간이 아닌 제안입니다. JavaScript의 변수·함수·배열을 이미 접했다면 1주차부터 시작하고, 아직 낯설다면 문자열·객체·반복문을 먼저 익힌 뒤 진행하세요. 매주 하나의 작은 결과물을 남기면 서로 다른 도구가 어떻게 연결되는지 확인할 수 있습니다.

주차학습 주제·읽을 소스남길 결과물
1주차문자열·정규식·상태 기계. parseReadme(), parseSection()항목 하나의 필드 표와 파서 입력 규칙
2주차순수 함수·Set·검색 조건. ratioOf(), fitsMe(), apply()분류 결정표, 검색·필터 조합 표
3주차Promise·fetch·복구·URL 상태. readText(), readUrl(), writeUrl()성공·404·429·시간 초과의 처리 흐름도
4주차DOM·이스케이프·지연 갱신. renderText(), CARD_IO텍스트·링크·검색 강조의 출력 경로 지도
5주차문서 변환·ZIP·SQLite. EPUB/PDF/Anki 빌더와 tools/lib/zip.mjs각 포맷의 내부 파일 목록과 ID 유지 정책
6주차CI·데이터 품질·변경 추적. .github/workflows/book.yml, tools/updates.mjs, 검사 도구필수 검사와 보고용 검사를 구분한 배포 설계

9-1. 그다음에는 무엇을 더 볼까

검색 쪽이 재미있다면 단순 문자열 포함 검사와 역색인을 비교해 보세요. 현재 구조는 항목 수를 N, 검색어 수를 T, 검색 문자열 길이를 L로 보면 대략 N × T × L에 비례하는 탐색을 수행합니다. 먼저 데이터 규모별 지연과 메모리를 측정하고, 필요할 때 인덱스나 Web Worker 도입을 판단합니다.

콘텐츠 도구 쪽이 재미있다면 Markdown AST와 스키마 검증으로 이어 가세요. 원고의 자유도를 늘릴수록 줄 단위 규칙은 복잡해집니다. 공통 AST나 중간 JSON을 두면 출력별 파서 중복을 줄일 수 있지만, 생성 단계와 데이터 파일을 관리하는 비용이 추가됩니다.

변경 추적 쪽에서는 tools/updates.mjs의 grams(), dice(), step()이 흥미롭습니다. 제목이 같으면 먼저 연결하고, 남은 항목은 인접 두 글자의 집합 유사도로 대응시킵니다. 후보 기준은 0.5 이상이며 본문 유사도에는 0.9 가중치를 줍니다. 영구 ID가 없는 텍스트에서 변경을 추정하는 휴리스틱의 장단점을 배울 수 있습니다.

프레임워크를 추가하기 전에 현재 데이터 흐름을 설명하고 측정할 수 있으면, 다음 기술을 고를 이유도 분명해집니다. 이 저장소는 브라우저 기본 API와 작은 제작 도구가 어디까지 해낼 수 있는지 확인하는 출발점입니다.

10핵심 키워드 사전

저장소의 파일과 연결해서 기억할 용어
용어쉬운 설명
Markdown제목·목록 등을 문자 기호로 적는 문서 형식. 여기서는 사람이 읽는 원고와 데이터 입력을 겸합니다.
스키마어떤 필드와 값이 있어야 하는지 정한 약속. 이 저장소에서는 제목 형식·필드 접두사·태그 규칙에 드러납니다.
파서문자열의 구조를 읽어 객체로 바꾸는 프로그램. parseReadme()가 항목 배열을 만듭니다.
상태 기계현재 단계에 따라 같은 입력도 다르게 처리하는 구조. 어느 장·항목 안인지 기억하며 원고를 읽습니다.
파생 데이터원본에서 계산할 수 있는 값. cs, ratio, hay가 해당합니다.
정적 호스팅미리 존재하는 파일을 전달하는 서비스. 별도 검색 API 없이도 브라우저에서 검색할 수 있습니다.
DOM브라우저가 문서를 요소와 텍스트 노드의 나무로 표현한 것. 카드 생성과 표시 변경의 대상입니다.
디바운스입력이 이어지는 동안 기다렸다가 잠시 멈추면 처리하는 방식. 검색 적용은 120ms 뒤로 미룹니다.
지연 갱신지금 보이지 않는 내용의 재작성을 나중으로 미루는 것. CARD_IO가 가까워진 카드부터 갱신합니다.
캐시한 번 얻은 결과를 다시 쓰려고 보관하는 곳. CORPUS_CACHE는 현재 페이지 메모리에 성공한 원고를 저장합니다.
이스케이프문법으로 오해할 문자를 안전한 표현으로 바꾸는 것. HTML에서 &lt;는 문자 <를 나타냅니다.
중간 표현 / AST입력을 바로 화면으로 만들지 않고 구조화해 둔 형태 / 문법 구조를 트리로 표현한 것. 여러 출력물의 공통 기반이 될 수 있습니다.
EPUB / XHTML전자책 패키지 형식 / XML 규칙을 지키는 HTML. 전자책 빌더가 XHTML 페이지와 목차 등을 묶습니다.
ZIP / CRC32여러 파일을 담는 컨테이너 / 데이터 손상을 검사하는 체크섬. CRC32는 보안용 해시가 아닙니다.
SQLite / .apkg파일 기반 관계형 데이터베이스 / Anki 카드 배포 패키지. 빌드에서 생성하므로 서비스 DB 운영은 필요하지 않습니다.
GUID대상을 계속 같은 것으로 알아보는 식별자. 이 저장소의 Anki GUID는 장·항목 순번에 의존합니다.
마이그레이션데이터 형식이나 의미가 바뀔 때 기존 데이터를 옮기는 절차. 자기 점검의 새 나이 필드가 사례입니다.
CI / job변경 때 자동 검사를 수행하는 흐름 / 그 안에서 실행되는 작업 단위. job 간 의존 관계가 배포 순서를 결정합니다.
artifact / release워크플로가 보관한 산출물 / 사용자에게 배포하는 릴리스. PR에서는 전자를 만들고 릴리스 게시 단계는 건너뜁니다.
shallow clone일부 최신 이력만 내려받은 저장소. 현재 코드는 읽을 수 있지만 과거 전체를 비교하는 도구에는 부족합니다.
휴리스틱정답을 보장하지는 않지만 실용적인 단서로 판단하는 방법. 제목·본문 유사도로 항목의 변경을 대응시킵니다.

11참고 링크

커밋에 고정된 소스와 공식 기술 문서를 나란히 읽기

11-1. 공식 저장소와 핵심 구현

11-2. 관련 기술 공식 문서

11-3. 현재 상태 (2026-10-23 확인)

항목확인 내용
선정TrendShift Monthly #8 · 분석일 2026-10-23
스타 / 포크68,151 / 5,899 · 2026-10-23 GitHub 기준
생성일2026-09-07
최근 push2026-10-21 · GitHub 기준
최근 커밋e2605d3 · 2026-10-21. PR #149 본문 교정: 표현을 쉽게 고치고 누락된 장소명과 연금 절세 수치를 보완
최신 릴리스epub-latest · GitHub 표시일 2026-09-18. 고정 태그와 파일을 반복 갱신하는 배포 방식
주 언어GitHub 기준 HTML. 실제 자산은 Markdown 원고와 JavaScript 제작 도구를 함께 포함
본문 규모34개 장 · 767개 항목 · 7,360줄 · 약 2.27MiB
근거·분류 집계A 495 / B 220 / C 52. 가성비 매우 높음 123 / 높음 344 / 보통 300
출처 URL본문의 출처·비고 필드에서 2,531회 출현. 중복을 제거한 문헌 수와 다름
전체 코드·문서추적 파일 331개. Markdown 25,443줄, HTML 2,310줄, 별도 JS 모듈 1,846줄
라이선스LICENSE: CC BY 4.0는 README의 설명상 본문·문서에 적용. LICENSE-CODE: MIT는 명시된 코드 범위에 적용
수치의 범위파일·설정·본문 수치는 커밋 e2605d3 기준. 실행·빌드 성공 여부나 성능 측정값을 나타내지는 않음
쓰기 전에 알아둘 한계
원고의 적용 범위, 배포 버전, 데이터 식별자를 확인하세요

생활 조언의 범위: 제도·법률·보험 내용은 README 기준 중국 본토를 대상으로 합니다. 한국에 그대로 적용할 수 없으며, A등급만으로 인과관계나 개인에게 맞는 결론이 보장되지 않습니다. 이 글은 그 조언을 새로 검증한 자료가 아닌 소프트웨어 구조 학습 자료입니다.

버전의 범위: 릴리스 표시일 2026-09-18이 파일 내용의 고정 날짜는 아닙니다. CI는 태그를 갱신하고 같은 이름의 파일을 덮어씁니다. 특정 결과를 재현하려면 산출물의 커밋·생성 시각을 확인해야 합니다.

운영과 데이터: 온라인 검색 화면에는 검색어를 GA 이벤트로 보내는 코드가 있습니다. 오프라인 빌더는 분석 구간을 제거하지만 외부 링크를 누르는 동작까지 없애지는 않습니다. 자기 점검 답안은 브라우저 저장소에 남으며, Anki의 순번 기반 ID는 항목 삭제·재번호 부여에 취약합니다.

검사와 권리: 독립 검사 실패가 배포를 모두 막지는 않으며, 문장 검사로 출처의 참됨을 증명할 수도 없습니다. shallow clone만으로 전체 변경 이력을 재구성할 수는 없습니다. 재사용 시에는 본문과 코드의 라이선스 범위가 다르다는 점도 함께 확인하세요.