본문 바로가기
source-code/FrontEnd

WebComponent에서 styled-components 사용하기

by mattew4483 2024. 7. 10.
728x90
반응형

Backgrounds

현재 회사에서 개발 중인 제품은 고객사 웹 페이지 위에서 동작하는 다양한 모듈들을 제공하는 형태입니다.

이때 고객사 페이지와 독립적인 스타일을 유지하기 위해,

별도의 웹 컴포넌트를 생성한 뒤 그 안에서 자체적으로 정의한 스타일이 동작하도록 구현했습니다.

 

https://developer.mozilla.org/en-US/docs/Web/API/Web_components

 

Web Components - Web APIs | MDN

Web Components is a suite of different technologies allowing you to create reusable custom elements — with their functionality encapsulated away from the rest of your code — and utilize them in your web apps.

developer.mozilla.org

웹 컴포넌트(Web Component)는 재사용 가능한 캡슐화된 HTML 태그를 만들 수 있는 기술이며, 주요 장점은 아래와 같습니다.

  • 캡슐화 : 웹 컴포넌트는 각 컴포넌트의 스타일과 기능을 자체적으로 관리할 수 있습니다. 이는 다른 요소들과의 충돌을 방지하고, 모듈화 된 코드를 작성하는 데 유리합니다.
  • 재사용성 : 한번 작성된 웹 컴포넌트는 여러 프로젝트에서 재사용할 수 있습니다.
  • 표준 기술 : 웹 컴포넌트는 브라우저 표준에 기반을 두고 있어, 추가적인 라이브러리 없이도 사용할 수 있습니다.

React에서 웹 컴포넌트는 아래와 같이 구현할 수 있습니다.

class MyWebComponent extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    this.render();
  }

  render() {
    // React 애플리케이션을 웹 컴포넌트의 shadow DOM에 렌더링
    const mountPoint = document.createElement('div');
    this.shadowRoot.appendChild(mountPoint);

    ReactDOM.render(<MyReactComponent />, mountPoint);
  }
}

// 웹 컴포넌트 등록
customElements.define('my-web-component', MyWebComponent);

 

이와 동시에 해당 모듈에 작성된 스타일 요소들을 다른 서비스에서도 사용할 수 있어야 했습니다.

이를 위해 styled-components 라이브러리를 사용,

style이 포함된 컴포넌트들을 export 하여 여러 사용처에서 쉽게 적용할 수 있도록 구현했습니다.

import React from 'react';
import styled from 'styled-components';

// 스타일링된 컴포넌트 정의
export const StyledDiv = styled.div`
  color: blue;
  background-color: lightgray;
  padding: 10px;
  border-radius: 5px;
`;

const MyReactComponent = () => {
  return (
    <StyledDiv>
      This is a styled component inside a web component.
    </StyledDiv>
  );
};


// index.ts
export { StyledDiv } from 'StyledDiv.tsx' // 외부에 export해, 다른 사내 프로젝트에서도 사용가능하게 만듬

export 한 뒤 해당 서비스를 내부 package로 제공해, style 관련 로직이 포함된 컴포넌트를 여러 곳에서 재사용할 수 있도록 만들었습니다.

 

Problems & Caused

하지만 실제로 MyWebComponent를 브라우저에 띄워보면... 

MyReactComponent에 StyledDiv내에 정의된 스타일들이 적용되지 않습니다!

왜 그런 것일까요? 이를 알기 위해서는 styled-components의 동작 방식과 web component의 특징에 대해 이해할 필요가 있습니다.

1. styled componenst 동작 방식

styled-components는 스타일을 DOM에 삽입하기 위해 style 태그를 사용하며, 이 과정은 아래와 같은 단계를 거칩니다.

  1. CSS 문자열 생성: styled-components는 정의된 스타일을 기반으로 고유한 클래스 이름과 함께 CSS 문자열을 생성합니다.
  2. 스타일 태그 생성 및 삽입: styled-components style 태그를 생성하고, 여기에 CSS 문자열을 삽입합니다. 이 style 태그는 문서의 <head> 섹션에 추가됩니다.
  3. 클래스 이름 적용: styled-components는 생성된 고유한 클래스 이름을 해당 React 컴포넌트의 className 속성에 적용합니다.
  4. 스타일 적용: 브라우저는 해당 클래스 이름을 가진 요소에 스타일을 적용합니다.

style 태그가 추가되고, 생성된 class name별 스타일이 정의된 모습

즉 styled-components는 작성된 스타일별로 고유한 클래스 이름과 CSS 문자열을 생성하고,

이들이 정의된 style 태그를 document의 head 내에 추가하여,

브라우저에서 클래스 이름에 해당하는 컴포넌트들에 작성된 스타일이 적용될 수 있도록 하는 것이죠.

2. web component 특징

web component는 shadow DOM을 사용합니다!

https://developer.mozilla.org/ko/docs/Web/API/Web_components/Using_shadow_DOM

 

shadow DOM 사용하기 - Web API | MDN

웹 컴포넌트의 중요한 측면은 캡슐화입니다. 캡슐화를 통해 마크업 구조, 스타일, 동작을 숨기고 페이지의 다른 코드로부터의 분리하여 각기 다른 부분들이 충돌하지 않게 하고, 코드가 깔끔하

developer.mozilla.org

shadow DOM의 가장 큰 장점이자 특징은 → 컴포넌트의 스타일과 구조를 캡슐화하여 외부의 영향을 받지 않도록 하는 것!

 

내부 요소들이 외부로부터 격리됩니다!

아하! 즉 shadow DOM 내부의 요소들은 document head에 정의된 전역 스타일의 영향을 받지 않으며,

이로 인해 styled-components가 생성한 style 태그가 웹 컴포넌트 내부에서는 적용되지 않았던 것이죠!

 

Solutions

styled-components의 style 태그가 shadow DOM 외부의 documenent head 태그에 생성된 것이 원인이므로,

이 style 태그가 shadow DOM 안에 추가될 수 있도록 하면 되겠죠!

https://styled-components.com/docs/api#stylesheetmanager

 

styled-components: API Reference

API Reference of styled-components

styled-components.com

styled-components에서는 이를 위해, StyleSheetManager라는 helper component를 제공하고 있습니다.

StyleSheetManager의 옵션들 중, target 속성을 통해 styled을 주입할 대체 DOM을 지정할 수 있죠!

class MyWebComponent extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    this.render();
  }

  render() {
    // React 애플리케이션을 웹 컴포넌트의 shadow DOM에 렌더링
    const mountPoint = document.createElement('div');
    this.shadowRoot.appendChild(mountPoint);

    // 스타일 시트 슬롯 생성
    const styleSlot = document.createElement('style');
    this.shadowRoot.appendChild(styleSlot);

    ReactDOM.render(
      <StyleSheetManager target={styleSlot}>
        <MyReactComponent />
      </StyleSheetManager>,
      mountPoint
    );
  }
}

// 웹 컴포넌트 등록
customElements.define('my-web-component', MyWebComponent);

ReactDOM render시 미리 만들어 둔 style 태그를 StyleSheetManager의 target으로 설정했고

이를 통해 웹 컴포넌트 내부에서도 styled-components를 사용하여 스타일링을 적용할 수 있었습니다.

 

728x90
반응형