8.2 TypeScript 프로젝트 및 Zenoh 개발 환경 설정

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] 베이스캠프 패키징 전술

  1. 프로젝트 뼈대 잡기
    먼저 빈 디렉토리를 파고 패키지 생태계를 정의한다. 최신 기술 스택에서는 하드디스크 복사를 줄이고 의존성 트리를 가장 엄격하게 관리하는 pnpm 의 사용을 강력히 권장한다.

    mkdir zenoh-ts-app
    cd zenoh-ts-app
    pnpm init
    

2. **코어 바인딩 주입**
이클립스 재단에서 공식 배포하는 패키지를 잡아야 한다.
과거의 잔재인 `zenoh-js` 같은 유사 패키지를 설치하면 C++ 하위 호환성이 박살 난다.

   ```bash
   pnpm add @eclipse-zenoh/zenoh
  1. 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.jswebpack.config.jsexperiments 영역을 열어야 한다.

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)시키는 데 성공한 것이다.