From an empty folder to a working component in about five minutes.
빈 폴더에서 동작하는 컴포넌트까지, 대략 5분.
npm run build.검색엔진이 읽는 회사 홈페이지,npm run build한 줄.A small company homepage, ready to edit. Copy it, run it, change the words. Static files only — no server to run.바로 고쳐 쓸 수 있는 작은 회사 홈페이지예요. 받아서, 돌리고, 문구만 바꾸세요. 정적 파일이라 서버가 필요 없어요.
npm run build→ HTML per page + sitemap.xml + robots.txtnpm run build→ 페이지별 HTML + sitemap.xml + robots.txtnpx degit dooboostore-develop/dooboostore-develop.github.io/templates/homepage-starter my-sitecdmy-site
npm install
npm run dev# http://localhost:5173A Node server renders the same components, ships the data with the page, and answers the browser through one shared interface. Open/usersand watch what happens.Node 서버가 같은 컴포넌트를 그리고, 데이터를 페이지에 실어 보내고, 공유 인터페이스 하나로 브라우저 호출에 답해요./users를 열어 보세요.
src/services— HTTP proxy in front, real code on the serversrc/services의 인터페이스 하나 — 프론트는 HTTP 프록시, 서버는 진짜 구현fetchcode — components just callgetUsers()API 라우트도fetch코드도 없음 — 컴포넌트는getUsers()를 부를 뿐npm run devrebuilds the front and restarts the server on savenpm run dev하나로 저장하면 프론트 다시 빌드 + 서버 재시작npx degit dooboostore-develop/dooboostore-develop.github.io/templates/ssr-starter my-appcdmy-app
npm install
npm run dev# http://localhost:3000The same thing, one piece at a time.같은 걸 한 조각씩.
simple-boot,coreandcore-webcome along as dependencies. Add@dooboostore/simple-bootyourself only when your code imports it (e.g.@Sim,@inject).simple-boot·core·core-web은 의존성으로 같이 따라옵니다. 내 코드에서 직접 import 할 때(@Sim,@inject등)만@dooboostore/simple-boot를 따로 설치하세요.
npm i @dooboostore/simple-web-component reflect-metadataemitDecoratorMetadatalets the container inject by type alone. Plain esbuild/Vite don't emit it — there, name what you want with@inject(SYMBOL)(the starters do), or run tsc in the pipeline (ts-loader, esbuild-plugin-tsc).emitDecoratorMetadata가 있으면 타입만 보고 주입됩니다. esbuild/Vite 단독은 이걸 안 만들어요 — 거기선@inject(SYMBOL)로 이름을 찍어 주입하거나(스타터들이 이 방식), 파이프라인에 tsc를 끼우세요(ts-loader, esbuild-plugin-tsc).
{"compilerOptions":{"experimentalDecorators":true,"emitDecoratorMetadata":true}}The method returns; the decorators decide where it goes. That's the whole idea.메서드는 반환만, 둘 곳은 데코레이터가. 이게 전부예요.
import{ elementDefine, onConnectedBodyShadow, event, innerHtml }from'@dooboostore/simple-web-component';exportdefault(w:Window) => {consttag ='hello-card';if(w.customElements.get(tag))returntag;@elementDefine(tag, {window: w })classHelloCardextendsw.HTMLElement{privatecount =0;@onConnectedBodyShadowrender() {return`<p>Hello,${this.getAttribute('name')}!</p>
<button>clicked <b>0</b> times</button>`;
}@eventClick('button')@innerHtml('b')onClick() {returnString(++this.count);
}
}returntag;
};<body is="swc-app-body">becomes the app host. Safari needs theis="…"polyfill (first script).<body is="swc-app-body">가 앱 호스트가 됩니다. 사파리는is="…"폴리필이 필요해요(첫 번째 스크립트).
<head><scriptsrc="https://unpkg.com/@ungap/custom-elements"></script><scripttype="module"src="./main.ts"></script></head><bodyid="app"is="swc-app-body"><hello-cardname="World"></hello-card></body>Register the app hosts, then connect: give it a window, a container and the components to define.앱 호스트를 등록하고 connect: window, 컨테이너, 정의할 컴포넌트를 넘기면 끝.
import'reflect-metadata';import{ defineSwcAppAll }from'@dooboostore/simple-web-component';importhelloCardfrom'./hello-card';awaitdefineSwcAppAll(window);document.querySelector<any>('#app').connect({window,container:Symbol('app'),onStartedLazyDefineComponent: [helloCard],
});The questions you were about to ask.막 물어보려던 그 질문들.
(w: Window) => { … }?왜 컴포넌트마다(w: Window) => { … }로 감싸나요?experimentalDecorators+reflect-metadata. Moving to standard decorators isa goal, and the aim is to keep your call sites as they are.지금은 TypeScript의experimentalDecorators+reflect-metadata위에서 돕니다. 표준 데코레이터로 옮기는 게목표이고, 호출하는 코드는 그대로 두는 걸 지향해요.is="swc-app-body"?is="swc-app-body"를 꼭 써야 하나요?swc-app-divand friends exist, andSwcAppMixinturns your own element into an app host (nois, no Safari polyfill).아니요 —swc-app-div같은 변형도 있고,SwcAppMixin으로 내 엘리먼트를 앱 호스트로 만들 수도 있어요(is도 사파리 폴리필도 불필요).Where it shines, and where to think twice.잘 맞는 곳, 그리고 한 번 더 생각할 곳.
Honest, including where we lose.솔직하게, 지는 칸까지.
| simple-web-component | Lit | React + Next | |
|---|---|---|---|
| What you ship결과물 | ✓standard custom elements✓표준 커스텀 엘리먼트 | ✓standard custom elements✓표준 커스텀 엘리먼트 | React componentsReact 컴포넌트 |
| Virtual DOM가상 DOM | none없음 | none없음 | yes있음 |
| How the DOM updatesDOM 갱신 방식 | a method returns → decorators write to targets메서드 반환 → 데코레이터가 대상에 씀 | reactive props → template re-render리액티브 프로퍼티 → 템플릿 재렌더 | state → re-render → diff상태 → 재렌더 → 비교 |
| After SSR, the browser…SSR 뒤 브라우저는… | ✓keeps the DOM, wires behavior✓DOM 유지, 행위만 연결 | hydrates templates (labs)템플릿 하이드레이트(labs) | re-runs components to hydrate컴포넌트를 다시 돌려 하이드레이트 |
| DI (dependency injection) containerDI(의존성 주입) 컨테이너 | ✓built in✓내장 | — (context protocol)— (context 프로토콜) | — (context)— (context) |
| Calling the server서버 호출 | ✓interface + Symbol (Symbol RPC)✓인터페이스 + Symbol (Symbol RPC) | —— | Server ActionsServer Actions |
| Decorators데코레이터 | TypeScript experimentalTypeScript experimental | standard + experimental표준 + experimental | —— |
| Ecosystem생태계 | small — you'd be early작음 — 초기 멤버가 됩니다 | large큼 | huge거대 |
Small ecosystem, big leverage: issues get read by the people who wrote the code.Say hi on GitHub.생태계는 작지만, 대신 이슈를 코드 쓴 사람이 직접 읽습니다.GitHub에서 인사하기.