Skip to content

Web Components를 이용한 클라이언트 통합

Web Components는 Custom Elements, Shadow DOM, HTML Templates 세 가지 웹 표준 기술을 조합하여 프레임워크에 독립적인 캡슐화된 UI 컴포넌트를 만들고, 이를 마이크로프론트엔드의 클라이언트 통합 수단으로 활용하는 방법이다.

학습 목표

  • Web Components를 구성하는 세 가지 핵심 기술(Custom Elements, Shadow DOM, HTML Templates)을 이해한다
  • Shadow DOM의 스타일 격리 원리와 한계를 설명할 수 있다
  • 마이크로프론트엔드에서 각 마이크로앱을 커스텀 엘리먼트로 제공하는 구조를 설계할 수 있다
  • Web Components 기반 통합의 장점과 단점을 비교하여 적절한 상황에 적용할 수 있다
  • 프레임워크(React, Vue 등)와 Web Components의 연동 방식을 파악한다

1. Web Components 표준 개요

Web Components는 단일 기술이 아니라 세 가지 브라우저 네이티브 기술의 조합이다. 이 세 기술이 함께 동작하면서 캡슐화된 재사용 가능한 UI 요소를 만들 수 있다.

1.1 Custom Elements (사용자 정의 요소)

Custom Elements는 개발자가 직접 새로운 HTML 태그를 정의할 수 있게 해주는 API이다. <div>, <p> 같은 기본 HTML 요소와 마찬가지로, <my-component>처럼 자신만의 태그를 브라우저에 등록할 수 있다.

Custom Elements 정의 규칙:

규칙설명
이름에 하이픈 포함 필수<my-app> (O), <myapp> (X). 기존 HTML 태그와의 충돌 방지
HTMLElement 상속class MyApp extends HTMLElement 형태로 정의
customElements.define()이름과 클래스를 브라우저에 등록하는 API
생명주기 콜백connectedCallback, disconnectedCallback, attributeChangedCallback
javascript
// Custom Element 정의 기본 구조
class MyMicroApp extends HTMLElement {
  constructor() {
    super();
    // Shadow DOM 생성
    this.attachShadow({ mode: 'open' });
  }

  // DOM에 삽입될 때 호출
  connectedCallback() {
    this.render();
  }

  // DOM에서 제거될 때 호출
  disconnectedCallback() {
    this.cleanup();
  }

  // 관찰 대상 속성이 변경될 때 호출
  attributeChangedCallback(name, oldValue, newValue) {
    this.render();
  }

  static get observedAttributes() {
    return ['data-route', 'data-user'];
  }

  render() {
    this.shadowRoot.innerHTML = `
      <style>
        :host { display: block; }
        h2 { color: #333; }
      </style>
      <h2>마이크로앱 콘텐츠</h2>
    `;
  }

  cleanup() {
    // 이벤트 리스너, 타이머 정리
  }
}

// 브라우저에 등록
customElements.define('my-micro-app', MyMicroApp);

생명주기 콜백 상세:

콜백호출 시점활용
constructor()요소 인스턴스 생성 시Shadow DOM 생성, 초기 상태 설정
connectedCallback()DOM에 삽입될 때렌더링, 데이터 fetching, 이벤트 바인딩
disconnectedCallback()DOM에서 제거될 때리소스 정리, 이벤트 해제, 타이머 취소
attributeChangedCallback()관찰 속성 변경 시외부에서 전달된 데이터로 재렌더링
adoptedCallback()다른 document로 이동 시거의 사용하지 않음

1.2 Shadow DOM (캡슐화된 DOM)

Shadow DOM은 컴포넌트 내부의 DOM과 스타일을 외부로부터 격리하는 메커니즘이다. 일반 DOM(Light DOM)과 별개의 DOM 트리를 생성하여, 외부 CSS가 내부에 침투하지 않고 내부 CSS도 외부에 영향을 주지 않는다.

javascript
// Shadow DOM 모드
this.attachShadow({ mode: 'open' });   // 외부에서 shadowRoot 접근 가능
this.attachShadow({ mode: 'closed' }); // 외부에서 shadowRoot 접근 불가

Shadow DOM의 격리 범위:

격리 대상격리 여부설명
CSS 스타일격리됨외부 스타일이 Shadow DOM 내부에 적용되지 않음
DOM 쿼리격리됨document.querySelector()로 Shadow DOM 내부 요소에 접근 불가
이벤트 버블링부분 격리일부 이벤트는 Shadow 경계를 넘어 버블링 (composed: true)
JavaScript격리 안 됨전역 변수, 전역 상태는 공유됨

