처음 프로젝트를 시작할 때 설계한 폴더 구조는 다음과 같았다.
폴더 구조를 설계할 때는 아래와 같은 생각으로 만들었다.
legacy 폴더로 옮겼다crates 폴더에 배치packages 폴더에 배치 (역시 국룰)create 명령어로 폴더를 생성하지 않고 더미 폴더 구조만 만들어두었다더미를 만든 이유는 아래에서 자세히 후술하겠다.
프로젝트의 목적은 하나의 패키지 모듈로 react-native, node, web을 제공하는 것이다.
NAPI-RS와 Craby는 각각 기존 레포지토리나 모노레포 기준으로 합치는 것에 대한 세팅을 제공하지 않는다. (당연함)
그래서 우선 분리해두었다. 그리고 사용할 각각의 라이브러리에 대해 간단히 설명하면,
NAPI-RS는 러스트를 node 환경에서 실행하기 위해 해야하는 번거로운 세팅이나 자바스크립트와 러스트 간의 타입 매치 등을 제공해주고, 개발에만 집중할 수 있게 해주는 라이브러리다.
후술할 Craby도 마찬가지. 구조는 약간 다르지만
napi는 세팅이 복잡하기에 세팅을 도와주는 codegen이 있다.
napi new를 실행하면 다음과 같은 인터랙티브 메뉴가 나타난다:
napi new로 생성되는 프로젝트는 다음과 같은 특징이 있다:
추후 더 자세하게 쓸 예정이지만,
간단하게 설명한다면 윈도우와 맥 두 환경을 지원한다면, 배포 패키지는 총 3개가 된다:
@ohah/hwpjs # 메인 패키지
@ohah/hwpjs-win32-arm64-msvc # 윈도우용 바이너리
@ohah/hwpjs-darwin-arm64 # 맥용 바이너리덕분에 빌드 과정은 일반적인 자바스크립트 모듈에 비해 까다롭다.
napi-rs에서 artifacts 등으로 지원은 해주지만, 일반적인 배포 개념과는 다르다. 프로세스와는 다르다.
가장 거슬리는 부분이자 핵심은 Enable Github Actions CI? 옵션이다.
napi-rs는 기본적으로 배포를 로컬에서 돌리는 것을 가정하지 않고, GitHub Actions가 베이스이다.
러스트 바인딩 특성상 운영체제별로 별도의 빌드가 필요하고, 윈도우/맥/리눅스는 서로 크로스 플랫폼 빌드를 하기 어렵거나 지원하지 않기 때문이다.
하지만 가난한 개발자는 GitHub Actions로 사치를 부릴 생각이 없으므로, 로컬 빌드가 당연한 선택이었다.
윈도우와 맥 환경이 있으니 일단 이 두 개만 지원하는 것으로 결정했다.
Craby는 TurboModule을 통하여 rust 코드를 C(cxx)를 통해 바인딩하고, 그걸 각각의 네이티브 영역에서 실행시킬 수 있게 도와주는 React Native 라이브러리이다.
패키지 매니저에 종속적이지 않지만, 다음과 같은 특징이 있다:
napi-rs에서 문서상으로 WebAssembly 지원이 가능하다고 하니, 초기 세팅에는 고려하지 않았다.
어떻게 빌드되는지 확인한 후 추가하려고 생각했다.
관리를 위해서는 배포되는 패키지가 하나라면 마찬가지로 폴더도 하나인 게 좋다.
하지만, 괜히 합쳤다가 아예 둘 다 배포가 어려워지는 것보다는, 각각 일단 배포가 되는 것을 확인한 별도의 저장소를 합치는 게 더 편할 거라 생각했다.
이 레포지토리의 목적은 하나의 패키지 모듈로 react-native, node, web을 제공하는 것이다.
@ohah/hwpjs로 설치가 다 되어야 함그래서 일단 관심사 분리와 구조부터 잡는 것을 순서로 정했다.
가능하면 합치지만, 역량 부족으로 불가능하면 빌드 결과물이라도 잘 합치자는 생각이었다.
이제 고민해야 할 건 최근 핫해진 AI를 위한 세팅, 그리고 문서화에 갑자기 병적인 습관이 생긴 나를 위한 RSPress 세팅이었다.
사실 이 두 개는 어느 정도 겹치는 게 사실이다.
문서는 사람이 보는 자료긴 하지만, AI에게도 사전 컨텍스트를 공유할 수 있기 때문이다.
RSPress를 세팅해서 문서화 인프라를 구축했다.
그리고 agent.md에 간단한 레포지토리 설명, 목적, 사용 툴들을 적었다.
# HWPJS 프로젝트 - AI 에이전트 가이드
## 프로젝트 개요
HWPJS는 한글과컴퓨터의 한/글 문서 파일(.hwp)을 읽고 파싱하는 라이브러리입니다.
이 프로젝트는 Rust로 핵심 로직을 구현하고, React Native와 Node.js 환경에서 사용할 수 있도록 래퍼를 제공합니다.
### HWP 파일 스펙 문서
**중요**: `.hwp` 파일을 읽고 파싱할 때는 반드시 `hwp/document.md` 파일을 참조해야 합니다.
이 파일은 한글과컴퓨터에서 공개한 한/글 문서 파일 형식 5.0의 공식 스펙 문서입니다.
HWP 파일의 구조, 데이터 레코드, 스토리지 정보 등 모든 구현은 이 문서를 기준으로 해야 합니다.
## 프로젝트 구조
hwpjs/
├── crates/
│ └── hwp-core/ # 공유 Rust 라이브러리 (핵심 HWP 파싱 로직)
├── packages/
│ ├── react-native/ # React Native용 래퍼 (Craby 사용)
│ └── node/ # Node.js용 래퍼 (NAPI-RS 사용)
├── examples/ # 사용 예제 코드
├── docs/ # 문서 사이트 (Rspress)
└── legacy/ # 기존 JavaScript 구현
## 기술 스택
### 런타임/빌드
- **Bun**: 워크스페이스 관리 및 패키지 매니저
- **Rust**: 핵심 로직 구현 언어
### Rust 관련
- **공유 라이브러리**: `crates/hwp-core` - 환경 독립적인 HWP 파싱 로직
- **React Native**: Craby - TypeScript에서 Rust로의 바인딩
- **Node.js**: NAPI-RS - Node.js 네이티브 모듈 생성
- **린트/포맷**:
- `rustfmt`: 코드 포맷팅
- `clippy`: 린팅
### JavaScript/TypeScript 관련
- **린트**: oxlint - 빠른 JavaScript/TypeScript 린터
- **포맷터**: oxfmt - Prettier 호환 포맷터
- **테스트 (Node)**: Vitest
- **배포 (Node)**: tsdown
### 문서
- **Rspress**: 문서 사이트 생성
### 테스트
- **React Native**: Maestro (E2E 테스트)
- **Node.js**: Vitest (유닛 테스트)
- **Rust**: cargo test
### 환경 관리
- **mise**: 버전 관리 도구
- Rust: stable (LTS)
- Bun: latest (LTS)
- Node.js: 24.11.1 (LTS)
## 아키텍처 설계
### 공유 라이브러리 (`crates/hwp-core`)
- HWP 파일 파싱의 핵심 로직을 담당
- 파일 읽기를 트레이트로 추상화하여 환경별 구현 가능
- 환경 독립적인 비즈니스 로직만 포함
### 환경별 래퍼
#### `packages/react-native`
- Craby를 사용하여 React Native 환경에서 사용 가능
- `hwp-core`를 의존성으로 사용
- React Native 환경의 파일 읽기 구현
- Maestro를 사용한 E2E 테스트
#### `packages/node`
- NAPI-RS를 사용하여 Node.js 네이티브 모듈 생성
- `hwp-core`를 의존성으로 사용
- Node.js 환경의 파일 읽기 구현
- Vitest를 사용한 유닛 테스트
- tsdown을 사용한 배포
## 워크스페이스 설정
### Bun 워크스페이스
- `packages/*` 디렉토리를 워크스페이스로 관리
- 각 패키지는 독립적으로 빌드 및 배포 가능
### Cargo 워크스페이스
- `crates/*` 및 `packages/*/Cargo.toml`을 워크스페이스 멤버로 포함
- NAPI-RS는 Cargo 워크스페이스에서 정상 작동
- 공유 의존성을 효율적으로 관리
## 개발 가이드라인
### 코드 스타일
- Rust: `rustfmt` 및 `clippy` 사용
- JavaScript/TypeScript: `oxlint` 및 `oxfmt` 사용
- 모든 코드는 저장 시 자동 포맷팅
### 테스트
- Rust 테스트: `bun run test:rust`
- Node.js 테스트: `bun run test:node`
- E2E 테스트: `bun run test:e2e`
### 빌드
- 전체 빌드: `bun run build`
- 개별 패키지 빌드는 각 패키지 디렉토리에서 실행
### 린트 및 포맷
- 린트 검사: `bun run lint`
- 포맷 적용: `bun run format`
## 패키지별 상세
### `crates/hwp-core`
- **역할**: HWP 파일 파싱 핵심 로직
- **의존성**: 없음 (순수 Rust 라이브러리)
- **인터페이스**: 파일 읽기를 위한 트레이트 정의
### `packages/react-native`
- **역할**: React Native 환경에서 HWP 파일 읽기
- **의존성**: `hwp-core`
- **도구**: Craby
- **테스트**: Maestro
### `packages/node`
- **역할**: Node.js 환경에서 HWP 파일 읽기
- **의존성**: `hwp-core`
- **도구**: NAPI-RS
- **테스트**: Vitest
- **배포**: tsdown
### `examples/`
- **역할**: 각 환경별 사용 예제
- **내용**: 기본적인 사용법 예제 코드
### `docs/`
- **역할**: 프로젝트 문서 사이트
- **도구**: Rspress
- **위치**: packages 밖 (루트 레벨)
## 주의사항
1. 모든 패키지는 초기 설정 단계이며 "Hello World" 수준의 코드만 포함
2. 실제 Rust 구현은 이후 단계에서 진행
3. Craby와 NAPI-RS 프로젝트 초기화는 각각의 CLI 도구로 진행 예정
4. 파일 읽기 로직은 환경별로 다르게 구현되지만, 핵심 파싱 로직은 공유docs에는 스펙 문서를 따로 작성하여 한글과 컴퓨터 문서를 그대로 옮길 계획을 세웠다.
내가 읽기도 편해야 하지만, AI도 그 문서를 보는 게 코드 작업하기 편할 거란 생각이 들어서였다.
그래서 한글과 컴퓨터에서 제공해주는 PDF 파일을 마크다운으로 옮기는 작업을 진행했다.
이후 코어 개발 시 이 문서를 참고하면서 타입을 정의하고 파싱 로직을 만들 때 편해지지 않을까란 생각에서였다.
대충 큰 그림은 저렇게 그렸는데, 어느 부분부터 개발해야 할지 고민됐다.
초기 설계는 매우 중요한 부분이지만, 설계하다가 개발 못하고, 예쁘게 설계한다고 코드 까뒤집거나 하는 시간에 공들이다보면 정작 코드를 개발하지 못하는 문제가 발생한다.
위에 설명한 것처럼 각각의 멀티플랫폼 배포 세팅, 문서 세팅 등으로 이것저것 확인해야하고 모험해야하는 부분이 많은 경우 세팅만하다가 실제 개발해야하는 부분이 밀릴 수 있다는 생각이 들었다.
근데 이런 걸 다 검증하면서 개발하기엔 너무 귀찮다.
그리고 과거에 NAPI-RS 2.0 시절 개발해본 경험이 있었고, 개발에 들어가기 전 Craby에 이미 기여를 한 적이 있어서, 실제로 세팅은 해보진 않았지만 이 두 부분은 합치면서 생기는 트러블슈팅이 있으면 있었지, 빌드 자체나 배포가 불가능하진 않을 것 같았다.
또, 러스트를 좋아하니 그냥 코어부터 개발하자는 생각에 코어부터 개발하기로 마음 먹었다.
각 환경별로 권한이나 파일 불러오는 네이티브 함수 등은 다를 수 있다.
결과적으로 3개의 플랫폼(Node, React-Native, web)만을 위한 함수는 들어가야 한다.
공통점은 3개의 플랫폼 모두에서 Uint8Array(러스트에선 u8)로 데이터를 받을 수 있다는 것이다.
나중에 이쁘게 실행시키게 하려면 각각의 플랫폼에서 커스텀 함수 제공해줘야겠지만, 일단 코어부분은 u8로 받으면 될 것 같으니까 그냥 바로 진행하기로 했다.
어차피 프로덕션 레벨에서 고민하고 설계하는게 아니다.
검증보단 행동이고 행동으로 검증하는거다.
그냥 퇴근하면서, 출근하면서 가능할거 같다라는 상상 속의 결론을 짓고 불완전한 모노레포 상태에서 코어부터 냅다 개발하기 시작했다.