Skip to Content
ComponentsCalsWebComponent

WebComponent

외부 React 또는 Vue 컴포넌트를 Shadow DOM에 로드하여 렌더링하는 컴포넌트


개요

Cals WebComponent는 외부에서 빌드된 React 또는 Vue 컴포넌트 번들을 Shadow DOM 내에 로드·마운트합니다. 번들의 export를 분석하여 프레임워크를 자동 감지하므로, 프레임워크 타입을 명시할 필요가 없습니다. 로드·마운트 구현은 @cals/extension-plugin-react의 useBaseWebComponent입니다.

주요 특징

  • ✅ 프레임워크 자동 감지: 번들이 createRoot·createElement를 export하면 번들의 React, createApp을 export하면 Vue, 둘 다 없으면 호스트 React로 마운트
  • ✅ Shadow DOM 격리: 스타일 규칙은 서로 넘나들지 않음. CSS 변수 토큰은 들어감 (토큰)
  • ✅ 동적 Props 동기화: componentProps 변경 시 React는 재마운트 없이 re-render, Vue는 앱을 새로 마운트
  • ✅ 자동 스타일 주입: 외부 번들의 config.js + assets/style.css 자동 로드
  • ✅ depth 지원: 번들이 depth를 export하면 해당 값만큼 상위 디렉터리를 baseURL로 사용 (Extension-Plugin 호환)
  • ✅ 라이프사이클 관리: 마운트/언마운트 시 React root / Vue app 자동 정리
  • ✅ children 지원: Shadow DOM 컨테이너 바로 아래에 추가 UI 렌더링 가능

기본 사용

src로 외부 컴포넌트 번들 URL을 지정합니다. 프레임워크(React/Vue)는 자동 감지됩니다.

WebComponent 렌더 영역 (외부 번들 필요)

동적 Props 전달

componentProps가 변경되면 React 번들은 재마운트하지 않고 re-render합니다. Vue 번들은 앱을 언마운트한 뒤 새로 마운트합니다. 1단계 얕은 비교로 값이 같으면 아무것도 하지 않습니다.

외부 컴포넌트는 componentProps에 elRef(Shadow DOM을 붙인 컨테이너 요소)가 더해진 props를 받습니다.

const [theme, setTheme] = useState("light") <WebComponent src="https://cdn.example.com/extension/Dashboard.js" componentProps={{ theme, screenId: "SCR-001", token: accessToken }} height={400} />

오버레이 (children)

children은 Shadow DOM 컨테이너 다음 형제로 렌더링됩니다. WebComponent는 위치를 지정하지 않으므로, 겹쳐 놓으려면 className으로 기준 위치를 줍니다.

<WebComponent src="https://cdn.example.com/extension/Widget.js" height={250} className="relative" > <div className="absolute inset-0 flex items-center justify-center bg-black/20"> <span className="text-white">로딩 중...</span> </div> </WebComponent>

useWebComponent Hook

WebComponent 컴포넌트 대신 훅을 직접 사용할 수도 있습니다.

import { useWebComponent } from "@vortex/ui-cals"; import { useRef } from "react"; function CustomContainer() { const containerRef = useRef<HTMLDivElement>(null); useWebComponent(containerRef, { src: "/extension/MyWidget.js", componentProps: { theme: "dark" }, mode: "open", }); return <div ref={containerRef} style={{ height: 400 }} />; }

API Reference

Props

PropTypeDefaultDescription
srcstring(필수)외부 컴포넌트 번들의 전체 URL
componentPropsRecord<string, any>{}컴포넌트에 전달할 props 객체
mode"open" | "closed""open"Shadow DOM 모드
tokenBridgebooleantrue호스트 [data-token] 토큰을 Shadow DOM으로 복사
loader(url: string) => Promise<any>동적 import()번들·config.js 로더
heightstring | number-Shadow DOM 컨테이너 높이
classNamestring-외부 컨테이너 CSS 클래스
styleCSSProperties-외부 컨테이너 인라인 스타일
childrenReactNode-오버레이 등 추가 렌더 콘텐츠

useWebComponent Options

useWebComponent(containerRef, options)의 containerRef는 RefObject<HTMLElement | null>입니다.

