[JavaScript] ShadowDOM을 사용한 Web Component에서 tailwindCSS 사용

Joowon Jang·2024년 11월 28일

JavaScript

목록 보기
16/17

Web Components를 사용한 프로젝트에서 발생한 에러 핸들링 과정을 담은 글이다.

ShadowDOM을 사용한 Web Components

Vanilla JavaScript 프로젝트에서 tailwindCSS를 사용해서 진행했다.
그 과정에서 여러 페이지에 공통적으로 사용되는 헤더, 푸터 같은 컴포넌트는 Web Components 기술을 사용해 구현하기로 했다.
Javascript로 Template을 생성해 ShadowRoot에 넣어주는 방식으로 구현해서 아래와 같은 형태가 되었다.

const template = document.createElement('template');
template.innerHTML = `
  <div>My Component</div>
`;

class MyComponent extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.appendChild(template.content.cloneNode(true));
  }
}

customElements.define('my-component', MyComponent);

ShadowDOM 내부에 tailwind 스타일이 적용되지 않는 문제

그런데, 위에서 구현한 Web Components 내부에서 아래의 코드처럼 tailwindCSS를 적용하려고 했더니 문제가 발생했다.

const template = document.createElement('template');
template.innerHTML = `
  <style>
    @import url('/styles/tailwind.css');
  </style>
  <div class="text-red-500">My Component</div>
`;

class MyComponent extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.appendChild(template.content.cloneNode(true));
  }
}

customElements.define('my-component', MyComponent);

ShadowRoot 내부는 스타일 충돌을 방지하기 위해 전역 스타일이 적용되지 않도록 캡슐화되어 있다.
그래서 template에서 <style> 요소 내부에 직접 css 코드를 작성해 주어야 하기 때문에, 위의 코드처럼 tailwind.css 파일을 import해 주었다.

개발 환경에서는 정상적으로 동작했지만, vite를 사용해서 build하고 vite preview 명령어를 통해 확인했더니 스타일이 적용되지 않고 있었다...

TailwindCSS의 스타일 적용 과정

문제는 Tailwind로 작성한 스타일이 적용되는 방식에 있었다.
Tailwind는 기본적으로 JIT 엔진을 사용해, 코드에서 사용된 p-5, flex 등의 className만을 찾아서 그 className에 해당하는 스타일 코드만을 css파일에 추가하도록 되어있다.
그래서 개발 환경에서는 DOM 요소가 변경 됨에 따라 실시간으로 사용된 className을 추적해 스타일을 추가해 반영이 되었지만, 빌드 과정에서는 PostCSS 플러그인 등을 통해 빌드 시점에 DOM에서 사용된 className만을 반영해 주기 때문에 적용이 되지 않은 것이다.

template 내부에 작성한 일반적인 css 코드는 잘 적용이 되는 건, 브라우저에서 실행될 때 해당 스타일 코드를 읽을 수 있기 때문인데, tailwind로 작성한 className에 매칭되는 css 코드가 build 시점에 생성되지 않아서 없기 때문이다 ㅠㅡㅠ

해결 방법

ShadowDOM을 사용하지 않는 방법

const template = document.createElement('template');
template.innerHTML = `
  <div class="text-red-500">My Component</div>
`;

class MyComponent extends HTMLElement {
  constructor() {
    super();
    this.appendChild(template.content.cloneNode(true));
  }
}

customElements.define('my-component', MyComponent);

위와 같이 ShadowDOM을 사용하지 않고 template을 그대로 사용하게 되면, 전역 스타일이 적용되므로, <style> 요소도 사용하지 않아도 되고, 정상적으로 스타일이 적용된다.
현재 코드로서는 문제가 생기지 않지만, 컴포넌트의 수가 많아진다면 다른 컴포넌트나 전역 스타일과의 충돌, 이벤트 버블링도 가능해져서 오류 발생 시 찾기 어려운 상황이 생길 수도 있다.

자세한 내용은 아래의 글에서 ShadowDOM을 사용하는 이유 참고

Web Components
https://velog.io/@juwon98/javascript-Web-Components

tailwind.css 전체를 삽입하는 방법

vite에서 제공하는 css 파일 전체를 inline으로 가져와 삽입하는 방식을 사용해 해결했다.

import cssText from "/src/styles/tailwind.css?inline"; // css 파일 inline 가져오기


const template = document.createElement('template');
template.innerHTML = `
  <style> ${cssText} </style>
  <div class="text-orange-500">My Component</div>
`;

class MyComponent extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.appendChild(template.content.cloneNode(true));
  }
}

customElements.define('my-component', MyComponent);

하지만, 이런 방식으로는 불필요한 css 코드들을 포함하기 때문에 성능 상 좋지 않다.
컴포넌트에서 사용한 className에 해당하는 css 코드만을 <style> 요소 내부에 작성해주는 방법으로 해결할 수 있겠지만, 컴포넌트에 사용되는 className이 많아질 경우 일일이 작성하는 데에 시간이 너무 많이 소요가 되고, 컴포넌트를 수정할 경우에도 다시 css 코드를 지워줘야 하는 번거로움이 있어 사용하지 않았다.

아직 React 같은 도구를 사용하지 않고 Web Components 기술로 커스텀 컴포넌트를 만들기에는 불편한 점이 많은 것 같다.

profile
깊이 공부하는 웹개발자

0개의 댓글