[G1-Sync] Manual knowledge update

This commit is contained in:
Antigravity Agent
2026-05-09 22:47:42 +09:00
parent 93ec7e9056
commit 21ac3ed255
56 changed files with 22043 additions and 43 deletions
@@ -0,0 +1,327 @@
---
id: frontend-web-components-deep
title: Web Components — Custom Element / Shadow DOM / Slots
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [frontend, web-components, vibe-coding]
tech_stack: { language: "TS / Lit", applicable_to: ["Frontend"] }
applied_in: []
aliases: [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
```ts
class HelloWorld extends HTMLElement {
connectedCallback() {
this.innerHTML = '<p>Hello, World!</p>';
}
}
customElements.define('hello-world', HelloWorld);
// HTML
// <hello-world></hello-world>
```
### Lifecycle callbacks
```ts
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
```ts
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)
```html
<my-card>
<p>This goes into the default slot</p>
<span slot="footer">Footer</span>
</my-card>
```
```ts
// Component
this.shadowRoot!.innerHTML = `
<slot></slot>
<hr>
<slot name="footer"></slot>
`;
```
### CSS shadow parts
```ts
this.shadowRoot!.innerHTML = `
<style>
.badge { padding: 4px; background: blue; color: white; }
</style>
<span part="badge">${this.textContent}</span>
`;
```
```css
/* 외부 */
my-component::part(badge) {
background: red;
}
```
→ Component 가 styling hook 노출.
### CSS custom property (theming)
```ts
shadow.innerHTML = `
<style>
:host { color: var(--my-color, black); }
</style>
`;
```
```css
my-element { --my-color: red; }
```
### Lit (가장 인기 framework)
```ts
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)
```ts
@property({ type: String, reflect: true }) name = '';
// reflect: true → attribute 도 업데이트
@state() private _internal = 0;
// re-render 만, 외부 X
// Manually trigger
this.requestUpdate();
```
### Events
```ts
// 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
```ts
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)
```html
<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
```ts
import { render } from '@lit-labs/ssr';
const html = await render(`<my-card>...</my-card>`);
```
### React 안에서 Web Component
```tsx
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
```ts
// vite.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
build: {
lib: {
entry: 'src/index.ts',
formats: ['es'],
},
},
});
```
### Component library 출판
```json
// 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
```ts
// 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 가 강점.
## 🔗 관련 문서
- [[Frontend_Design_Tokens]]
- [[React_Headless_UI_Patterns]]
- [[Web_PWA_Service_Worker]]