OptionTypeDefaultDescription
srcstring(필수)외부 번들 URL
componentPropsRecord<string, any>{}컴포넌트에 전달할 props
mode"open" | "closed""open"Shadow DOM 모드
tokenBridgebooleantrue호스트 [data-token] 토큰 복사
loader(url: string) => Promise<any>동적 import()번들·config.js 로더
projectIdstring"ext"externalCss <style> id 접두어

mirrorHostTokens

@cals/extension-plugin-react의 mirrorHostTokens(shadowRoot): () => void를 그대로 다시 내보냅니다. WebComponent는 tokenBridge로 자동 호출하므로 직접 부를 일은 Shadow DOM을 직접 만드는 경우뿐입니다. 반환값은 복사를 멈추는 정리 함수입니다.

토큰

  • :root 전역 토큰은 상속으로 Shadow DOM 안에 들어갑니다.
  • [data-token] 범위 토큰은 tokenBridge(기본 켜짐)가 Shadow DOM 안으로 복사합니다. 효과는 외부 번들 화면에 같은 data-token 요소가 있을 때만 납니다.
  • .dark [data-token="x"]처럼 Shadow DOM 밖 조상과 결합한 규칙은 Shadow DOM 안에서 맞지 않습니다. @media·@layer 안의 [data-token] 규칙은 조건 없이 적용됩니다.

외부 번들 규약

React 번들

React 컴포넌트를 default export합니다. createRoot·createElement도 export하면 호스트 React 대신 번들의 React로 마운트합니다(Extension-Plugin 규약).

// 번들 파일 (예: MyWidget.js) export default function MyWidget(props: Record<string, unknown>) { return <div>...</div>; }

Vue 번들 (Extension-Plugin)

@cals/extension-plugin의 calsExPlugin()으로 빌드된 번들은 자동으로 호환됩니다. 번들은 createApp, ref를 named export하며, CalsWrapper가 내장되어 있어 componentProps로 전달한 CALS 컨텍스트(CalsSdk, emitter, token 등)가 Vue provide/inject로 자동 배포됩니다.

// Extension-Plugin이 자동 생성하는 번들 구조 export { createApp, ref } from 'vue' export default h(CalsWrapper, ...) // CalsWrapper가 props를 provide

호스트에서 전달해야 하는 componentProps:

PropTypeDescription
calsSdkCalsSdk호스트 앱 API (Alert, Navigate 등)
emitterEmittermitt 기반 이벤트 버스
tokenstring인증 토큰
screenIdstring화면 ID
componentIdstring컴포넌트 ID
themestring테마 (light/dark)
langstring언어 (ko/en)

createApp이 export에 존재하면 자동으로 Vue로 감지됩니다. componentProps가 바뀌면 앱을 언마운트한 뒤 새 props로 다시 마운트합니다.

Vue 번들 (직접 빌드)

Extension-Plugin 없이 직접 빌드한 Vue 번들도 지원됩니다:

// 번들 파일 (예: MyWidget.js) import MyComponent from "./MyComponent.vue"; export { createApp, ref } from "vue"; export default MyComponent;

선택적으로 동일 baseURL에 다음 파일을 배치할 수 있습니다:

파일역할
config.jsglobalCss 배열은 assets/style.css 앞에 붙여 Shadow DOM에, externalCss 배열은 document.head에 주입
assets/style.cssShadow DOM 내에 주입할 스타일시트

depth (baseURL 보정)

번들이 depth를 named export하면, 해당 값만큼 상위 디렉터리를 baseURL로 사용합니다. 예를 들어 depth = 1이고 src = "https://cdn.example.com/plugins/v1/MyWidget.js"이면, baseURL은 https://cdn.example.com/plugins가 됩니다 (기본값 0이면 https://cdn.example.com/plugins/v1).

이는 Extension-Plugin의 calsExPlugin() 빌드 구조와 호환됩니다.

// 번들 파일에서 depth export (선택) export const depth = 1; // config.js가 1단계 상위 디렉터리에 위치

접근성

권장 사항

  • ✅ Shadow DOM 내부의 포커스 가능한 요소는 문서 순서에 자연스럽게 통합됩니다.
  • ✅ 외부 컴포넌트 내에서 ARIA 속성을 적절히 사용하도록 번들 개발 시 고려합니다.
  • ✅ 로딩 상태를 children 오버레이로 제공할 때 aria-live="polite"를 고려합니다.

관련 컴포넌트

  • Iframe: 외부 웹 페이지를 iframe으로 임베드
Last updated on