네이티브 모듈 & N-API
이 문서가 답하는 질문: “왜 Electron에서는
npm install만으로 네이티브 모듈이 안 돌고,electron-rebuild가 필요한가?” 한 줄 답 (Pyramid Top): “Electron이 쓰는 Node ABI는 시스템 Node와 다르다 — N-API로 안 짜인 모듈은 Electron 버전이 바뀔 때마다 다시 컴파일해야 한다.”
Why — 왜 존재하는가
JavaScript만으로는 풀 수 없는 일이 있다. 빠른 SQLite, 이미지 변환(sharp), OS 키체인(keytar), 네이티브 알림. 이들은 C/C++로 짜여 Node API에 노출된다. 문제는 — Node 자체와 Electron이 쓰는 Node가 같은 바이너리가 아니다.
| 문제 | 이전 해법 | 한계 |
|---|---|---|
| Node 모듈 = JS만 | 순수 JS 라이브러리 | 성능·OS API 한계 |
| C++ 바인딩 (NaN) | 매 Node 메이저 업그레이드마다 컴파일 | 빌드 환경 의존 |
| ABI 안정 추상화 (N-API) | 한 번 빌드, 여러 Node 버전 호환 | 일부 저수준 기능 미지원 |
웹은 이 레이어가 없다. 브라우저가 노출하는 Web API만으로 살아야 한다 — File System Access·WebUSB 정도가 한계.
How — 어떻게 동작하는가
핵심: N-API(Node-API)는 Node의 C ABI를 안정 인터페이스로 추상화한 것이다. 한 번 빌드된 .node 바이너리가 Node 18, 20, 22에서 모두 동작한다. Electron 28(Node 18 기반)·30(Node 20)·31(Node 20) 등 ABI를 공유하면 그대로 쓰인다.
What — 구체 사양·수치·예시
electron-rebuild (NAN 기반 모듈용)
# 어떤 Electron 버전에 맞춰 rebuild할지
npx electron-rebuild
# 특정 모듈만
npx electron-rebuild -f -w better-sqlite3N-API 모듈 (prebuild 동봉)
npm install sharp
# 설치 단계에서 자기 OS/CPU에 맞는 prebuilt 바이너리를 다운로드package.json에서 외부화 — electron-builder
{
"build": {
"asarUnpack": [
"**/node_modules/better-sqlite3/**/*",
"**/node_modules/sharp/**/*"
]
}
}네이티브 모듈은
.node바이너리이므로 asar로 묶으면 dlopen이 실패한다. 반드시asarUnpack으로 빼야 한다.
흔한 네이티브 모듈 매트릭스
| 모듈 | 용도 | N-API? | 특이사항 |
|---|---|---|---|
better-sqlite3 | 동기 SQLite | NAN | electron-rebuild 필수 |
sharp | libvips 이미지 변환 | Yes | prebuild 자동 |
keytar | OS 키체인 | Yes | macOS Keychain·Win Credential Vault·libsecret |
node-pty | PTY (터미널) | NAN | electron-rebuild + node-gyp |
ffi-napi | C ABI 호출 | Yes | 최후의 수단 — 보안 위험 |
CI 빌드 (GitHub Actions)
- name: Install
run: |
npm ci
npx electron-rebuild
- name: Build
run: npm run buildMac arm64 + x64 dual build:
electron-builder --mac --arm64 --x64. 네이티브 모듈은 둘 다 prebuilt가 있어야 한다.
What-if — 잘못 쓰면
- 함정 1:
npm install후 앱이Cannot find module '*/build/Release/binding.node'→ 시스템 Node로 빌드됐다.electron-rebuild안 돌린 것. - 함정 2: Electron 메이저 업그레이드 후 모듈 죽음 → ABI 변경. NAN 모듈은 모든 메이저마다 rebuild. N-API 모듈은 대부분 호환.
- 함정 3: macOS arm64 빌드인데 x64 prebuilt만 받음 → 사용자가 “rosetta로 실행됨” 신고.
prebuild-install캐시 무시하고 강제 재빌드:npm rebuild --update-binary. - 함정 4: asar 안에
.node바이너리 → 런타임dlopen실패.asarUnpack으로 분리.
Insight — 흥미로운 이야기
“N-API는 io.js 분열의 유산이다”
2014년 Node가 io.js로 갈라졌을 때, 가장 큰 고통은 네이티브 모듈 생태계였다. 매번 Node 버전이 바뀔 때마다 C++ 코드가 부서졌다. Atomic, fork-cleanup, NAN(Native Abstraction for Node) 같은 라이브러리가 임시변통으로 등장했지만 — 본질적 해결은 Node 자체가 ABI 안정 인터페이스를 제공하는 것이었다.
그 결과가 N-API다(2017년 Node 8에서 실험적 등장). 한 번 빌드된
.node가 Node 메이저 업그레이드를 넘어 동작한다. Electron은 N-API의 최대 수혜자 — 자기 Node 버전을 자주 바꾸면서도 모듈 생태계와의 호환을 잃지 않을 수 있게 됐다. 오늘날sharp·keytar·bcrypt같은 핵심 모듈은 모두 N-API다.
요약
- 시스템 Node ≠ Electron Node. ABI가 다르면 재컴파일해야 한다.
- NAN 모듈은
electron-rebuild, N-API 모듈은 보통 그대로 동작. .node바이너리는 asar에 못 넣는다 —asarUnpack필수.- 멀티 아키텍처(x64+arm64) 배포 시 prebuilt가 모두 있어야 한다.