---
type: note
kind: Convention
tags: ['signals', 'reactivity', 'build-tooling', 'architecture']
status: release
ctime: 2026-06-21
mtime: 2026-06-21
generated: { by: claude/opus-4.8, at: 2026-06-28T14:49:32Z }
---

<img
  src="/svg/formisch_build_time_signal_swap.svg"
  alt="코어의 createSignal import가 빌드 타임에 프레임워크별 sibling 파일로 redirect되는 구조"
/>

코어는 `createSignal`·`batch`·`untrack`·`createId` 4개를 _이름으로만_ import하고 구현은 절대 고르지 않는다. 번들러가 타깃별 sibling(`index.solid.ts` … `index.qwik.ts`)으로 redirect → 런타임엔 각 프레임워크의 네이티브 signal이 그대로 들어가 추상화 세금이 0.

핵심은 **런타임 DI vs 빌드 타임 DI**. 런타임 어댑터는 간접층이 배포물에 잔존해 인라이닝·tree-shaking을 막는다. 빌드 타임 swap은 seam이 출력물에서 증발한다 — `createSignal`이 문자 그대로 `shallowRef`가 된다. (monomorphization vs virtual dispatch, `#ifdef` vs 런타임 `if`)

가능했던 이유: 계약을 `Signal<T>` = `get value` / `set value` 단 둘로 좁힌 것(narrowest interface). `.value` shape로 모인 Vue/Preact/Qwik은 **re-export 한 줄**로 충족된다.

날카로운 반례 — **React만 예외**. 네이티브 signal이 없어 ~100줄 pub/sub을 직접 구현한다. 빌드 swap이 어댑터를 _없앤_ 게 아니라, 런타임 방식이었다면 _모두가_ 졌을 비용을 React 번들 하나에 가둔 것.

대가: universal 번들 불가(타깃당 아티팩트 N개), 코어는 4-함수 계약 *밖*의 reactivity를 못 쓴다. **계약을 좁게 유지할 수 있느냐 = 패턴 성립 여부.**
