a TypeScript framework on web standards
웹 표준 위의 TypeScript 프레임워크
Build company sites and web apps out of standard Web Components. Buttons that update the screen, data that loads itself, pages search engines can read —each in a few lines.
표준 Web Components로 회사 홈페이지와 웹앱을 만듭니다. 누르면 바뀌는 화면, 알아서 불러오는 데이터, 검색엔진이 읽는 페이지까지 —각각 몇 줄이면 돼요.
privateclicks =0;@eventClick('#click-btn')// when: clicked@innerHtml('.click-out')// where: output divonClick() {this.clicks++;return`${this.clicks}clicks so far.`;
}Full source: demo-click.ts전체 소스 보기: demo-click.ts@eventInput('.q-in')// every key@innerHtml('.q-keys')countKey() {return++this.keys; }@eventInput('.q-in', {debounceTime:300,// after a pause})@innerHtml('.q-runs', {valueKey:'runs'})@innerHtml('.q-out', {valueKey:'hits'})search(@matchedElementinput:HTMLInputElement) {constq = input.value;consthits =NAMES.filter(n=>n.includes(q));return{runs: ++this.runs,hits:chips(hits) };
}Full source: demo-search.ts전체 소스 보기: demo-search.ts@eventClick('#fetch-btn')// shown while the request is pending@innerHtml('.fetch-out', {fallback:() =>'Loading…'})// click again → the previous request is aborted@fetch({url:POST_URL,abortPrevious:true})load(@fetchPromisep:Promise<Post>) {// the request, as a Promisereturnp.then(post=>`<b>${post.title}</b>`,() =>'Failed — but no try/catch.');
}Full source: demo-fetch.ts전체 소스 보기: demo-fetch.ts@fetchGet(POST_URL)declarepost?:Promise<Post>;// read → request once → remembered@eventClick('#read-btn')@innerHtml('.mem-out')read() {returnthis.post?.then(p=>p.title);
}@eventClick('#reload-btn')reload() {this.post=undefined;// forget → the next read requests again}Full source: demo-remember.ts전체 소스 보기: demo-remember.ts@eventPointermove('.chase')aim(@eventObjecte:PointerEvent) {constr = (e.currentTargetasHTMLElement).getBoundingClientRect();this.tx= e.clientX- r.left;this.ty= e.clientY- r.top;
}@requestAnimationFrame// every frame@updateStyle('.chase-dot')follow() {this.x+= (this.tx-this.x) *0.12;// ease towardthis.y+= (this.ty-this.y) *0.12;return{transform:`translate(${this.x}px,${this.y}px)`};
}Full source: demo-chase.ts전체 소스 보기: demo-chase.ts@setInterval(1000)@innerHtml('.clock-time', {valueKey:'time'})@updateStyle('.clock-ring', {valueKey:'ring'})tick() {constnow =newDate();constdeg = now.getSeconds() *6;return{time: now.toTimeString().slice(0,8),// 19:31:33ring: {background:`conic-gradient(#FF385C${deg}deg, #1E1E1E 0)`},
};
}// no clearInterval — it stops when the element leavesFull source: demo-clock.ts전체 소스 보기: demo-clock.ts@eventInput('.text-in')@publishSwcAppMessage('home:echo')// return = messagepublish(@matchedElementinput:HTMLInputElement) {returninput.value;
}// Behavior: late joiners get the last one@subscribeSwcAppMessageBehavior('home:echo')@innerHtml('.echo-out')show(@swcAppMessagemsg:SwcAppMessage<string>) {returnmsg.data||'Waiting for a message…';
}Full source: demo-bus.ts전체 소스 보기: demo-bus.tsprivateseen =newSet<Element>();@intersectionObserver('.reveal-card', {threshold:0.6,
})@updateClass('.reveal-card', {root:'auto',valueKey:'cls',
})@innerHtml('.reveal-count', {valueKey:'count'})onReveal(_els:unknown,entries:IntersectionObserverEntry[]) {for(consteofentries)
e.isIntersecting?this.seen.add(e.target)
:this.seen.delete(e.target);return{cls: {on:(el:Element) =>this.seen.has(el) },count:`${this.seen.size}/ 8 in view`,
};
}Full source: demo-reveal.ts전체 소스 보기: demo-reveal.ts@intersectionObserver('.feed-end')// the bottom marker@insertBeforeEnd('.feed', {valueKey:'items'})@innerHtml('.feed-count', {valueKey:'count'})more(_els:unknown,entries:IntersectionObserverEntry[]) {constseen = entries.some(e=>e.isIntersecting);if(seen &&this.loaded<100)this.loaded+=10;return{items: seen ?rows(this.loaded-10,10) :undefined,count:`${this.loaded}/ 100`,
};
}Full source: demo-feed.ts전체 소스 보기: demo-feed.ts@eventClick('#buy-btn', {filter:(_e, { currentThis: c }) =>c.agreed(),before:async() => {awaitsleep(900);// pretend workreturn'TOFU-'+randomDigits(4);// → injected},finally:(_e, { currentThis: c }) =>c.unlock(),
})@innerHtml('.buy-out')onBuy(@eventBeforeReturnorderId:string) {return`✓ Order${orderId}placed`;
}Full source: demo-hooks.ts전체 소스 보기: demo-hooks.ts@eventClickDelegate('.pick')@innerHtml('.inject-out')onPick(@helperHostSeth:HelperHostSet,// $this, $q …@eventObjecte:MouseEvent,// DOM event@matchedElementitem:HTMLElement,// the .pick) {consthost = h.$this.localName;return`${item.dataset.name}·${e.type}·${host}`;
}Full source: demo-inject.ts전체 소스 보기: demo-inject.ts// shadow root resolved for you@query('.chips')declarechips:HTMLElement;@mutationObserver('.chips', {childList:true})@resizeObserver('.chips')// same box@innerHtml('.chip-out')onChips() {const{ children, offsetHeight } =this.chips;return`${children.length}chips ·${offsetHeight}px`;
}@eventInput('.rs-range')// slider → box width@updateStyle('.rs-box')onRange(@matchedElementr:HTMLInputElement) {return{width:`${r.value}px`};
}@resizeObserver('.rs-box')// any size change@innerHtml('.rs-out')onBoxResize([box]:HTMLElement[]) {return`${box.offsetWidth}×${box.offsetHeight}`;
}Full source: demo-observers.ts전체 소스 보기: demo-observers.ts@elementDefine('leak-probe', {window: w })classLeakProbeextendsw.HTMLElement{@setInterval(100)tick() { stats.ticks++; }@eventWindow('swc-leak-ping')pong() { stats.answers++; }
}// the button: mount 1,000 → ping (all answer)// → remove all → reset → ping again → wait → count// no removeEventListener, no clearInterval anywhereFull source: demo-leak.ts전체 소스 보기: demo-leak.tsYou won't find thousands of plugins here yet. What you will find: issues that land on the desk of the person who wrote the code, an API that is still shaped by the people using it, and a stack you can read top to bottom. Issues in Korean or English are both welcome.
아직 수천 개의 플러그인은 없습니다. 대신 이런 게 있어요: 이슈가 코드를 쓴 사람 책상에 바로 떨어지고, API가 아직 쓰는 사람들 손으로 다듬어지고, 스택을 위에서 아래까지 읽을 수 있습니다.이슈는 한국어로 남겨도 돼요.
experimentalDecoratorsflagTC39 표준 데코레이터 —experimentalDecorators없이Decorator-stack Web Components. Methods return values; decorators decide where they go.
데코레이터 스택 Web Components. 메서드는 값을 반환하고, 데코레이터가 둘 곳을 정합니다.
Server-side rendering that doesn't render twice, and RPC (calling server functions like local ones) by Symbol.
두 번 그리지 않는 SSR(서버 사이드 렌더링), 그리고 Symbol로 오가는 RPC(서버 함수를 내 함수처럼 부르기).
Fourteen packages, one philosophy. The full lineup at a glance.
열네 개 패키지, 하나의 철학. 전체 라인업을 한눈에.