가장 먼저 튼튼한 기반을 다집니다. 이 프로젝트는 모든 구성 요소(서버, 앱, 플러그인)가 하나의 저장소에 있는 Monorepo 구조를 따릅니다.
은유: 이것은 **"온 가족의 짐을 하나의 거대한 트렁크에 담는 것"**과 같습니다. 각자 작은 가방을 들고 다니면 잃어버리기 쉽지만, 하나로 묶어 관리하면 필요한 물건을 찾기도, 통째로 옮기기도 훨씬 쉬워지죠.
이제 2회에서 준비한 '베이스캠프(openclaw-lab)' 폴더에 들어가 본격적으로 OpenClaw의 **'국가 헌법(규칙)'**을 세워보겠습니다.
- 준비 상태 확인: VSCode를 열고
openclaw-lab폴더가 열려 있는지 확인하세요. 터미널(Ctrl +`)도 준비되어 있어야 합니다. - 프로젝트 정체성 선포: 아래 명령어로 우리 집의 문패(
package.json)를 답니다.pnpm init
- 핵심 부품 로컬 조립: 우리 프로젝트에서만 쓸 수 있도록 부품들을 정식으로 주문합니다.
pnpm add hono @hono/node-server @whiskeysockets/baileys better-sqlite3 pnpm add -D typescript @types/node
많은 분들이 "일단 server.ts 파일 만들고 코드부터 치면 안 되나요?" 라고 묻습니다. 결론부터 말하면, OpenClaw(Moltbot) 같은 거대한 프로젝트에서는 불가능합니다. 그 이유를 아주 쉽게 설명해 드립니다.
- 일반적인 코딩 (HTML/JS): 맨땅에 텐트를 치는 것과 같습니다. 그냥 폴대(
index.html) 꽂고 천(script.js) 덮으면 집이 됩니다. 규칙이 없어도 됩니다. - OpenClaw(Moltbot) (TS/ESM): 100층짜리 마천루를 짓는 것과 같습니다. 땅을 파기 전에 "여기는 상업지구이며, 내진 설계 기준은 7.0이고, 철근은 KS규격을 써야 한다" 는 법적/행정적 허가(
tsconfig.json) 가 먼저 떨어져야 합니다. 허가증 없이 철근(import)을 하나라도 세우면 공사 중지 명령(Syntax Error)이 떨어집니다.
- 동네 축구 (Legacy JS Project): 심판도 없고 규칙도 대충입니다. "손만 안 쓰면 돼." 바로 공 차면 됩니다.
- 월드컵 (Modern TypeScript Project): 경기 시작 전에 "오프사이드 반칙 기준", "VAR 판독 여부", "교체 선수 숫자"가 룰북(
tsconfig.json)에 정의되어야 합니다. OpenClaw(Moltbot)은 월드컵 결승전급의 엄격한 규칙 하에 돌아가는 시스템입니다.
만약 설정 없이 바로 코드를 짠다면?
- 개발자: (자신있게)
import { Server } from 'http'; - 컴퓨터(Node.js): "잠깐,
import가 뭐죠? 저는 옛날 방식(require)밖에 모르는데요?" -> 에러 발생 (Crash) - 개발자: (당황하며) "어? 요즘 다 이거 쓰던데?"
- 컴퓨터: "그건
tsconfig.json이라는 번역기가 있을 때 얘기입니다. 저한테 미리 언질을 안 주셨잖아요."
결론: tsconfig.json을 먼저 만드는 것은 컴퓨터에게 "지금부터 우리는 최신 유행어(ESM)와 엄격한 문법(TypeScript)으로 대화할 거야. 준비해." 라고 선전포고를 하는 것입니다. 이 합의가 없으면 단 한 줄의 코드도 실행되지 않습니다.
---
### 1-3. 🛠️ [실전 가이드] tsconfig.json 설정: 새로운 나라의 헌법 제정
"Moltbot은 대형 프로젝트라서 이미 설정이 다 되어있지만, 맨땅에서 시작하는 나만의 작은 앱은 어떻게 시작하나요?"
1. 은유(Metaphor): 새로운 국가를 세울 때의 "헌법 제정"
백지에 그림을 그리는 것과 같습니다. 나라를 처음 세울 때, 우리 국민은 어떤 언어를 쓰고(Target), 주변 나라와는 어떻게 소통하며(Module), 범죄(Error)에는 얼마나 엄격할 것인지(Strict)를 정하는 **'국가 헌법'**을 만드는 과정이 바로 tsconfig.json 초기화입니다. 헌법이 모호하면 나라가 혼란에 빠지듯, 이 설정이 부실하면 개발 과정 내내 충돌이 일어납니다.
2. 개념(Concept): 초기화의 두 가지 경로 (자동 vs 실전)
TypeScript는 개발자가 처음부터 고생하지 않도록 **'표준 헌법 초안'**을 자동으로 생성해주는 도구(npx tsc --init)를 제공합니다. 초보자는 자동 생성된 초안에서 시작하고, 숙련자는 프로젝트의 목적(고성능 서버, 프론트엔드 등)에 맞춰 그 초안을 수정하여 사용합니다.
3. 코드(Code): 헌법의 탄생과 핵심 3조항
-
방법 A: 표준 초안 자동 생성 (권장) 연재 2회에서 사용하던 터미널 창을 그대로 사용하세요. 다음 명령어를 입력하면 수중한 주석과 함께 설정 파일이 생성됩니다.
npx tsc --init
-
방법 B: 실전 프로젝트 핵심 설정 (수동 수정) 자동 생성된 파일에서 아래 **'핵심 3개 조항'**만은 반드시 확인하고 수정해 주세요.
{ "compilerOptions": { "target": "ES2022", // [제1조] 사용할 언어의 문법 수준 (최신 사투리 인정) "module": "NodeNext", // [제2조] 소통 규약 (최신 스마트폰 규격 사용) "strict": true // [제3조] 법 집행의 엄격함 (무관용 원칙) } }
4. 배경 및 실전 가이드(Context & Guide): 왜 이 3가지인가?
- 왜
target: ES2022인가?: 과거에는 구식 브라우저를 위해 코드를 어렵게 번역해야 했습니다. 하지막 지금 우리가 만드는 서버(Node.js 22)는 똑똑합니다. 최신 문법을 그대로 써야 속도가 빠르고 코드가 깔끔해집니다. - 왜
module: NodeNext인가?: 자바스크립트 세계는 지금 '구식(CommonJS)'과 '신식(ESM)'이 교체되는 과도기입니다.NodeNext는 "우리는 신식을 쓰되, 구식도 알아듣겠다"는 가장 현명한 중재안입니다. - 왜
strict: true인가?: "대충 짜고 나중에 고치지 뭐"라는 생각은 개발자의 가장 큰 착각입니다. 처음부터 엄격하게 관리하는 것이, 나중에 버그를 찾느라 밤을 새우는 것보다 100배 경제적입니다.
Tip
요약: 작은 프로젝트라도 npx tsc --init으로 시작하고, Target(최신), Module(NodeNext), Strict(True) 이 세 가지만 기억하세요. 이것만으로도 여러분의 프로젝트는 구글이나 메타(Facebook) 부럽지 않은 튼튼한 기초를 갖추게 됩니다.
- 자바스크립트(JavaScript): 원래 브라우저라는 '수족관' 안에서만 살 수 있는 물고기였습니다.
- Node.js: 이 물고기에게 **'기계 슈트'**를 입혀 수족관 밖(서버)에서 파일을 만지고 DB를 관리하는 **'천하무적 일꾼'**으로 만든 것입니다.
2. 개념(Concept): TypeScript 컴파일러 설정 안내서
이 파일은 tsc(컴파일러)에게 "이 프로젝트의 소스 코드는 여기에 있고, 번역된 결과물은 저기에 저장하고, 번역할 때는 이런 규칙을 지켜줘!"라고 상세히 지시하는 메뉴얼입니다. 단순한 설정 파일이 아니라, 프로젝트의 **정체성(ESM vs CJS)**을 결정짓는 핵심 문서입니다.
은유로 보는 주요 조항들:
- outDir (dist): 번역이 끝난 문서(결과물)를 차곡차곡 쌓아두는 **'출고 창고'**입니다.
- rootDir (src): 번역가가 작업해야 할 원본 원고가 들어있는 **'원고 보관함'**입니다.
- esModuleInterop: 서로 다른 통신 규격(ESM과 CJS)을 쓰는 부품들이 대화할 수 있게 해주는 **'유니버설 어댑터'**입니다.
- skipLibCheck: "이미 검증된 외부 서적까지 일일이 오타 검사를 하느라 시간을 낭비하지 않겠다"는 '검수 생략' 조항입니다.
3. 코드(Code): 원본 tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "dist",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"noEmitOnError": true,
"allowSyntheticDefaultImports": true
},
"include": ["src/**/*"],
"exclude": [
"node_modules",
"dist",
"src/**/*.test.ts",
"src/**/*.test.tsx",
"src/**/test-helpers.ts"
]
}