Backgrounds
현재 회사에서 개발 중인 제품은 고객사 웹 페이지 위에서 동작하는 다양한 모듈들을 제공하는 형태입니다.
이때 고객사 페이지와 독립적인 스타일을 유지하기 위해,
별도의 웹 컴포넌트를 생성한 뒤 그 안에서 자체적으로 정의한 스타일이 동작하도록 구현했습니다.
https://developer.mozilla.org/en-US/docs/Web/API/Web_components
웹 컴포넌트(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 태그를 사용하며, 이 과정은 아래와 같은 단계를 거칩니다.
- CSS 문자열 생성: styled-components는 정의된 스타일을 기반으로 고유한 클래스 이름과 함께 CSS 문자열을 생성합니다.
- 스타일 태그 생성 및 삽입: styled-components는 style 태그를 생성하고, 여기에 CSS 문자열을 삽입합니다. 이 style 태그는 문서의 <head> 섹션에 추가됩니다.
- 클래스 이름 적용: styled-components는 생성된 고유한 클래스 이름을 해당 React 컴포넌트의 className 속성에 적용합니다.
- 스타일 적용: 브라우저는 해당 클래스 이름을 가진 요소에 스타일을 적용합니다.
즉 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의 가장 큰 장점이자 특징은 → 컴포넌트의 스타일과 구조를 캡슐화하여 외부의 영향을 받지 않도록 하는 것!
아하! 즉 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에서는 이를 위해, 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를 사용하여 스타일링을 적용할 수 있었습니다.
'source-code > FrontEnd' 카테고리의 다른 글
Feature-Sliced Design(FSD) 도입기 (0) | 2024.08.03 |
---|---|
[chrome extension] dynamic import시 Cannot find module 에러 해결하기 (0) | 2024.07.26 |
[jest] waitFor을 통한 비동기 함수 테스트 (0) | 2024.06.23 |
[jest] test.each를 통해 여러 입력값 테스트 간결하게 구현하기 (0) | 2024.06.13 |
내가 프론트엔드에 클린 아키텍처를 도입한 이유 (0) | 2024.06.12 |