⚡ Electron4. 네이티브 통합네이티브 모듈 & N-API

네이티브 모듈 & 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-sqlite3

N-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동기 SQLiteNANelectron-rebuild 필수
sharplibvips 이미지 변환Yesprebuild 자동
keytarOS 키체인YesmacOS Keychain·Win Credential Vault·libsecret
node-ptyPTY (터미널)NANelectron-rebuild + node-gyp
ffi-napiC ABI 호출Yes최후의 수단 — 보안 위험

CI 빌드 (GitHub Actions)

- name: Install
  run: |
    npm ci
    npx electron-rebuild
- name: Build
  run: npm run build

Mac 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가 모두 있어야 한다.