Stencil 완벽 가이드 — 웹 컴포넌트 컴파일러·TSX·테스트·프레임워크 통합
이 글의 핵심
Stencil는 Web Components를 위한 컴파일러입니다. TSX와 데코레이터로 컴포넌트를 작성하고, 표준 Custom Elements로 출력하여 React·Vue·Angular 등 어떤 스택에도 끼워 넣을 수 있습니다. 이 글에서는 핵심 개념부터 빌드·테스트·통합까지 실무 관점으로 정리합니다.
이 글의 핵심
Stencil는 Ionic 팀이 만든 웹 컴포넌트(Web Components) 컴파일러입니다. 브라우저 표준인 Custom Elements, Shadow DOM, HTML Templates 위에, TypeScript·TSX, 반응성, 테스트·번들링까지 한 번에 다루는 도구입니다. “프레임워크 없이 컴포넌트만 재사용하고 싶다”, “디자인 시스템을 여러 제품(React·Angular·Vue)에 동일하게 올리고 싶다”는 요구에 잘 맞습니다.
이 가이드에서는 다음을 다룹니다.
- Stencil의 역할(컴파일러·런타임·산출물)
- TSX와 데코레이터로 컴포넌트 정의하기
- 반응성과 상태(
@State,@Prop,@Watch) - 스타일링과 테마(Shadow DOM, CSS 변수,
::part) - 단위·E2E 테스트(Jest, Puppeteer 기반)
- 빌드 최적화(
stencil.config타깃·extras) - 프레임워크 통합(output targets)
1. Stencil를 두는 올바른 자리
1.1 “컴파일러”라는 말의 의미
Stencil는 소스(*.tsx)를 읽어 표준에 가까운 Custom Element 클래스와 최적화된 자바스크립트로 변환합니다. 즉, 개발자에게는 React와 비슷한 컴포넌트 모델을 주고, 배포 시점에는 프레임워크에 묶이지 않는 위젯을 남깁니다.
왜 컴파일 단계가 필요한가에 대한 답은 간단합니다. 순수 HTMLElement만으로도 컴포넌트를 만들 수 있지만, 템플릿·상태·속성 동기화·이벤트·접근성을 매번 수작업으로 작성하면 생산성과 일관성이 떨어집니다. Stencil는 그 보일러플레이트를 줄이고, 잘못된 패턴(예: 속성과 내부 상태 불일치)을 줄이기 위한 구조를 제공합니다.
1.2 런타임과 번들 크기
Stencil 런타임은 가볍게 유지되도록 설계되어 있습니다. 다만 Shadow DOM·반응성·슬롯 등을 쓰면 당연히 Vanilla JS 몇 줄보다는 큽니다. 트레이드오프는 “한 번 빌드한 컴포넌트를 여러 앱에서 재사용”할 때 이득이 큰지로 판단하면 됩니다.
1.3 Ionic·Stencil 관계
Ionic UI는 Stencil로 구현된 사례가 널리 알려져 있습니다. “모바일 하이브리드 앱 = Ionic”으로만 이해하면 범위를 좁힌 것이고, Stencil 자체는 임의의 웹 프로젝트·디자인 시스템에 쓸 수 있습니다.
2. 핵심 개념 정리
2.1 Custom Element와 태그 이름
Stencil 컴포넌트는 @Component의 tag로 커스텀 태그명을 갖습니다. 이름은 하이픈 포함 소문자(예: my-ui-button)처럼 Custom Elements 명명 규칙을 따르는 것이 안전합니다.
@Component({
tag: 'my-ui-button',
styleUrl: 'my-ui-button.css',
shadow: true,
})
export class MyUiButton {
// ...
}
태그 문자열은 전역 레지스트리에 등록되므로, 한 페이지에 동일 태그가 두 번 정의되면 충돌합니다. 마이크로 프론트엔드처럼 여러 번들이 한 문서에 올라오는 경우 번들 중복·버전 불일치를 특히 주의해야 합니다.
2.2 Shadow DOM: 캡슐화와 한계
shadow: true이면 스타일과 DOM 트리가 캡슐화됩니다. 서드파티 위젯·디자인 토큰이 고정된 UI에는 유리합니다. 반면 전역 CSS(예: 앱의 Tailwind 유틸리티)와 자연스럽게 섞기 어렵고, 폼·레이블 연결처럼 Light DOM이 더 편한 경우도 있습니다. 이럴 때는 CSS 변수로 토큰을 주입하거나, 팀 정책에 따라 scoped·Light DOM 관련 옵션을 검토합니다.
2.3 슬롯(Slot)과 합성
웹 컴포넌트의 합성(composition)은 주로 <slot>으로 이루어집니다. Stencil TSX에서도 slot 속성으로 이름 있는 슬롯을 나눌 수 있습니다. 라이브러리 설계 시 “어디까지를 내부 구현으로 숨기고, 어디를 슬롯으로 열 것인가”가 API 품질을 가릅니다.
2.4 비동기 로딩과 defineCustomElements
배포 패키지는 보통 엔트리 스크립트에서 defineCustomElements()를 호출해 태그를 등록합니다. 앱 부팅 시점에 한 번만 로드되도록 하고, 라우트 단위로 나눌 경우 지연 로딩 전략과 맞춰야 합니다.
2.5 프로젝트 생성과 디렉터리 구조
공식 스타터로 컴포넌트 라이브러리 뼈대를 만들 수 있습니다. 팀 표준에 맞게 패키지 매니저(npm·pnpm·yarn)를 선택하면 됩니다.
npm init stencil@latest component my-lib
cd my-lib
npm install
npm start
일반적인 라이브러리 레이아웃은 다음과 비슷합니다. 경로는 버전에 따라 약간 달라질 수 있으나, src/components에 컴포넌트, stencil.config.ts에 빌드 설정이 있다는 점은 동일합니다.
my-lib/
├── src/
│ ├── components/
│ │ └── my-ui-button/
│ │ ├── my-ui-button.tsx
│ │ └── my-ui-button.css
│ └── index.html # 개발 시 프리뷰
├── stencil.config.ts
├── tsconfig.json
└── package.json
npm start는 로컬 개발 서버를 띄워 핫 리로드로 TSX를 빠르게 확인합니다. 라이브러리 소비자 관점에서는 dist/ 산출물과 loader 엔트리가 중요하므로, 배포 전 npm run build로 결과물을 반드시 검토합니다.
3. TSX와 데코레이터
3.1 TSX의 역할
Stencil는 JSX와 유사한 TSX로 마크업을 작성합니다. 이는 “HTML 문자열을 조립하는 것”이 아니라, 컴파일러가 렌더링 루틴으로 변환할 수 있는 형태로 쓰라는 뜻입니다. 조건부 렌더링·리스트 렌더링 패턴도 익숙한 React 개발자에게 친숙합니다.
3.2 @Component
클래스에 붙이는 최상위 메타데이터입니다. tag, styleUrl / styleUrls, shadow, scoped 등을 설정합니다. 스타일 파일을 분리하면 테마별 CSS 스왑이 쉬워집니다.
3.3 @Prop: 외부에서 주입되는 입력
@Prop()은 부모(호스트)가 HTML 속성 또는 DOM 프로퍼티로 넘기는 값입니다. reflect를 켜면 속성이 DOM에 반영되어 CSS 선택자·외부 스크립트와 연동하기 쉽습니다. 불변 입력처럼 다루는 것이 일반적이며, 자식이 마음대로 바꾸면 추적이 어려워집니다.
@Prop({ reflect: true }) variant: 'primary' | 'secondary' = 'primary';
@Prop() label: string;
문자열로만 오는 HTML 속성과, 객체를 넘기는 프로퍼티의 차이를 이해해야 합니다. 복잡한 객체는 JSON 문자열을 파싱하는 패턴보다는 프로퍼티로만 설정하도록 API를 단순화하는 편이 유지보수에 유리합니다.
3.4 @State: 내부 반응형 상태
@State()는 컴포넌트 내부 상태입니다. 값이 바뀌면 Stencil가 재렌더를 스케줄합니다. 상태 갱신은 불변성을 지키는 편이 디버깅에 유리합니다(참조가 바뀌어야 변경으로 인식되는 경우가 많음).
@State() private open = false;
private toggle = () => {
this.open = !this.open;
};
3.5 @Event와 @Listen
@Event는 CustomEvent를 발행하기 위한 선언입니다. EventEmitter 패턴으로 자식 → 부모로 메시지를 보냅니다. @Listen은 호스트나 window/document의 이벤트를 메서드에 바인딩할 때 씁니다. 문서 클릭으로 드롭다운을 닫는 패턴처럼 전역 이벤트를 다룰 때 유용합니다.
@Event({ eventName: 'myChange' }) changeEvent: EventEmitter<string>;
emitValue(value: string) {
this.changeEvent.emit(value);
}
3.6 @Watch, @Method, @Element
@Watch('propName'): 지정한@Prop또는@State가 바뀔 때 훅을 실행합니다. 파생 데이터 동기화·외부 라이브러리와의 브리지에 씁니다.@Method(): 클래스 퍼블릭 메서드를 호스트에서await el.open()처럼 호출할 수 있게 노출합니다. 남용하면 캡슐화가 깨지므로 꼭 필요한 제어 API만 노출합니다.@Element(): 호스트 요소 참조를 주입합니다. 포커스 이동·측정·포털 등 DOM이 필요할 때 사용합니다.
3.7 render()와 TSX 규칙
클래스에 render() 메서드를 두고 그 안에서 TSX를 반환합니다. Stencil는 이 함수를 컴포넌트의 뷰 트리로 취급하며, @State·@Prop 변경 시 다시 호출됩니다. 부수 효과(side effect)는 render() 안에 두지 않고, 라이프사이클·이벤트 핸들러·@Watch로 옮기는 것이 안전합니다.
import { Component, h, Prop, State } from '@stencil/core';
@Component({
tag: 'my-ui-button',
styleUrl: 'my-ui-button.css',
shadow: true,
})
export class MyUiButton {
@Prop() label: string;
@State() private pressed = false;
render() {
return (
<button
type="button"
aria-pressed={this.pressed}
onMouseDown={() => (this.pressed = true)}
onMouseUp={() => (this.pressed = false)}
onMouseLeave={() => (this.pressed = false)}
>
<slot name="icon" />
{this.label}
</button>
);
}
}
h는 JSX 런타임으로 명시적으로 import 할 수 있습니다(팀/TS 설정에 따라 생략되기도 함). 접근성 속성(aria-*, role)은 공개 컴포넌트에서 빼먹기 쉬우므로, 디자인 시스템 수준에서 체크리스트를 두는 것이 좋습니다.
4. 반응성과 상태 관리
4.1 Stencil의 반응성 모델
Stencil는 컴포넌트 단위로 상태 변경을 감지하고 렌더링합니다. 거대한 단일 스토어가 아니라, 웹 컴포넌트가 본래 지향하는 캡슐화와 잘 맞습니다. 앱 전체 상태가 필요하면 Context API 패턴(Stencil의 @Prop 드릴링, 또는 가벼운 스토어를 상위에서 주입)을 쓰거나, 호스트 프레임워크의 상태관리와 경계를 명확히 하는 편이 낫습니다.
4.2 @Prop vs @State 경계
실무에서는 다음 규칙이 보수적으로 잘 작동합니다.
@Prop: 부모가 제어하는 설정·데이터 입력@State: 위젯 내부 UI 전용 일시 상태(열림/닫힘, 탭 인덱스 등)
경계가 애매하면 “이 값이 다른 인스턴스와 공유되어야 하는가”, “URL·폼·상위 상태와 동기화되는가”를 기준으로 나눕니다.
4.3 @Watch로 파생 값과 동기화
외부 @Prop이 바뀔 때 내부 정규화·검증을 하려면 @Watch가 적합합니다. 예: 날짜 문자열 Prop을 받아 내부 Date로 파싱하되, 잘못된 입력 시 fallback.
4.4 비동기 데이터
async 데이터는 componentWillLoad·componentWillRender 등 라이프사이클 훅에서 로드하고, @State에 넣어 반응형으로 표시합니다. 로딩·에러·빈 상태 UI를 컴포넌트 계약의 일부로 설계합니다.
async componentWillLoad() {
this.loading = true;
try {
this.items = await fetchJson('/api/items');
} catch (e) {
this.error = e;
} finally {
this.loading = false;
}
}
4.5 라이프사이클 훅
Stencil는 커스텀 엘리먼트 수명에 맞춘 훅을 제공합니다. 대표적인 흐름만 정리하면 다음과 같습니다.
| 훅 | 용도 |
|---|---|
connectedCallback | DOM에 연결될 때(네이티브와 동일). 외부 구독 시작 시 주의(메모리 누수 방지). |
disconnectedCallback | DOM에서 제거될 때. 구독 해제·타이머 정리. |
componentWillLoad | 첫 로드 전 비동기 준비(데이터 패칭 등). Promise 반환 가능. |
componentDidLoad | 첫 렌더 후 한 번. 포커스 트랩·측정 DOM 등. |
componentWillRender / componentDidRender | 렌더 직전·직후. 렌더 횟수를 늘리지 않도록 가벼운 로직만. |
componentWillUpdate / componentDidUpdate | 재렌더 전후. @Prop·@State 변경에 따른 업데이트 사이클. |
언마운트 시 정리를 빼먹으면 이벤트 리스너·MutationObserver가 누적되어 메모리와 성능 문제로 이어집니다. React의 useEffect cleanup과 같은 책임을 disconnectedCallback에 두는 습관이 필요합니다.
5. 스타일링과 테마
5.1 Shadow DOM에서의 스타일 전략
Shadow 내부 스타일은 전역 리셋의 영향을 덜 받습니다. 대신 디자인 토큰은 보통 CSS Custom Properties(변수)로 바깥에서 주입합니다. :host에 기본값을 두고, 호스트 앱에서 루트에 변수를 덮어씁니다.
:host {
--my-button-bg: #2563eb;
--my-button-color: #fff;
}
button {
background: var(--my-button-bg);
color: var(--my-button-color);
}
5.2 ::part로 외부 스타일 허용 범위 열기
캡슐화를 유지하면서 특정 부분만 스타일을 허용하려면 part 속성과 ::part() 선택자를 사용합니다. 디자인 시스템에서 “안전한 커스터마이징 포인트”를 문서화하기 좋습니다.
5.3 다크 모드·테마 스위칭
테마는 클래스 토글보다는 CSS 변수 레이어로 두는 경우가 많습니다. :host(.dark)처럼 호스트에 붙은 클래스를 :host-context 대신 Prop으로 테마를 받아 :host에 반영하는 방식도 흔합니다(브라우저 지원과 팀 컨벤션에 맞게 선택).
5.4 SCSS·PostCSS
stencil.config.ts에서 플러그인을 연결해 SCSS·Autoprefixer 등을 사용할 수 있습니다. 팀 빌드 파이프라인과 동일한 후처리를 맞추면 크로스 브라우저 이슈를 줄입니다.
6. 테스트: Jest와 E2E
6.1 철학: 표준 DOM 위에서
Stencil 테스트는 실제 Custom Element가 DOM에 마운트되는 경로를 지향합니다. @stencil/core/testing의 newSpecPage는 렌더링·업데이트·이벤트를 한 번에 검증하기 좋습니다.
import { newSpecPage } from '@stencil/core/testing';
import { MyUiButton } from '../my-ui-button';
describe('my-ui-button', () => {
it('renders label', async () => {
const page = await newSpecPage({
components: [MyUiButton],
html: `<my-ui-button label="저장"></my-ui-button>`,
});
expect(page.root.shadowRoot.textContent).toContain('저장');
});
});
왜 newSpecPage인가: 단위 테스트에서 가장 비싼 비용은 “브라우저 없이도 충분히 비슷한 환경을 만드는 것”입니다. Stencil가 제공하는 헬퍼는 컴포넌트 등록·렌더 사이클·비동기 플러시를 반영합니다.
6.2 사용자 상호작용
클릭·키보드 입력은 DOM 이벤트를 디스패치하거나, 컴포넌트 메서드를 직접 호출해 검증합니다. @Method로 노출한 API는 계약 테스트에 포함하는 것이 좋습니다.
6.3 E2E(Puppeteer)
Stencil 프로젝트는 종종 Puppeteer 기반 E2E를 포함합니다. 실제 Chromium에서 레이아웃·포커스 링·접근성을 확인할 때 유리합니다. E2E는 느리므로 핵심 사용자 플로우에만 집중하고, 나머지는 단위 테스트로 커버합니다.
6.4 스냅샷
스냅샷은 빠르지만 마크업 변경에 둔감한 팀에서는 노이즈가 됩니다. 디자인 시스템에서는 의미 있는 서브트리만 스냅샷하거나, 스토리북·시각적 회귀와 역할을 나눕니다.
6.5 Jest와 Stencil 테스트 설정
Stencil 프로젝트는 @stencil/core/testing과 함께 Jest를 쓰는 구성이 흔합니다. stencil.config.ts에 testing 블록을 두어 변환·모듈 매핑을 맞추고, CI에서는 --maxWorkers로 병렬도를 조절합니다.
// stencil.config.ts (발췌)
import { Config } from '@stencil/core';
export const config: Config = {
namespace: 'mylib',
outputTargets: [{ type: 'dist' }, { type: 'docs-readme' }],
testing: {
browserHeadless: true,
transformIgnorePatterns: ['/node_modules/(?!lodash-es)/'],
},
};
스타터가 생성한 jest.config/package.json 스크립트를 그대로 쓰되, 모노레포에서는 경로 별칭(paths)과 Jest의 moduleNameMapper를 동기화해야 합니다. 불일치하면 “테스트만 import 에러”가 반복됩니다.
6.6 E2E 테스트 예시(Puppeteer)
E2E는 실제 브라우저에서 포커스 이동·키보드·스크롤을 검증할 때 유리합니다. Stencil E2E 스타터는 Puppeteer를 사용하는 경우가 많습니다.
// src/components/my-ui-button/my-ui-button.e2e.ts (개념 예시)
import { newE2EPage } from '@stencil/core/testing';
describe('my-ui-button e2e', () => {
it('클릭 시 이벤트가 발생한다', async () => {
const page = await newE2EPage();
await page.setContent('<my-ui-button label="확인"></my-ui-button>');
const el = await page.find('my-ui-button');
const spy = await page.spyOnEvent('myChange');
await el.click();
await page.waitForChanges();
expect(spy).toHaveReceivedEvent();
});
});
E2E는 느리고 깨지기 쉬우므로 “사용자 여정 상 반드시 보장할 것”만 남기고, 나머지는 newSpecPage로 내립니다.
7. 빌드 최적화
7.1 stencil.config.ts의 중심
Stencil 빌드는 stencil.config.ts에서 결정됩니다. 여기서 타깃 브라우저, 출력 모듈 형식, 프레임워크 래퍼 생성, 문서 사이트 출력 등을 한곳에서 조정합니다.
7.2 extras와 폴리필
구형 브라우저 지원이 필요하면 폴리필·extras 설정을 검토합니다. 반대로 최신 브라우저만 지원한다면 불필요한 폴리필을 줄여 번들을 가볍게 유지합니다. “지원 매트릭스”는 성능·유지보수 비용과 직결됩니다.
7.3 코드 분할·지연 로딩
라이브러리 패키지에서는 엔트리 청크와 lazy-loaded 번들 설계가 중요합니다. 앱 측에서는 import '@my-ui/components/loader' 후 필요한 페이지에서만 위젯 번들이 로드되게 합니다.
7.4 Tree-shaking과 공개 API
소비자가 실제로 쓰는 컴포넌트만 포함되도록 package.json의 sideEffects, 배럴 파일(index.ts) 설계를 정리합니다. 사용하지 않는 아이콘·유틸이 대량으로 끌려오면 체감 성능이 떨어집니다.
7.5 사전 렌더링(Prerender)
문서·마케팅 페이지처럼 정적 HTML이 유리한 경우 prerender 출력을 켜 SEO와 FCP를 개선할 수 있습니다. 동적 대시보드 위젯 라이브러리에는 우선순위가 낮을 수 있습니다.
7.6 stencil.config.ts로 한 번에 보는 출력 전략
아래는 개념을 압축한 예시입니다. 실제 프로젝트에서는 패키지 이름, 번들 형식, 문서 타깃이 달라집니다.
import { Config } from '@stencil/core';
export const config: Config = {
namespace: 'mylib',
srcDir: 'src',
taskQueue: 'async',
outputTargets: [
{ type: 'dist', esmLoaderPath: '../loader' },
{ type: 'dist-custom-elements' },
{
type: 'docs-readme',
},
{
type: 'www',
serviceWorker: null,
},
],
extras: {
cssShim: true,
shadowDomShim: true,
},
};
dist: npm 패키지로 배포되는 일반 빌드.loader는 지연 하이드레이션에 유리합니다.dist-custom-elements: 각 컴포넌트를 더 잘게 쓰고 싶을 때 검토합니다.www: 문서/데모 사이트용. 라이브러리만 배포한다면 생략 가능합니다.extras: 타깃 브라우저에 맞춰 shim을 줄이는 방향으로 튜닝합니다(불필요한 폴리필은 번짐).
taskQueue를 async로 두면 업데이트를 배치할 수 있어 대량 렌더에서 유리한 경우가 있습니다. 반면 상호작용 지연이 체감되면 설정을 재검토합니다.
8. 프레임워크 통합
8.1 왜 통합 전략이 필요한가
Stencil 산출물은 표준이지만, React는 속성 이름·이벤트 명명, Vue는 v-model, Angular는 NgModule·CUSTOM_ELEMENTS_SCHEMA 등 각기 다른 접점이 있습니다. Stencil의 output targets는 이런 보일러플레이트를 생성해 소비자 경험을 프레임워크답게 맞춥니다.
공식적으로는 각각 @stencil/react-output-target, @stencil/vue-output-target, @stencil/angular-output-target 등을 stencil.config.ts의 outputTargets에 추가하는 방식이 문서화되어 있습니다. 팀에서는 한 번 설정해 CI에서 래퍼를 재생성하고, 패치 버전 업 시 타입·속성 매핑이 깨지지 않는지 회귀 테스트를 돌리는 흐름이 일반적입니다.
8.2 React
래퍼는 보통 속성을 props로, 이벤트를 콜백으로 매핑합니다. 타입 정의가 함께 생성되면 DX가 좋아집니다. 서버 컴포넌트 환경에서는 커스텀 엘리먼트가 클라이언트 경계 안에 있어야 한다는 점을 문서에 명시합니다.
소비자 측 사용감은 대략 다음과 같이 정리할 수 있습니다.
// 소비 앱(개념): 생성된 래퍼가 있다면 프레임워크답게 쓸 수 있음
import { MyUiButton } from '@acme/my-lib-react';
export function SaveBar() {
return (
<MyUiButton
variant="primary"
label="저장"
onMyChange={(e) => console.log(e.detail)}
/>
);
}
이벤트 이름은 Stencil의 @Event 설정과 생성기 규칙에 따르므로, 공개 문서에 이벤트 표를 함께 제공하는 것이 좋습니다.
8.3 Vue·Angular
- Vue: 모델 바인딩·이벤트 이름을 래퍼에서 흡수합니다.
v-model을 지원하려면 어떤 prop을 값으로, 어떤 이벤트를 업데이트로 볼지를 컴포넌트 계약에 명시합니다. - Angular:
CUSTOM_ELEMENTS_SCHEMA를 모듈에 넣거나, 생성된 컴포넌트 래퍼를 사용해 템플릿 타입체크 이점을 살립니다. 대규모 팀일수록 래퍼 생성·타입 안전성 쪽이 유지보수 비용이 낮은 경우가 많습니다.
8.4 번들러와 커스텀 엘리먼트
Vite·Webpack 등에서 커스텀 태그를 알 수 없는 경우 외부화(external) 설정이 필요할 수 있습니다. 프레임워크별 스타터를 따르되, SSR·프리렌더를 쓰면 “클라이언트에서만 정의”를 전제로 문서화해야 합니다.
8.5 버전 관리와 브레이킹 체인지
라이브러리로 배포할 때는 시맨틱 버저닝, CHANGELOG, 마이그레이션 가이드가 필수입니다. Custom Elements는 등록된 태그 이름이 계약이므로, 태그 변경은 거의 항상 브레이킹입니다.
9. 운영 시 자주 만나는 이슈
9.1 중복 등록과 버전 충돌
한 페이지에 Stencil 라이브러리가 두 번 로드되면 이상 동작할 수 있습니다. 번들러 설정·마이크로 프론트 경계에서 단일 복사본을 보장해야 합니다.
9.2 폼·접근성
Shadow 내부의 input과 외부 <label for> 연결이 어려울 수 있습니다. 라벨링 전략(슬롯으로 라벨을 받기, aria-* 정리)을 초기에 설계합니다.
9.3 SSR과 하이드레이션
호스트 앱이 SSR을 쓰면, 커스텀 엘리먼트가 클라이언트에서만 등록되는지, 하이드레이션 불일치가 없는지 확인합니다. 프레임워크별 통합 문서를 따르는 것이 안전합니다.
10. 정리
Stencil는 “표준 웹 컴포넌트를 현대적인 개발 경험으로 생산”하기 위한 컴파일러입니다. TSX·데코레이터로 익숙한 문법을 유지하고, Shadow DOM·슬롯·CSS 변수로 디자인 시스템을 캡슐화하며, Jest·E2E로 회귀를 막고, output targets로 React·Vue·Angular 소비자 경험을 다듬을 수 있습니다. 성공의 열쇠는 기술만이 아니라 공개 API(속성·이벤트·슬롯·테마 훅)를 얼마나 일관되게 정의하느냐에 있습니다.
배포 전에는 git add, git commit, git push 후 npm run deploy를 수행하는 것이 이 저장소의 워크플로입니다.