[JavaScript 시리즈 20편] 캡스톤: 접근성·패턴·성능을 아우르는 미니 웹앱

English version

시리즈의 마지막 편입니다. 02~19편에서 다룬 개념을 모두 묶어 태스크 허브(Task Hub)라는 미니 앱을 완성합니다. 여기서는 “기능·기술 요구사항 목록 + 해시 라우터 + 테스트”가 핵심, 성능·배포 파이프라인은 선택 확장으로 정리했습니다.

이번 글에서 새로 나오는 용어

  1. 기능 요구사항: 사용자가 실제로 하길 원하는 기능 목록으로, 작업 추가·필터·통계 같은 행동을 정의합니다.
  2. 기술 요구사항: 어떤 도구와 구조로 구현해야 하는지 정한 조건(상태 관리, 테스트, 배포 등)입니다.
  3. 워크플로: 설계 → 구현 → 테스트 → 배포 순서를 정해 팀이 같은 리듬으로 움직이게 하는 작업 단계입니다.
  4. Task Hub: 이번 캡스톤에서 만드는 태스크 관리 미니 앱의 이름으로, 모든 예제가 이 앱을 기준으로 합니다.

핵심 개념

  • 기능 요구사항(Feature Requirements, 사용자가 기대하는 동작): 작업 추가·수정·삭제, 필터, 통계, 활동 로그, 라우팅(Routing, 경로 제어) 흐름, 접근성 보장을 포함합니다.
  • 기술 요구사항(Technical Requirements, 구현 조건): 상태 관리, 패턴 렌더러, 접근성 속성, 지연 로딩, 테스트, Vite 빌드, 자동 배포 파이프라인을 다룹니다.
  • 워크플로(Workflow, 작업 순서): 설계 → 기능 구현 → 테스트 → 성능 점검 → 배포 → 확인 순서를 지킵니다.

D2로 보는 캡스톤 워크플로

요구사항 정리폴더/라우터 설계기능 구현테스트 + BrowserMock성능·접근성 점검배포검증/회고 다음 반복

도식의 뜻은 “계획을 정리 → 폴더 구조·라우터 설계 → 기능 구현 → 테스트 → 성능 검사 → 배포 → 회고”입니다. 다시 말해, 기능을 만들면서도 다음 단계를 미리 예약해 두는 흐름입니다. 20편에서는 이 순서를 처음부터 끝까지 따라가며 12편 중간 합본에서 만든 블록을 확장합니다.

코드로 확인하기

먼저 폴더 구조를 정리해 컴포넌트·라우터·유틸 위치를 고정합니다. 들어가기 전, "파일을 한 곳에 몰아두면 헷갈린다"는 경험을 떠올려 보세요. 이번 구조는 그 혼란을 막는 안전띠입니다.

src/
  components/
    pattern.js
    task-form.js
    task-list.js
    stats-panel.js
    activity-log.js
  router/
    hash-router.js
  lib/
    storage.js
    bus.js
    performance.js
  tests/
    task-form.test.js
    router.test.js
  index.js
  styles.css

이제 해시 라우터를 만들어 화면 전환을 담당하게 합니다.

// router/hash-router.js
export function createHashRouter(routes, outlet) {
  function renderRoute() {
    const hash = location.hash.replace("#", "") || "home";
    const view = routes[hash] ?? routes["404"];
    outlet.innerHTML = view();
  }
  window.addEventListener("hashchange", renderRoute);
  renderRoute();
}

요약하면 “현재 hash를 읽고, 없으면 home으로, 있으면 해당 view 함수를 실행해 outlet에 채운다”입니다.

폼 컴포넌트는 접근성 속성과 초안 저장을 관리합니다.

// components/task-form.js

export function initTaskForm(root) {
  const form = root.querySelector("form");
  const feedback = root.querySelector(".form-feedback");
  const persistDraft = debounce(() => {
    const data = Object.fromEntries(new FormData(form));
    sessionStorage.setItem("task-draft", JSON.stringify(data));
  }, 300);

  form?.addEventListener("input", (event) => {
    const target = event.target;
    if (!(target instanceof HTMLInputElement)) return;
    target.setAttribute("aria-invalid", String(!target.value.trim()));
    persistDraft();
  });

  form?.addEventListener("submit", (event) => {
    event.preventDefault();
    const data = Object.fromEntries(new FormData(form));
    if (!data.title?.trim()) {
      if (feedback) feedback.textContent = "제목을 입력하세요.";
      return;
    }
    emit("task:add", { task: { ...data, id: crypto.randomUUID(), done: false, createdAt: Date.now() } });
    form.reset();
    sessionStorage.removeItem("task-draft");
    if (feedback) feedback.textContent = "작업이 추가되었습니다.";
  });
}

이 컴포넌트의 역할을 한 줄로 정리하면 “폼 입력을 감시하다가 조건이 맞으면 task:add 이벤트를 보낸다”입니다. 캡스톤에서도 이 규칙만 지키면 됩니다.

라우터의 핵심 기능은 테스트로 확인합니다.

// tests/router.test.js

describe("createHashRouter", () => {
  it("해시 변경 시 올바른 화면을 렌더링한다", () => {
    const dom = new JSDOM(`<!doctype html><div id="app"></div>`);
    const outlet = dom.window.document.getElementById("app");
    createHashRouter({ home: () => "<p>홈</p>", stats: () => "<p>통계</p>" }, outlet);
    dom.window.location.hash = "#stats";
    dom.window.dispatchEvent(new dom.window.HashChangeEvent("hashchange"));
    expect(outlet.innerHTML).toContain("통계");
  });
});

테스트는 “hash를 바꾸면 outlet이 바뀌는가?” 하나만 묻지만, 캡스톤 전체의 안전망 역할을 합니다.

CI에서 테스트와 빌드를 묶어 안정성을 확인합니다.

npm run test
npm run build

CI(Continuous Integration, 지속적 통합)에서 테스트 후 빌드하도록 흐름을 묶습니다.

왜 중요한가

  • 기능·상태·라우팅을 한 화면에서 연결하면 병목을 빠르게 찾을 수 있습니다.
  • 접근성, 성능, 배포를 모두 챙겨야 실제 서비스 품질을 재현할 수 있습니다.
  • 캡스톤을 마치면 포트폴리오에 넣을 만한 근거 자료가 생깁니다.

실습

  • 따라 하기: 폴더 구조를 복제하고 Task Hub 앱을 동일 기능으로 구현합니다.
  • 확장하기: 라우터, 패턴 렌더러, 저장소 모듈을 요구사항에 맞게 재구성합니다.
  • 디버깅: Lighthouse 진단 도구로 성능과 접근성 점수를 측정해 90점 이상을 만듭니다.
  • 완료 기준: 테스트 통과, 빌드 성공, 배포 링크 확인, 접근성·성능 90점 이상이면 완료입니다.

마무리

축하합니다! 20편을 모두 따라 했다면 순수 JavaScript로 UI를 설계하고 배포할 수 있는 실력을 갖춘 것입니다. 이제는 원하는 프레임워크나 백엔드와 연결해 더 큰 프로젝트로 확장해 보세요.

💬 댓글

이 글에 대한 의견을 남겨주세요