Files
shkim4 89011a2e42
Deploy Storybook / deploy (push) Successful in 1m12s
docs: standardize Git submodule and workspace installation
2026-10-23 11:06:19 +09:00

10 KiB

Theme JSON 운영과 독립 배포

Repository와 배포 단위

하나의 Git Repository에서 소스·Theme·Storybook·문서를 관리한다. UI, CENTER Graphic, Theme JSON은 각각 별도 배포 단위다. 현재 두 패키지는 private: false이며 compiled dist(JS·타입·CSS)를 export한다. Library build는 npm run build:packages로 구현되어 있다. HM 패키지는 npm Registry로 배포하지 않으며 소비 프로젝트는 Git Submodule + npm Workspace로 연결한다. Theme JSON의 독립 산출 경로는 구현했다. 실제 CDN URL·인프라·인증·CI publish는 미정이다.

운영 배포 경계는 packages/ui → UI package, packages/center-graphic → Graphic package, themes/*.json → Theme CDN이다. Git push ≠ Theme CDN publish다. 운영 CI/CD는 JSON 검증·독립 artifact 산출 후 CDN에 배포하는 별도 단계로 구성하며, 현재 저장소에는 실제 CDN publish pipeline이 구현되어 있지 않다.

HMDesignSystem/
├─ packages/
│  ├─ ui/                     # @hm-design/ui: logic, contract, mapping, runtime
│  └─ center-graphic/         # 독립 Graphic package
├─ themes/
│  ├─ default.json            # 92개 public color 값의 공식 Default 원본
│  ├─ service-example.json    # 5개 역할만 바꾸는 명시적 예제
│  └─ drafts/                 # 과거 초안; Theme 산출 대상 아님
├─ scripts/
│  ├─ theme-templates/        # 컬러 literal 없이 alias/geometry만 소유
│  ├─ theme-defaults.mjs      # JSON -> package CSS/default 데이터 생성
│  └─ theme-artifacts.mjs     # JSON validation -> 독립 CDN artifact
├─ src/theme/                 # Storybook Preview/Editor/verification 소비자
└─ docs/guidelines/

themes/default.json은 기존 v1 {schemaVersion:1,tokens:{...}} 계약을 사용한다. Component Token은 없다. 94개 Runtime 입력 중 92개 컬러를 포함한다. 나머지 두 radius와 layout은 패키지 template에서 관리한다. 서비스 Theme는 같은 계약의 부분 컬러 override다.

단일 Default 원본과 package fallback

사람이 관리하는 컬러 값은 themes/*.json이다. Default JSON과 scripts/theme-templates에서 packages/ui/src/theme/primitives.css, default.css, default-values.generated.ts를 생성한다. Template는 기존 alias/geometry 정책을 유지하고 HEX literal을 따로 관리하지 않는다. JSON의 역할값이 팔레트 alias의 값과 같으면 기존 alias를 유지하고, 달라지면 JSON의 역할값을 우선한다.

패키지는 생성된 CSS fallback snapshot을 포함하므로 네트워크 Theme가 없거나 실패해도 기본 화면을 렌더링한다. 이미 배포한 UI package의 fallback은 그 패키지 버전에서 생성한 HM Default snapshot이다. 최신 운영 Default JSON을 CDN에서 로드하면 UI 재배포 없이 새 값이 적용된다. 네트워크 장애에서 최신 CDN 값까지 보장하는 시스템은 아니며, 그 경우 패키지 snapshot으로 복원한다.

Theme-only CDN 배포는 npm run build:themes만 실행한다. UI 재빌드·version up·registry publish가 필요 없다. 이후 UI 패키지를 새로 유지보수/검증할 때는 npm run generate:theme-defaults로 repo의 생성 snapshot을 갱신한다. typecheck/build/build-storybook의 pre hook은 JSON/template/생성 snapshot의 불일치를 검사한다. 이 검사는 Theme artifact 산출의 선행 조건이 아니다.

개발팀 Runtime 적용

설치/초기 서비스 연결 시 UI Component와 CSS를 가져온다. 실제 themeUrl은 서비스 설정에서 결정하며 package 내부에는 회사 CDN 주소가 없다.

import "@hm-design/ui/theme.css"
import {
  fetchRuntimeTheme, resolveRuntimeTheme, loadRuntimeTheme,
  applyRuntimeTheme, resetRuntimeTheme,
} from "@hm-design/ui/theme"

// serviceConfig.themeUrl은 CDN/API 설정값이다.
const root = document.documentElement
const success = await loadRuntimeTheme(root, serviceConfig.themeUrl)
// success=false: 이 root의 override를 해제하고 host/package HM fallback 유지.

// 별도의 Default JSON + 서비스 partial을 사용할 때:
try {
  const defaults = await fetchRuntimeTheme(serviceConfig.defaultThemeUrl)
  const serviceTheme = await fetchRuntimeTheme(serviceConfig.themeUrl)
  applyRuntimeTheme(root, resolveRuntimeTheme(serviceTheme, defaults))
} catch {
  resetRuntimeTheme(root)
}

// 이미 받은 설정/API/로컬 JSON에도 같은 Apply 계약을 사용한다.
// Apply에는 unknown 전체 validation이 있다.
applyRuntimeTheme(root, configurationTheme)

// 설정 URL의 버전/hash가 바뀌거나 운영 refresh 이벤트가 오면 다시 호출한다.
// 라이브러리/Component 재빌드와 브라우저 reload는 필요 없다.
await loadRuntimeTheme(root, serviceConfig.updatedThemeUrl)
  • fetchRuntimeTheme(url, RequestInit?): HTTP status/JSON/기존 allowlist 검증 후 partial 문서 반환. DOM을 건드리지 않고 실패 시 throw한다. URL/캐시/credentials/signal은 소비 서비스에서 지정한다.
  • resolveRuntimeTheme(theme, defaults?): 패키지 Default + 선택적 supplied Default + partial override를 검증 후 합친다. Component Token을 만들지 않는다. 그룹의 일부 stop만 제공하는 입력은 거부한다.
  • loadRuntimeTheme(root,url,{request?,defaults?}?): fetch/검증/선택적 merge/Apply의 편의 함수. 오류/invalid/HTTP 실패 시 false와 package fallback. 더 늦게 도착한 이전 요청은 새 Theme를 덮거나 reset하지 않는다. 직접 Apply/Reset도 pending load를 무효화한다.
  • isRuntimeTheme: DOM 변경 없는 사전 검증. applyRuntimeTheme/resetRuntimeTheme: 기존 public Apply/Reset 경로와 host inline 값/priority 복원 정책 유지.

앱 시작 전/후 어디서든 호출할 수 있다. CSS는 최초 paint 전에 로드한다. 네트워크 응답 전에는 package fallback, 응답 후에는 External Theme다. 로딩 timeout이 필요하면 서비스에서 AbortSignal.timeout(...) 또는 AbortController를 RequestInit에 제공한다. 로더는 polling/push subscription을 자동 시작하지 않는다. 이미 열린 서비스가 최신 JSON을 쓰려면 소비 서비스의 refresh 이벤트/설정 갱신/호출 정책이 필요하다.

디자인팀 workflow

  1. themes/service-example.json을 서비스 파일로 복사하거나 themes/default.json의 상위 컬러 역할을 수정한다. 공통 팔레트/gradient보다 Primary/Active/Focus/Accent 같은 역할을 우선 편집한다.
  2. Storybook의 Theme/Designer Playground/Editor에서 URL/파일 Import로 JSON을 불러온다. 기본값은 themes/default.json을 Runtime merge하여 표시한다. 개발 서버에서는 Default JSON import의 HMR Preview도 가능하다. 다른 서비스 파일을 변경한 뒤 같은 URL을 Load URL로 다시 불러오면 CSS/Component 수정 없이 즉시 반영한다.
  3. 실제 Input/Control/Calendar/Date Picker/메뉴/표/내비게이션과 1280/768/390 레이아웃을 확인한다. Editor의 값 변경도 같은 공식 Contract를 사용한다.
  4. Editor를 이용했다면 Export한 JSON을 해당 themes/*.json에 저장해 Git에 포함한다. Editor는 repository 파일을 자동으로 덮어쓰지 않는다.
  5. Git diff/review 후 디자인팀의 승인된 운영 절차로 commit/push한다.
  6. 아래 Theme-only 산출물을 CDN에 배포한다. Git은 이력/검토, CDN은 Runtime delivery를 담당한다. 서비스가 Git 파일을 직접 읽지 않는다.

Theme-only 배포

npm run build:themes
# 결과: theme-dist/themes/default.json, service-example.json 및 추가한 top-level JSON

전체 문서 validation을 통과한 뒤에만 산출한다. themes/drafts는 제외한다. 삭제된 source의 옛 top-level JSON artifact도 출력 디렉터리에서 제거한다. 산출물은 theme-dist/themes/이며 UI/Graphic bundle과 별개다. 이 디렉터리의 파일만 선택한 CDN origin에 업로드하고 서비스 설정의 Theme URL을 갱신한다. CDN infrastructure/publish credentials/자동 pipeline은 구현하지 않았다. CDN은 cross-origin 사용을 위한 CORS 응답과 JSON Content-Type을 설정해야 한다.

Cache/버전/롤백 권장안

인프라 미정 상태의 추천안은 /themes/service-a/<content-hash>.json 또는 /themes/service-a/v12.json처럼 변경마다 별도 파일을 발행하고 서비스 설정을 새 URL로 바꾸는 방식이다. 이전 파일을 유지하므로 rollback은 이전 URL로 돌린 후 Loader를 다시 호출한다. 버전 파일에는 장기 Cache-Control: public, max-age=31536000, immutable을 사용할 수 있다. AWS도 versioned 파일/디렉터리 이름으로 갱신하는 방식을 안내한다. CloudFront 파일 versioning, MDN Cache-Control.

대안은 /themes/service-a.json?v=<content-hash>다. CDN cache key가 query를 포함하도록 설정해야 효과가 있다. 안정 URL을 덮어쓰려면 짧은 TTL/no-cache와 ETag 재검증 또는 CDN purge 정책을 함께 정한다. 브라우저의 fetch(...,{cache:"no-store"})만으로 CDN edge 캐시를 항상 갱신한다고 보장하지 않는다. Preview는 no-store로 요청하지만 Production 캐시 정책은 소비 서비스/CDN 설정의 책임이다. MDN HTTP caching, MDN Request.cache.

실제 운영 URL/versioning 방식은 서비스 설정에서 확정한다. 자동 버전 관리/manifest/publish/rollback 서비스는 미구현이다.

보안/제한

공개 allowlist의 HEX6/기존 radius 값만 Production Runtime이 허용한다. Theme artifact와 Designer UI는 컬러 전용이며 Component Token/임의 CSS/HTML/JS/URL 값은 거부한다. invalid Import는 마지막 유효 Preview를 유지하고 URL Loader 실패는 package HM Default로 돌아간다. Partial은 교체이며 이전 Theme key가 남지 않는다.

Global root Theme는 body Portal에도 적용된다. wrapper 밖 Portal은 Global을 사용하며 Scoped 전달은 기존 FOLLOW_UP이다. 동일 root는 한 owner가 관리한다. 새 상위 역할/Mapping/behavior가 필요한 변경은 UI package 작업이며 단순 Theme 컬러 변경과 구분한다.