Files
2nd/10_Wiki/Topics/Coding/Frontend_Web_Components_Deep.md
T
2026-05-09 22:47:42 +09:00

6.9 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
frontend-web-components-deep Web Components — Custom Element / Shadow DOM / Slots Coding draft B conceptual 2026-05-09 2026-05-09
frontend
web-components
vibe-coding
language applicable_to
TS / Lit
Frontend
Web Components
Custom Element
Shadow DOM
slot
declarative shadow
Lit

Web Components

표준 component (React 안 써도). Custom Element + Shadow DOM + Template + Slot. Lit 가 가장 ergonomic. Storybook / design system / framework-agnostic.

📖 핵심 개념

  • Custom Element: <my-button> 같은 새 tag.
  • Shadow DOM: scoped DOM + style.
  • Slot: composition.
  • Declarative Shadow DOM: SSR.

💻 코드 패턴

가장 간단 Custom Element

class HelloWorld extends HTMLElement {
  connectedCallback() {
    this.innerHTML = '<p>Hello, World!</p>';
  }
}
customElements.define('hello-world', HelloWorld);

// HTML
// <hello-world></hello-world>

Lifecycle callbacks

class MyEl extends HTMLElement {
  connectedCallback() {
    // DOM 에 붙음
  }
  disconnectedCallback() {
    // DOM 에서 떼어짐 — cleanup
  }
  adoptedCallback() {
    // 다른 document 로 이사 (iframe)
  }
  static observedAttributes = ['name'];
  attributeChangedCallback(name: string, oldVal: string, newVal: string) {
    // attribute 변경
  }
}

Shadow DOM

class CardEl extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot!.innerHTML = `
      <style>
        :host { display: block; padding: 16px; border: 1px solid #ccc; }
        h2 { color: blue; }
      </style>
      <h2>Title</h2>
      <slot></slot>
    `;
  }
}
customElements.define('my-card', CardEl);

→ Style scoped — h2 가 외부 영향 X.

Slot (composition)

<my-card>
  <p>This goes into the default slot</p>
  <span slot="footer">Footer</span>
</my-card>
// Component
this.shadowRoot!.innerHTML = `
  <slot></slot>
  <hr>
  <slot name="footer"></slot>
`;

CSS shadow parts

this.shadowRoot!.innerHTML = `
  <style>
    .badge { padding: 4px; background: blue; color: white; }
  </style>
  <span part="badge">${this.textContent}</span>
`;
/* 외부 */
my-component::part(badge) {
  background: red;
}

→ Component 가 styling hook 노출.

CSS custom property (theming)

shadow.innerHTML = `
  <style>
    :host { color: var(--my-color, black); }
  </style>
`;
my-element { --my-color: red; }

Lit (가장 인기 framework)

import { LitElement, html, css } from 'lit';
import { customElement, property } from 'lit/decorators.js';

@customElement('my-counter')
class MyCounter extends LitElement {
  @property({ type: Number }) count = 0;
  
  static styles = css`
    button { font-size: 1rem; padding: 8px 16px; }
  `;
  
  render() {
    return html`
      <button @click=${() => this.count++}>+</button>
      <span>${this.count}</span>
    `;
  }
}

→ React 비슷한 ergonomic + 표준 API.

Reactive properties (Lit)

@property({ type: String, reflect: true }) name = '';
// reflect: true → attribute 도 업데이트

@state() private _internal = 0;
// re-render 만, 외부 X

// Manually trigger
this.requestUpdate();

Events

// Component 안
this.dispatchEvent(new CustomEvent('change', {
  detail: { value: this.value },
  bubbles: true,
  composed: true,  // shadow boundary 통과
}));

// 사용 측
<my-input @change=${this.handleChange}></my-input>

Form-associated custom element

class MyInput extends HTMLElement {
  static formAssociated = true;
  internals_: ElementInternals;
  
  constructor() {
    super();
    this.internals_ = this.attachInternals();
  }
  
  set value(v: string) {
    this.internals_.setFormValue(v);
  }
}

<form> 안에서 native input 처럼 동작.

Declarative Shadow DOM (SSR)

<my-card>
  <template shadowrootmode="open">
    <style>:host { padding: 8px }</style>
    <h2>Card</h2>
    <slot></slot>
  </template>
  <p>Content</p>
</my-card>

→ JS 없이 shadow DOM. SSR / SEO 친화.

Lit SSR

import { render } from '@lit-labs/ssr';
const html = await render(`<my-card>...</my-card>`);

React 안에서 Web Component

function App() {
  return (
    <my-card name="Alice"></my-card>
  );
}

// JSX type augmentation
declare global {
  namespace JSX {
    interface IntrinsicElements {
      'my-card': any;
    }
  }
}

→ React 19+ 가 web component property 자연 지원.

Vite Lit setup

// vite.config.ts
import { defineConfig } from 'vite';

export default defineConfig({
  build: {
    lib: {
      entry: 'src/index.ts',
      formats: ['es'],
    },
  },
});

Component library 출판

// package.json
{
  "name": "@me/my-components",
  "main": "dist/index.js",
  "module": "dist/index.js",
  "types": "dist/index.d.ts",
  "customElements": "custom-elements.json"
}

custom-elements.json (manifest) → Storybook / docs 자동.

Storybook

// my-card.stories.ts
export default { title: 'MyCard' };
export const Default = () => `
  <my-card>Hello</my-card>
`;

→ Web Components storybook 가 framework-agnostic 의 큰 장점.

Use case

  • Design system (Salesforce Lightning, Adobe Spectrum)
  • Embeddable widgets (chat, analytics, payment)
  • Cross-framework (React + Vue + Svelte 다 사용)
  • Browser extension UI
  • Edge / SSR friendly

Browser support

Custom Elements v1: Chrome, Firefox, Safari (모두 OK)
Shadow DOM: 모두 OK
Declarative Shadow: Chrome 90+, Safari 16.4+, FF 123+
Form-associated: 대부분 OK

Adoption examples

  • GitHub: 작은 widget 일부 web components
  • YouTube: 일부 player UI
  • Apple Music Web: web components heavy
  • Salesforce Lightning Web Components

🤔 의사결정 기준

상황 추천
Cross-framework component Web Component (Lit)
Design system Lit / Stencil
단일 React 앱 React component
Embeddable widget Web Component
SSR 중요 Declarative Shadow DOM
작은 단순 element Vanilla custom element

안티패턴

  • Light DOM 만 사용 + scoped 가정: style leak.
  • composed: false event + parent 안 잡힘: shadow 막힘.
  • 모든 거 Web Component: 큰 앱 = framework 가 좋음.
  • Lit 안 쓰고 vanilla 큰 앱: 보일러플레이트 폭발.
  • CSS-in-shadow + custom prop 없음: theming 불가.
  • Form integration 없음: form 깨짐.
  • Slot 미지원 framework + Web Component: composition 깨짐.

🤖 LLM 활용 힌트

  • Lit 가 Web Component 표준 framework.
  • Shadow DOM = scoped style, slot = composition.
  • Declarative shadow = SSR.
  • Cross-framework / embeddable 가 강점.

🔗 관련 문서