1.3 HTML Templates

<template> 태그 안에 작성된 마크업은 페이지 로드 시 렌더되지 않고, JavaScript로 활성화될 때 비로소 DOM에 삽입된다. <slot> 태그는 외부에서 콘텐츠를 주입할 수 있는 삽입점을 제공한다.

html
<!-- 렌더되지 않는 템플릿 정의 -->
<template id="micro-app-template">
  <style>
    .container { padding: 16px; border: 1px solid #e0e0e0; }
    ::slotted(h2) { color: #1a73e8; }
  </style>
  <div class="container">
    <slot name="header">기본 헤더</slot>
    <slot>기본 콘텐츠</slot>
  </div>
</template>

<!-- 사용 -->
<my-micro-app>
  <h2 slot="header">팀 A의 마이크로앱</h2>
  <p>이 콘텐츠가 기본 슬롯에 들어간다</p>
</my-micro-app>

2. 마이크로프론트엔드에서의 Web Components 활용

각 팀이 자신의 마이크로앱을 하나의 Custom Element으로 패키징하여 제공하면, Shell(컨테이너) 앱은 해당 태그를 삽입하는 것만으로 마이크로앱을 통합할 수 있다.

2.1 통합 구조

Shell 앱의 역할은 매우 단순하다. 각 팀의 번들(bundle.js)을 <script> 태그로 로드하고, 라우트에 따라 해당 커스텀 엘리먼트를 DOM에 삽입하기만 하면 된다.

html
<!-- Shell 앱의 index.html -->
<!DOCTYPE html>
<html>
<head>
  <!-- 각 팀의 번들을 스크립트로 로드 -->
  <script src="http://localhost:3001/bundle.js"></script>
  <script src="http://localhost:3002/bundle.js"></script>
</head>
<body>
  <nav>
    <a href="/">홈</a>
    <a href="/jobs">채용</a>
  </nav>
  <div id="app-container"></div>

  <script>
    const container = document.getElementById('app-container');

    function route() {
      container.innerHTML = '';
      const path = window.location.pathname;

      if (path === '/') {
        container.appendChild(document.createElement('team-home-app'));
      } else if (path === '/jobs') {
        container.appendChild(document.createElement('team-jobs-app'));
      }
    }

    window.addEventListener('popstate', route);
    route();
  </script>
</body>
</html>

2.2 마이크로앱 간 통신

Web Components 간 통신은 Custom Events를 통해 이루어진다. DOM 이벤트 시스템을 활용하므로 프레임워크에 의존하지 않는다.

javascript
// 팀 A의 컴포넌트에서 이벤트 발송
this.dispatchEvent(new CustomEvent('item-selected', {
  detail: { id: 42, name: '상품 A' },
  bubbles: true,
  composed: true  // Shadow DOM 경계를 넘어 전파
}));

// Shell 또는 팀 B의 컴포넌트에서 이벤트 수신
document.addEventListener('item-selected', (e) => {
  console.log('선택된 항목:', e.detail);
});

composed: true가 핵심이다. 이 옵션 없이는 이벤트가 Shadow DOM 경계에서 멈추어 외부로 전파되지 않는다.

2.3 프레임워크 래핑 패턴

React, Vue, Angular 등으로 작성한 컴포넌트를 Web Component로 래핑하여 제공할 수 있다. 각 팀이 선호하는 프레임워크로 개발하되, 최종 산출물은 Custom Element로 노출하는 것이다.

javascript
// React 컴포넌트를 Web Component로 래핑
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

class TeamReactApp extends HTMLElement {
  connectedCallback() {
    const mountPoint = document.createElement('div');
    this.attachShadow({ mode: 'open' }).appendChild(mountPoint);
    const root = ReactDOM.createRoot(mountPoint);
    root.render(React.createElement(App));
    this._root = root;
  }

  disconnectedCallback() {
    this._root?.unmount();
  }
}

customElements.define('team-react-app', TeamReactApp);

3. Shadow DOM 스타일 격리 심화

Shadow DOM의 가장 큰 가치는 브라우저 레벨의 스타일 격리이다. iframe을 제외하면, 별도의 문서 컨텍스트를 생성하지 않으면서 스타일을 격리할 수 있는 유일한 웹 표준 메커니즘이다.

스타일 격리의 특성:

특성설명
외부 -> 내부 차단글로벌 CSS, 부모 요소의 스타일이 Shadow DOM 내부에 적용되지 않음
내부 -> 외부 차단Shadow DOM 내부의 CSS가 외부 DOM에 영향을 미치지 않음
상속 가능한 속성은 통과font-family, color, line-height 등 CSS 상속 속성은 Shadow DOM으로 전파됨
CSS 변수 통과--my-color 같은 CSS Custom Properties는 Shadow DOM 경계를 넘어 적용됨

CSS Custom Properties를 활용하면 Shell 앱에서 마이크로앱의 테마를 제어할 수 있다:

css
/* Shell 앱에서 테마 변수 정의 */
:root {
  --brand-primary: #1a73e8;
  --brand-secondary: #ea4335;
  --font-base: 'Noto Sans KR', sans-serif;
}
css
/* 마이크로앱의 Shadow DOM 내부에서 변수 사용 */
:host {
  font-family: var(--font-base, sans-serif);
}
.header {
  color: var(--brand-primary, #333);
}

4. 장점과 단점

4.1 장점

장점설명
웹 표준W3C 표준 기술이므로 특정 라이브러리에 의존하지 않으며, 미래 지향적이다
프레임워크 무관React, Vue, Angular, Svelte 등 어떤 프레임워크와도 조합 가능하다
브라우저 레벨 스타일 격리Shadow DOM으로 CSS 충돌을 원천 차단한다. iframe 외에 유일한 브라우저 네이티브 격리 수단이다
생명주기 관리connectedCallback, disconnectedCallback 등으로 마이크로앱의 마운트/언마운트를 제어할 수 있다
독립 배포 가능각 팀이 자신의 번들을 별도 서버/CDN에 배포하면 Shell은 스크립트만 로드하면 된다
점진적 마이그레이션레거시 앱에 부분적으로 Web Component를 도입하여 단계적으로 마이크로프론트엔드로 전환할 수 있다

4.2 단점

단점설명
SSR 지원 어려움Web Components는 클라이언트 JS가 필수이므로, 서버에서 직접 렌더할 수 없다. Declarative Shadow DOM이 제안되었지만 아직 표준화 과정 중이다
브라우저 호환성Shadow DOM v1은 모든 모던 브라우저에서 지원되지만, 구형 브라우저에서는 폴리필이 필요하다 (폴리필 필요성은 감소 추세)
프레임워크 연동 복잡성React는 Custom Element의 속성/이벤트 전달에 제약이 있다. 래퍼가 필요하고 이 과정에서 보일러플레이트 코드가 증가한다
내부 라이브러리 부족라우팅, 상태 관리, 폼 처리 등을 위한 Web Components 네이티브 라이브러리가 React/Vue 생태계에 비해 부족하다
번들 크기 문제여러 마이크로앱이 동일 프레임워크(예: React)를 래핑하면, 각 번들에 프레임워크가 중복 포함될 수 있다
디버깅 난이도Shadow DOM 내부 요소는 일반적인 DOM 검사 도구에서 접근이 제한적이다

4.3 실무 고려 사항

Web Components는 개인적인 평가에서 미래 가능성이 높은 기술이다. 만약 Web Components가 더 일찍 웹 표준으로 자리 잡았다면, React나 Vue 대신 더 발전된 형태의 Web Components를 사용하고 있었을 수도 있다.

현재 시점에서는 Web Components를 직접 사용하기보다, Lit, Stencil 같은 경량 래퍼 라이브러리를 함께 사용하는 것이 현실적이다. 이러한 라이브러리는 Web Components의 장점은 유지하면서 개발 편의성을 크게 개선한다.


핵심 정리

핵심 개념요약
Web Components 3요소Custom Elements(태그 정의) + Shadow DOM(격리) + HTML Templates(마크업 정의)
Custom ElementscustomElements.define()으로 브라우저에 등록. 이름에 반드시 하이픈 포함
Shadow DOM브라우저 레벨의 스타일/DOM 격리. attachShadow({ mode: 'open' })으로 생성
마이크로프론트엔드 적용각 팀이 번들을 Custom Element으로 패키징하여 Shell 앱에서 태그 삽입으로 통합
통신 방식Custom Events + composed: true로 Shadow DOM 경계를 넘는 이벤트 전달
핵심 장점웹 표준, 프레임워크 무관, 브라우저 네이티브 스타일 격리
핵심 단점SSR 어려움, 프레임워크 연동 보일러플레이트, 라이브러리 생태계 부족

다음 단계

Web Components는 프레임워크에 독립적인 표준 기반 통합 수단이지만, SSR 불가와 생태계 한계가 있다. 다음 장에서는 더 오래된 클라이언트 통합 방식인 iframe을 살펴보고, 그 격리 수준과 성능 영향, 마이크로프론트엔드에서의 적합성을 검토한다.

다음: iframe 통합 ->