8.2 TypeScript 프로젝트 및 Zenoh 개발 환경 설정
“Zenoh 라이브러리 추가“는 npm install 한 줄로 끝나지 않는다.
Zenoh TS 바인딩은 내부에 Rust 웹어셈블리(WASM)와 시스템 네이티브 バイナリ(Binary)를 품고 있는 거대한 생태계 하이브리드 모듈이다. 이 모듈을 순수 자바스크립트 모듈처럼 번들러(Vite, Webpack)에 던져 넣으면 알 수 없는 컴파일 에러와 WASM 로딩 실패 창을 마주하게 될 것이다.
이 장에서는 TypeScript 컴파일러(tsconfig.json)의 어느 나사를 조여야 Zenoh의 강력한 타입을 100% 이끌어낼 수 있는지, 패키지 매니저의 선택은 성능에 어떤 영향을 미치는지, 그리고 모던 번들러들이 WASM 파일을 어떻게 소화하게 할 것인지에 대한 프론트엔드/백엔드 데브옵스 수준의 런북을 전개한다.
1. 패키지 매니저(npm, yarn, pnpm)를 이용한 의존성 설치
Zenoh 스웜(Swarm)에 합류하기 위한 최초의 관문이다.
1.0.1 [Runbook] 베이스캠프 패키징 전술
-
프로젝트 뼈대 잡기
먼저 빈 디렉토리를 파고 패키지 생태계를 정의한다. 최신 기술 스택에서는 하드디스크 복사를 줄이고 의존성 트리를 가장 엄격하게 관리하는pnpm의 사용을 강력히 권장한다.mkdir zenoh-ts-app cd zenoh-ts-app pnpm init
2. **코어 바인딩 주입**
이클립스 재단에서 공식 배포하는 패키지를 잡아야 한다.
과거의 잔재인 `zenoh-js` 같은 유사 패키지를 설치하면 C++ 하위 호환성이 박살 난다.
```bash
pnpm add @eclipse-zenoh/zenoh
-
TypeScript 방어구 장착
당연히 정적 타입(Static Type) 검사를 위한 툴체인이 필요하다.pnpm add -D typescript @types/node ts-node # tsconfig.json 초기화 pnpm tsc --init
이 설치 과정 중 Node 버전을 주의해야 한다. Zenoh 내부에 내장된 N-API (Rust 네이티브 바인딩) 컴포넌트는 Node.js `v18.0.0` 이하거나 운영체제가 특이한 ARM 장비일 경우(ex: 라즈베리 파이 구형), `node-gyp` 를 통해 소스 코드부터 Rust 코어(Cargo)를 실시간 컴파일하려고 들 것이다. 터미널에 붉은 에러가 쏟아진다면, **"내 장비에 Rust 툴체인(rustup)이 깔려 있는가?"** 부터 확인하라.
## 2. `tsconfig.json` 컴파일러 옵션 최적화 및 모듈 시스템 설정
TypeScript 컴파일러(Tsc)는 Zenoh가 던져주는 깊고 복잡한 타입 체인(Type Chain)을 해석해야 한다. 기본 설정(`strict: false`)으로 둔다면, TypeScript를 쓰는 의미가 0%로 수렴한다.
#### 2.0.1 [Runbook] 데이터 타입-세이프(Type-Safe) 방어벽 구축 설정
`tsconfig.json` 을 열고 다음 옵션들을 반드시 타협 없이(Must-have) 강제하라.
```json
{
"compilerOptions": {
// 1. 모던 모듈 스펙터
// Zenoh 패키지는 CommonJS(require)와 ESModules(import)를 이중 지원하지만,
// 대세인 ESModule로 강제해야 트리쉐이킹(Tree Shaking) 최적화가 먹힌다.
"module": "ESNext",
"moduleResolution": "Node",
"target": "ES2022",
// 2. 가혹한 런타임 방어막 세팅
// Zenoh에서 리턴하는 session.get() 의 비동기 제너레이터를 안전하게 뽑아 쓰기 위함.
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
// 3. 비동기 이터레이터 지원 (핵심)
// for await (const reply of session.get(...)) 문법을 쓰려면
// lib 에 반드시 DOM 뿐 아니라 AsyncIterable 을 지원하는 라이브러리를 박아야 한다.
"lib": ["ES2022", "DOM", "DOM.Iterable"],
// 4. WASM 브릿지 모듈 임포트 호환성
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"]
}
이 규칙을 적용하지 않으면 for await 쿼리문에서 “AsyncIterator 가 Symbol.asyncIterator를 구현하지 않았습니다” 라는 치명적인 컴파일 에러가 발생하여 첫 발짝조차 떼지 못하게 된다. 컴파일러에게 비동기 세계의 길을 닦아주는 과정이다.
3. Webpack, Vite, Rollup 등 모던 번들러와의 연동 전략
브라우저 안에서 구동되는 프론트엔드 React/Vue 앱에 Zenoh를 이식할 때 마주하는 가장 거대한 벽은 WebAssembly(WASM) 로더(Loader) 세팅이다. 번들러(Vite, Webpack)들은 .js 는 잘 읽지만, 내부에 숨겨진 수 메가바이트짜리 .wasm 바이너리를 클라이언트에게 서빙하는 법을 기본적으로 모르기 때문이다.
3.0.1 [Runbook] WASM 번들러 인젝션 플러그인 설정 가이드
1. Vite 방어구 설정 (가장 권장됨)
Vite는 WASM을 네이티브로 제법 잘 지원하지만, 최신 Zenoh 바인딩에 맞게 옵션을 약간 튜닝해야 한다. vite.config.ts 를 열어라.
import { defineConfig } from 'vite'
import wasm from "vite-plugin-wasm";
import topLevelAwait from "vite-plugin-top-level-await";
export default defineConfig({
plugins: [
// WASM 모듈을 알아서 로드하게 돕는다
wasm(),
// 웹어셈블리 초기화가 비동기이므로 Top-level await 가 필수적이다.
topLevelAwait()
],
optimizeDeps: {
// Vite가 Zenoh 모듈을 잘못 전처리하지 않도록 강제로 배제하라.
exclude: ['@eclipse-zenoh/zenoh']
}
})
2. Webpack5 방어구 설정 (Next.js 등)
기존 엔터프라이즈 환경에서 쓴다면 next.config.js 나 webpack.config.js의 experiments 영역을 열어야 한다.
module.exports = {
// ... 생략
experiments: {
asyncWebAssembly: true,
topLevelAwait: true,
},
module: {
rules: [
{
test: /\.wasm$/,
type: 'webassembly/async', // Webpack 5 전용 WASM 로더 활성화
},
],
},
};
이 번들러 세팅을 1자라도 틀리면 프론트엔드 프로젝트 부팅 시 브라우저 콘솔창에 “Wasm magic word not found” 나 “Cannot use ‘import’ statement outside a module” 이라는 피 말리는 에러 메시지의 폭격 속으로 걸어 들어가야 한다.
4. 첫 번째 TypeScript 기반 Zenoh 헬로월드(Hello World) 작성
길었던 설정과 튜닝을 마쳤으니 전투에 임할 무기를 시험 발사해 볼 시간이다. TS 환경이 Node.js (서버)라고 가정하고 가벼운 Ping-Pong 코드를 띄운다.
4.0.1 [Runbook] TS - 헬로 스웜(Hello Swarm) 테스트
프로젝트 루트에 src/index.ts 를 생성한다. 최신 Node.js 환경에서 작동하는 가장 우아하고 규격화된 TS 코드다.
// 반드시 ES Module 문법 사용
import * as zenoh from "@eclipse-zenoh/zenoh";
async function run() {
console.log("🚀 TypeScript Zenoh 스크립트 시동 중...");
// 1. 네이티브 백엔드 세션 연결 (기본 스카우팅 모드)
// await를 통한 비동기 장착! C/Rust 코어가 뒤에서 라우터를 탐색한다.
const conf = zenoh.Config.default();
console.log("Config 획득. 세션 개방 타격 개시.");
// 이 구문이 통과해야 WASM/Native C 바인딩이 성공적으로 돌아가는 것이다.
const session = await zenoh.open(conf);
console.log(`✅ 세션 부팅 완료 (ZID: ${session.info().zid()})`);
// 2. 구독 (Sub) 개통
const keyExpr = "demo/ts/hello";
const sub = await session.declareSubscriber(keyExpr, (sample: zenoh.Sample) => {
// [수동 캐스팅] Rust 코어가 던져준 바이트를 TS의 string 으로 강제 변환
const payloadStr = Buffer.from(sample.payload).toString('utf-8');
console.log(`[>> 수신 콜백 트리거] 수신 데이터: ${payloadStr}`);
});
console.log(`🔗 구독망 연결 완료: ${keyExpr}`);
// 3. 발사 (Pub) 준비 및 송출
const pub = await session.declarePublisher(keyExpr);
console.log("🔫 발사대 거치 완료. 1초 뒤 발사합니다.");
await new Promise(resolve => setTimeout(resolve, 1000));
// 바이트 전송 (TS String -> Buffer 변환)
const outData = Buffer.from("Hello from TypeScript Engine!");
await pub.put(outData);
console.log("발사 완료! 콜백을 기다립니다...");
// 4. 안전망: 조금 대기 후 자원 정리 (매우 중요)
await new Promise(resolve => setTimeout(resolve, 500));
sub.undeclare();
pub.undeclare();
await session.close();
console.log("🛑 스크립트 클린 종료");
}
run().catch(e => {
console.error("시스템 크래시:", e);
process.exit(1);
});
터미널에서 pnpm ts-node src/index.ts 를 쳤을 때 수신 콜백(Subscriber Callback)이 무사히 화면에 출력된다면, 축하한다. 이제 당신은 세계 최고 속도의 C++ 분산 라우터와 TypeScript의 V8 엔진을 물리적으로 동기화(Sync)시키는 데 성공한 것이다.