VanillaFront는 왜 Shadow DOM을 선택했는가?
웹 컴포넌트 시대의 View 격리 — 그 결정 배경과 실전 효과
도입
브라우저 UI 프레임워크를 만들 때 가장 먼저 마주하는 문제는 화면과 화면 사이의 간섭입니다. 스타일이 뒤섞이고, id 값이 충돌하고, 이벤트가 엉키고, 다른 라이브러리와 부딪히죠. 이 문제는 웹이 30년 넘게 안고 온 근본 문제입니다.
VanillaFront는 이 문제를 Shadow DOM으로 정면 돌파한 프레임워크입니다. 지금은 이 결정이 자연스럽고 표준적으로 보이지만, 프레임워크 설계 관점에서는 신중한 트레이드오프 계산이 있었습니다. 이 글은 그 배경과 실전 효과를 정리한 글입니다.
Shadow DOM이 무엇인가
Shadow DOM은 W3C 웹 표준(Web Components 표준의 일부)으로, 특정 DOM 요소에 캡슐화된 별도의 DOM 트리를 붙일 수 있는 기술입니다.
[일반 DOM] [Shadow DOM]
<div> <div>
<span>안녕</span> #shadow-root ─┐
</div> <span>독립적</span> │ 격리됨
</div> ─┘
이 격리된 트리는:
- 외부 CSS의 영향을 받지 않음
- 내부 CSS가 밖으로 새지 않음
- 외부 JS가 접근하기 어려움 (특히 closed 모드)
- id/class 이름이 밖과 충돌하지 않음
즉, 한 페이지 안에 완전히 독립된 여러 개의 미니 문서를 담을 수 있는 것입니다.
왜 VanillaFront가 Shadow DOM을 선택했는가
1. B2B 화면의 근본적 요구 — 격리
VanillaFront는 B2B UI Framework입니다. 실무에서 B2B 애플리케이션은:
- 수백~수천 개의 화면이 한 프로젝트에 존재
- 다양한 개발자가 만들어서 class/id 이름 컨벤션이 제각각
- 써드파티 위젯(리포트 툴, 지도, 결제 등)을 함께 임베드
- 레거시 CSS와 공존해야 하는 경우 많음
- 여러 프로젝트의 화면을 한 페이지에 조합해야 하는 경우도 있음
기존 방식(BEM 명명 규칙, CSS Modules, CSS-in-JS 등)은 약속으로 격리를 흉내 냅니다. 하지만 개발자가 실수하거나 외부 라이브러리가 규칙을 안 지키면 즉시 깨집니다.
Shadow DOM은 브라우저 자체가 강제하는 격리입니다. 규칙이 아니라 기술적 boundary. 이 근본적 차이가 결정적이었습니다.
2. SPA의 View 단위 = 자연스러운 Shadow Root 단위
VanillaFront의 SPA 아키텍처에서 View는 화면의 기본 단위입니다. 하나의 View = 하나의 완결된 UI 조각. 이 개념이 Shadow Root의 개념과 정확히 맞아떨어집니다.
- View 하나 = Shadow Root 하나
- 각 View의 CSS는 그 안에서만 유효
- 각 View의 DOM 조작은 다른 View에 영향 없음
프레임워크 사용자는 View만 잘 만들면 격리는 자동. 별도 설정이나 규칙 학습이 불필요합니다.
3. 순수 표준 — 브라우저 native 기능
Shadow DOM은 웹 표준입니다. Vue의 scoped styles, React의 CSS Modules 같은 프레임워크 종속 해결책이 아닌 브라우저가 직접 처리하는 기능. 이 특성은 VanillaFront의 철학인 "Vanilla JS의 활용 + 표준 웹 유지"와 정확히 맞습니다.
- 프레임워크 없이도 동작하는 표준
- 브라우저가 사라지지 않는 한 사라지지 않을 기능
- 벤더 종속 없음
브라우저 지원 현황 — 실무에서 안심하고 써도 되는가
Shadow DOM의 실무 도입을 망설이게 하는 가장 큰 걱정은 "호환성"입니다. 결론부터 말하면:
실무 브라우저는 사실상 100% 지원
브라우저Shadow DOM v1 지원 시작현재 사용률
| Chrome / Edge | 53 (2016) | 대다수 |
| Firefox | 63 (2018) | 지원 |
| Safari | 10 (2016) | 지원 |
| Samsung Internet | 5 (2017) | 지원 |
| Opera | 40 (2016) | 지원 |
| Internet Explorer | ❌ 미지원 | 2022년 6월 15일 EOL(수명 종료) |
주목할 점:
- 모든 주요 브라우저에서 최소 7~8년 이상 지원
- IE는 마이크로소프트가 공식 종료 — 2022년 이후 보안 패치도 없음
- 국내 공공/금융권 프로젝트도 IE 지원 요구 사실상 사라짐 (Edge로 통합)
- 모바일 브라우저(iOS Safari, Chrome Mobile, Samsung Internet) 모두 지원
실무 관점의 현실
과거엔 "IE를 지원해야 하니 최신 기술 못 쓴다"가 발목을 잡는 대표적 이유였지만, 지금은:
- 공공기관도 Edge 표준 사용 확대 (2022~)
- 금융권도 IE 종료 이후 전환 완료
- 기업 내부 시스템도 크롬/엣지 표준 전환
- 모바일은 애초에 IE와 무관
즉, 오늘 새로 시작하는 프로젝트에서 Shadow DOM을 못 쓸 이유는 사실상 없습니다. VanillaFront가 이 결정을 자신 있게 할 수 있었던 배경입니다.
DOM 처리 관점의 장점
1. id 충돌이 없다
전통적으로 웹에서 가장 흔한 실수 중 하나:
<!-- 개발자 A가 만든 화면 -->
<div id="header">...</div>
<!-- 개발자 B가 만든 화면 -->
<div id="header">...</div>
같은 페이지에 있으면 document.getElementById('header')는 첫 번째만 찾음. 두 번째는 무시. B의 코드가 조용히 깨집니다.
Shadow DOM 안에서는 id가 그 shadow root 안에서만 유효. 여러 View가 같은 id를 써도 서로 영향 없음.
2. getElementById querySelector의 범위가 명확
각 View는 자기 Shadow Root 안의 DOM만 조회합니다. 실수로 다른 View의 요소를 건드릴 수가 없습니다.
// View A 안에서
this.shadowRoot.querySelector('.button') // View A 안의 .button만
// View B의 .button은 절대 못 찾음
이는 디버깅 지옥을 근본적으로 방지합니다. "왜 저 화면의 스타일이 여기서 바뀌지?" 같은 문제가 발생 불가.
3. DOM 트리가 작다
가상 스크롤/대량 데이터 표시 시 성능이 문제되는데, Shadow DOM은 각 View의 DOM을 격리해서 관리합니다. 브라우저 입장에서 각 shadow root는 별개의 계산 단위. querySelectorAll이나 mutation observer 등이 전체 페이지가 아니라 shadow root 범위에서 동작.
4. 이벤트 재타깃(Event Retargeting)
Shadow DOM 안에서 발생한 이벤트는 밖으로 나갈 때 shadow root 호스트 요소로 재타깃됩니다. 즉:
- View 안의 버튼 클릭 → 이벤트가 밖으로 나갈 땐 View 컨테이너 클릭으로 보임
- 외부 이벤트 핸들러는 view 내부 세부사항을 알 수 없음
- 이벤트 캡슐화 자동
이는 View를 블랙박스로 다룰 수 있게 해줍니다. 외부는 View의 인터페이스만 알면 되지 내부 DOM 구조를 알 필요 없음.
스타일 적용 관점의 장점
1. CSS 격리 — 유일하게 완벽한 방법
CSS 격리는 프론트엔드에서 30년 넘게 시도된 미해결 문제입니다:
방법격리 방식완벽한가
| BEM | 명명 규칙 (block__element--modifier) | ❌ 개발자 실수 시 깨짐 |
| CSS Modules | 빌드 타임 해싱 | ⚠️ 빌드 도구 필요, 런타임 격리 X |
| CSS-in-JS | JS로 CSS 생성 | ⚠️ 런타임 오버헤드, SSR 복잡 |
| Scoped styles (Vue) | 컴파일러가 속성 셀렉터로 변환 | ⚠️ 프레임워크 종속, 완전 격리 X |
| Shadow DOM | 브라우저 native boundary | ✅ 완벽 |
Shadow DOM 안의 CSS는 밖으로 나갈 수 없고, 밖의 CSS는 안으로 들어갈 수 없습니다. 약속이 아니라 물리적 boundary.
2. 클래스명 자유도
일반 CSS에서는 .button, .title, .item 같은 짧고 자연스러운 이름을 쓰기 어렵습니다. 다른 곳과 충돌할 수 있으니 .my-app__button--primary 같이 길게 씁니다.
Shadow DOM 안에서는 가장 자연스러운 이름을 자유롭게 사용할 수 있습니다.
/* Shadow DOM 안 — 다른 View에 영향 없음 */
.button { ... }
.title { ... }
.header { ... }
3. Adopted Stylesheets — 공유와 격리의 조화
Shadow DOM의 유일한 실전 이슈였던 "중복 CSS 로딩" 문제도 이미 해결됐습니다. Constructable Stylesheets / Adopted Stylesheets 표준으로:
const themeSheet = new CSSStyleSheet();
themeSheet.replaceSync(themeCss);
// 여러 shadow root가 같은 sheet를 참조
shadowRoot1.adoptedStyleSheets = [themeSheet];
shadowRoot2.adoptedStyleSheets = [themeSheet];
- 하나의 CSS를 여러 shadow root가 공유
- 메모리 효율적 (실제로 CSS는 한 번만 파싱됨)
- 테마 전환 시 모든 View에 즉시 반영
VanillaFront는 이 방식으로 테마 CSS를 여러 View에 공유하면서도 격리 유지합니다.
4. 테마 전환의 실시간성
VanillaFront는 11종의 테마 × 각 tight 변형(총 22개 변형)을 지원합니다. 사용자가 테마를 바꾸면:
- 모든 View가 동시에 새 테마로 전환
- 각 View 내부의 컴포넌트 스타일이 CSS 변수 재계산으로 즉시 반영
- 새로고침 없이 완벽 전환
이건 Adopted Stylesheets + CSS Custom Properties + Shadow DOM 조합의 결과입니다.
속도(성능) 관점의 장점
1. 스타일 계산 범위 축소
브라우저가 CSS 셀렉터를 매칭할 때 성능 병목이 가장 큰 부분입니다. 페이지에 CSS 규칙이 5,000개 있으면, 모든 요소마다 5,000개를 검사합니다.
Shadow DOM 안의 요소는 그 shadow root의 CSS만 매칭합니다:
- View A: 200개 CSS 규칙
- View B: 150개 CSS 규칙
- 각 View의 요소는 자기 View의 규칙만 검사 → CPU 시간 대폭 감소
특히 대량 컴포넌트가 있는 그리드/차트 화면에서 이 효과가 큽니다.
2. Layout/Paint 격리
브라우저 렌더링 파이프라인에서 Layout → Paint → Composite 단계에서 Shadow DOM은 격리된 서브트리로 처리됩니다:
- View A의 크기 변화가 View B의 layout 계산을 트리거하지 않음
- 각 View는 자기 영역에서만 reflow
- 대규모 페이지에서 특히 유리
3. Selector Matching 최적화
document.querySelectorAll('.item')은 전체 페이지를 스캔하지만, shadowRoot.querySelectorAll('.item')은 해당 shadow root만 스캔합니다. 대량 DOM 조작 시 매우 큰 차이.
4. 초기 로딩
각 View는 마운트될 때만 shadow root를 생성합니다. 사용자가 접근 안 한 View는 아예 존재하지 않으므로:
- 초기 렌더 부담 최소
- 필요할 때 lazy 하게 확장
- 라우터 이동 시 이전 View 자원 해제 용이
독립성 관점의 장점
1. 여러 프로젝트/버전 공존 가능
한 페이지 안에 서로 다른 프레임워크 버전으로 만든 View를 함께 놓을 수 있습니다. 각 View의 CSS/JS가 격리되어 있으니:
- 레거시 View (v1) + 신규 View (v2) 공존
- A 팀 화면 + B 팀 화면 조합
- 마이그레이션 시 점진적 전환 가능
2. 써드파티 임베드
지도, 차트, 리포트 등 외부 라이브러리를 View에 임베드할 때:
- 그 라이브러리의 CSS가 다른 View에 새지 않음
- View 안에서만 영향
- 여러 다른 써드파티 조합 시 CSS 충돌 없음
3. 위젯 형태 배포
VanillaFront View를 다른 사이트에 위젯으로 삽입 가능합니다. 호스트 사이트의 CSS와 완전히 격리되어 시각 스타일이 보존됩니다.
<!-- 다른 사이트 안에 VanillaFront View 삽입 -->
<my-vanilla-view>
<!-- 내부는 Shadow DOM, 호스트 사이트 CSS 무관 -->
</my-vanilla-view>
4. VSCode Extension 통합
VanillaFront의 WYSIWYG 에디터는 VSCode 확장으로 실행되는데, 이때 에디터 UI가 VSCode의 webview iframe 안에서 동작합니다. Shadow DOM 격리 덕분에 VSCode의 다양한 테마 CSS와 충돌 없이 자체 UI를 유지합니다.
보안 관점의 장점
1. Closed 모드 — DOM 접근 차단
Shadow DOM은 open과 closed 두 모드가 있습니다:
- open: element.shadowRoot로 외부 JS가 접근 가능
- closed: 외부 JS가 shadow root에 접근 불가
민감한 정보나 폼(예: 결제, 인증)을 담은 View를 closed 모드로 만들면:
- 외부 스크립트가 DOM 트리를 읽지 못함
- 폼 자동 조작 공격 방어
- 브라우저 확장 프로그램의 무단 접근 차단
2. XSS 완화 (완벽하지 않지만 도움됨)
XSS 자체를 100% 막지는 못하지만:
- 침투된 스크립트가 다른 shadow root 안 데이터에 접근 어려움
- View 간 데이터 유출 저항
- 방어 심층화(defense in depth)에 기여
3. 스타일 인젝션 공격 방어
외부에서 CSS 규칙을 주입해 UI를 조작하는 공격(예: 로그인 버튼 위치 이동으로 클릭 하이재킹) 방어:
- 외부 CSS가 shadow root 안으로 들어갈 수 없음
- 각 View의 시각적 무결성 보장
4. 브라우저 확장 프로그램 간섭 방지
일부 브라우저 확장은 페이지의 DOM/CSS를 임의로 조작합니다. Shadow DOM 안의 View는:
- 확장이 스타일을 덮어쓰기 어려움
- View의 원래 UX가 보존됨
- 특히 폼/입력 필드 관련 확장의 간섭 최소
주의: Shadow DOM은 완전한 보안 boundary는 아닙니다. closed라도 결정된 공격자는 우회 가능. 하지만 일상적 간섭·오작동·실수는 대부분 방어됩니다. 방어 심층 층으로서 큰 가치.
단점 / 트레이드오프
정직하게 짚어야 할 부분들:
1. 전역 CSS가 안 들어감
<head>에 넣은 CSS가 Shadow DOM 안에는 적용되지 않습니다. 각 View가 자기 CSS를 명시적으로 로드해야 함. → 해결: Adopted Stylesheets로 공유 시트 관리 (VanillaFront가 자동 처리)
2. 폰트 로딩
@font-face는 document 레벨에서만 등록됩니다. Shadow DOM 안에서 새로 등록해도 무효. → 해결: 폰트는 document <head>에 등록. Shadow DOM은 상속받아 사용
3. 일부 CSS 셀렉터 제약
- :hover, :focus는 정상 동작
- :target (URL 해시 기반)는 shadow boundary 넘어가지 않음
- ::selection (텍스트 선택 스타일)는 개별 지정 필요
4. 접근성(A11y) — 처음엔 이슈
- aria-labelledby 같은 속성이 shadow boundary를 넘지 못함
- 스크린 리더의 초기 지원이 약했음 → 현재 상태: 주요 브라우저 스크린 리더 모두 지원. aria-* 처리도 대부분 해결됨. 다만 일부 오래된 스크린 리더 사용자엔 여전히 제약 가능
5. 개발자 도구 학습 곡선
Chrome DevTools의 Elements 탭에서 shadow root를 열어보는 익숙함이 필요. 초보 개발자에겐 처음엔 어색. → 현재: 도구가 성숙해서 shadow root 트리 탐색이 매우 자연스러움
6. 일부 레거시 라이브러리 미지원
jQuery 같이 document 전체를 가정한 라이브러리는 Shadow DOM 안에서 안 동작할 수 있음. → VanillaFront 대응: 애초에 표준 JS 기반이라 이런 라이브러리 의존 없음
7. 폼 자동완성
브라우저의 폼 자동완성(로그인 정보 등)이 shadow root 안 input에서 안 될 수 있음. → 해결: <form>을 shadow root 밖에 두거나, 표준화된 자동완성 지원 진행 중
VanillaFront가 이 트레이드오프를 어떻게 다루는가
VanillaFront는 Shadow DOM의 단점을 프레임워크 차원에서 대부분 해결합니다:
1) Adopted Stylesheets 자동 관리
- 테마 CSS는 하나만 로드되고 모든 View가 공유
- 개발자는 별도 CSS 로딩 신경 안 씀
2) CSS 변수(Custom Properties) 활용
- --colorPrimary 같은 CSS 변수는 shadow boundary를 통과
- 모든 View에 일관된 테마 자동 적용
3) 표준 Web API 기반
- 레거시 라이브러리 의존 없음
- ES6 모듈만으로 동작
4) 폼/자동완성 고려된 컴포넌트 설계
- 필드 컴포넌트가 브라우저 자동완성과 호환되게 구현
- autocomplete 속성 지원
5) 개발자 경험 개선
- 각 View 파일에서 자연스러운 이름 사용 가능
- WYSIWYG 에디터가 shadow DOM 트리 시각화
결론 — 왜 지금이 Shadow DOM의 시대인가
"완벽한 격리" 를 원한다면 Shadow DOM 외에 다른 답이 없습니다. BEM은 규칙이고, CSS Modules는 빌드 도구고, CSS-in-JS는 런타임 트릭입니다. Shadow DOM만이 브라우저가 직접 강제하는 boundary입니다.
그리고 2025년 현재:
- ✅ 브라우저 지원 완료 (IE 종료로 걱정 사라짐)
- ✅ 초기 이슈들 해결 (Adopted Stylesheets, 개발자 도구 성숙)
- ✅ 성능 이점 검증 (스타일 계산·layout 격리 실효)
- ✅ 웹 표준 확정 (사라질 걱정 없음)
VanillaFront는 이 검증된 표준 위에 SPA 아키텍처를 구축했습니다. 결과는 실무에서 발생하는 CSS 충돌, id 중복, 이벤트 간섭 같은 문제들이 애초에 발생 불가능한 프레임워크입니다.
Vue의 scoped 방식이나 React의 CSS-in-JS 같은 프레임워크 종속 해결책이 아닌, 브라우저가 영구히 지원할 웹 표준을 활용한 격리 — 이것이 VanillaFront가 Shadow DOM을 선택한 이유이자, 이 선택이 만들어낸 실전 강점입니다.