Skip to content

Commit 6097dc9

Browse files
committed
docs(n-api): Node-API 문서 내용 보강
1 parent 4e022be commit 6097dc9

File tree

3 files changed

+103
-59
lines changed

3 files changed

+103
-59
lines changed

_posts/js/n-api/build-addon.webp

22.4 KB
Loading

_posts/js/n-api/docs.mdx

Lines changed: 103 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ date: '2025-03-16T12:37:30.218Z'
1010

1111
> 이미지 출처: https://medium.com/ai-innovation/a-guide-for-javascript-developers-to-build-c-add-ons-with-node-addon-api-28c84a0c0cb1
1212
13-
최근 Rust를 공부 하면서 Rust 컴파일 파일이 Node.js Addon에 어떻게 연결되는지 궁금했다. 따라서 이번 기회에 N-API에 대해 "얕게" 알아보려고 한다.
13+
최근 Rust를 공부하면서 Rust 컴파일 파일이 Node.js Addon에 어떻게 연결되는지 궁금했다. 따라서 이번 기회에 N-API에 대해 "얕게" 알아보려고 한다.
1414

1515
이 글은 다음과 같은 내용을 다룬다.
1616

@@ -26,59 +26,96 @@ date: '2025-03-16T12:37:30.218Z'
2626

2727
Node-API는 [Node.js v8에서 Stable로 소개](https://nodejs.org/docs/latest-v8.x/api/n-api.html#n_api_n_api)되었다.
2828

29-
Node-API는 Node.js 네이티브 애드온(C/C++)을 개발하기 위한 API이다. 기본 JavaScript 런타임(예: V8)과 독립적이며 Node.js 자체의 일부로 유지 관리되고 있다.
29+
Node-API는 Node.js 네이티브 Addon(C/C++)을 개발하기 위한 API이다. 기본 JavaScript 런타임(예: V8)과 독립적이며 Node.js 자체의 일부로 유지 관리되고 있다.
3030

3131
이 API는 Node.js의 모든 버전에서 [ABI 안정적](https://nodejs.org/en/learn/modules/abi-stability)이다.
3232

3333
> ABI(Application Binary Interface): 응용 프로그램이 컴파일된 이후에도 호환성을 유지하기 위한 인터페이스.
3434
35-
기본 JavaScript 엔진의 변경 사항으로부터 애드온을 보호하고 특정 버전에 대해 컴파일된 모듈을 재컴파일 없이 이후 버전의 Node.js에서 실행할 수 있도록 하는 것이 목적이다.
35+
기본 JavaScript 엔진의 변경 사항으로부터 Addon을 보호하고 특정 버전에 대해 컴파일된 모듈을 재컴파일 없이 이후 버전의 Node.js에서 실행할 수 있도록 하는 것이 목적이다.
3636

3737
## Node-API의 장점
3838

3939
![addon chart](addon.webp 'l')
4040

4141
Node-API가 탄생한 배경을 이해하면 Node-API의 장점을 이해할 수 있다.
4242

43-
Node.js는 JavaScript를 브라우저 외부에서 실행시킬 수 있는 앱이다. 따라서 다양한 기능을 충족시키기 위해 C/C++ 네이티브 애드온을 통해 Node.js를 확장시키고자 하는 노력을 기울여 왔다.
43+
Node.js는 JavaScript를 브라우저 외부에서 실행시킬 수 있는 앱이다. 따라서 다양한 기능을 충족시키기 위해 C/C++ 네이티브 Addon을 통해 Node.js를 확장시키고자 하는 노력을 기울여 왔다.
4444

45-
이러한 노력은 유지보수의 어려움이 있었다. 내부적으로 의존하는 V8 엔진 및 Libuv 등은 지속적으로 업데이트되며 새로운 기능이 추가되거나 변경된다. 이러한 변화는 C/C++로 개발된 애드온의 ABI 안정성을 보장하기 어렵게 만들었다.
45+
이러한 노력은 유지보수의 어려움이 있었다. 내부적으로 의존하는 V8 엔진 및 Libuv 등은 지속적으로 업데이트되며 새로운 기능이 추가되거나 변경된다. 이러한 변화는 C/C++로 개발된 Addon의 ABI 안정성을 보장하기 어렵게 만들었다.
4646

47-
> 애드온 C++에서 V8에 직접 접근한 경우 Node.js는 안정성을 보장할 방법이 없기 때문.
47+
> Addon C++에서 V8에 직접 접근한 경우 Node.js는 안정성을 보장할 방법이 없기 때문.
4848
49-
또한 C/C++ 애드온을 개발하고자 하는 개발자는 인터페이스 가이드를 받지 못하기 때문에 숙련된 개발자가 아니면 애드온을 적용하기가 어려웠다.
49+
또한 C/C++ Addon을 개발하고자 하는 개발자는 인터페이스 가이드를 받지 못하기 때문에 숙련된 개발자가 아니면 Addon을 적용하기가 어려웠다.
5050

5151
이러한 문제를 해결하기 위해 Node-API가 등장하게 되었다.
5252

5353
### 1. ABI 안정성
5454

55-
Node.js의 업데이트로 인해 애드온이 깨지는 문제를 해결한다.
55+
Node.js의 업데이트(내부 바인딩 디펜던시 라이브러리; V8 버전업 등)로 인해 Addon이 깨지는 문제를 해결한다.
5656

57-
### 2. 개발자 친화적
57+
### 2. 인터페이스 제공
5858

5959
Node-API는 C/C++ 개발자가 아니더라도 사용할 수 있도록 설계되었다.
6060

61-
주요 인터페이스를 제공하고 이를 통해 Node.js와 상호작용할 수 있도록 한다.
61+
주요 인터페이스를 제공하고 이를 통해 Node.js와 상호작용 할 수 있도록 한다.
62+
63+
> 인터페이스는 아래의 [Rust와 Node-API를 사용한 Node.js Addon 개발](#rust와-node-api를-사용한-nodejs-addon-개발)에서 간단하게 소개한다.
6264
6365
### 3. 최적화
6466

65-
기존 C/C++ 애드온은 GC 메모리 관리를 직접해야 했으며 멀티 스레딩도 직접 구현해야 했다. 이는 애드온 개발자에게 추가적인 부담을 준다.
67+
C/C++ Addon을 특별한 바인딩 과정 없이 적용할 경우 직접 메모리 관리를 직접 해야 했으며 멀티 스레딩도 스스로 구현해야 했다.
68+
69+
이는 Node.js와 V8, Libuv 등 다양한 바인딩 라이브러리들의 관계를 정확하게 알아야 하는 부담으로 다가왔다.
70+
71+
Node-API는 인터페이스를 통해 최적화해 주기 때문에 Addon 개발자에게 추가적인 부담을 주지 않는다.
6672

6773
## Rust와 Node-API를 사용한 Node.js Addon 개발
6874

69-
바로 실전으로 이해해보자.
75+
이제 Addon 개발 과정을 따라가면서 실체를 조금 더 자세히 이해해 보자.
76+
77+
개발 과정은 다음과 같다.
7078

71-
Rust로 컴파일된 목적 파일을 Node.js Addon에 연결하여 JavaScript 파일에서 사용하는 과정을 다루고자 한다.
79+
1. Addon 개발 환경 설정
80+
2. Rust 파일을 목적 파일로 컴파일
81+
3. 목적 파일을 Node.js Addon에 연결
82+
4. JavaScript 파일에서 사용
7283

7384
### 1. Node-gyp 설치
7485

7586
```sh
7687
$ npm i -g node-gyp
7788
```
7889

79-
Node-API 에서 제공하는 헤더, C 코드 등이 포함되어 있는 [node-gyp](https://github.com/nodejs/node-gyp)를 설치한다.
90+
Node-API에서 제공하는 헤더, C 코드 등이 포함된 [node-gyp](https://github.com/nodejs/node-gyp)를 설치한다.
8091

81-
### 2. binding.gyp 파일 생성
92+
사전 파일이 포함된 `node-gyp`로 Addon을 빌드할 예정이기 때문에 [gcc](https://namu.wiki/w/GCC) 등의 컴파일러를 쓰지 않는다는 것을 확인할 수 있다.
93+
94+
### 2. Rust 파일을 목적 파일로 컴파일
95+
96+
```rs
97+
// mycalc.rs
98+
// rust_mul이라는 곱셈 함수를 export 할 예정.
99+
#[no_mangle]
100+
pub extern "C" fn rust_mul(a: isize, b: isize) -> isize {
101+
a * b
102+
}
103+
```
104+
105+
```sh
106+
# compile
107+
$ rustc --crate-type="dylib" mycalc.rs -o libmycalc.so
108+
```
109+
110+
Rust 코드로 작성된 `rust_mul` 함수를 `libmycalc.so` 파일로 컴파일한다.
111+
112+
해당 함수를 Node-API를 통해 C Addon으로 가져와 JavaScript 모듈에서 사용할 수 있도록 할 예정이다.
113+
114+
![so file](so-file.webp)
115+
116+
`.so` 파일은 컴파일 단계에서 코드 영역에 추가되지 않고 런타임에 동적으로 로드되는 라이브러리이다.
117+
118+
### 3. binding.gyp 파일 생성
82119

83120
```json
84121
// binding.gyp
@@ -88,98 +125,79 @@ Node-API 에서 제공하는 헤더, C 코드 등이 포함되어 있는 [node-g
88125
"target_name": "mycalc_addon",
89126
"sources": ["mycalc_addon.c"],
90127
"libraries": [
91-
"/PATH/libmycalc.so"// 동적 로딩을 사용하기 위해 추가
128+
"/PATH/libmycalc.so" // 동적 로딩을 사용하기 위해 추가(Rust 파일을 컴파일한 파일)
92129
],
93130
}
94131
]
95132
}
96133
```
97134

98-
### 3. Rust 파일 목적 파일로 컴파일
135+
`node-gyp`를 사용해 빌드할 때 필요한 설정 파일인 `binding.gyp`를 작성한다.
99136

100-
```rs
101-
// mycalc.rs
102-
// rust_mul 이라는 곱셈 함수를 export 할 예정.
103-
#[no_mangle]
104-
pub extern "C" fn rust_mul(a: isize, b: isize) -> isize {
105-
a * b
106-
}
107-
```
108-
109-
```sh
110-
# compile
111-
$ rustc --crate-type="dylib" mycalc.rs -o libmycalc.so
112-
```
137+
`mycalc_addon.c` 파일은 Node-API를 사용해 C Addon을 작성할 파일이다.
113138

114139
### 4. Addon C 파일 작성
115140

116141
```c
117-
// mycalc_addon.c// node_api를 가져옴(node-gyp에 있을 것이므로 바로 사용한다)
142+
// mycalc_addon.c
143+
// node_api를 가져오는 모습(node-gyp에 있을 것이므로 사용할 수 있다)
118144
#include <node_api.h>
119145
#include <stdio.h>
120-
// Rust 파일의 함수를 C로 가져온다(so 파일에 있을 예정).
146+
// Rust 파일의 함수를 C로 가져온다(so 파일에 있을 예정)
121147
int rust_mul(int a, int b);
122148
// 두 숫자를 더하는 C 함수
123-
napi_value Mul(napi_env env, napi_callback_info info)
124-
{
149+
napi_value Mul(napi_env env, napi_callback_info info) {
125150
napi_status status;
126-
// 인자 개수와 인자 배열
151+
// 인자 개수와 인자 배열
127152
size_t argc = 2;
128153
napi_value argv[2];
129154
status = napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
130-
if (status != napi_ok)
131-
{
155+
if (status != napi_ok) {
132156
napi_throw_error(env, NULL, "Failed to parse arguments");
133157
return NULL;
134158
}
135-
// 인자가 2개인지 확인
136-
if (argc < 2)
137-
{
159+
// 인자가 2개인지 확인
160+
if (argc < 2) {
138161
napi_throw_error(env, NULL, "Wrong number of arguments");
139162
return NULL;
140163
}
141-
// 인자를 double로 변환
164+
// 인자를 double로 변환. JavaScript와 C++의 다른 타입을 일치시킨다
142165
double value1, value2;
143166
status = napi_get_value_double(env, argv[0], &value1);
144-
if (status != napi_ok)
145-
{
167+
if (status != napi_ok) {
146168
napi_throw_error(env, NULL, "Invalid argument 1");
147169
return NULL;
148170
}
149171
status = napi_get_value_double(env, argv[1], &value2);
150-
if (status != napi_ok)
151-
{
172+
if (status != napi_ok) {
152173
napi_throw_error(env, NULL, "Invalid argument 2");
153174
return NULL;
154175
}
155-
// rust 모듈을 활용해 결과 계산 //////////////////////////////////////////////
176+
// rust 모듈을 활용해 결과 계산 //////////////////////////////////////////////
156177
double result = rust_mul(value1, value2);
157-
// 결과를 napi_value로 변환
178+
// rust 모듈의 결과를 napi_value로 변환
158179
napi_value result_value;
159180
status = napi_create_double(env, result, &result_value);
160-
if (status != napi_ok)
161-
{
181+
if (status != napi_ok) {
162182
napi_throw_error(env, NULL, "Failed to create result value");
163183
return NULL;
164184
}
185+
// 결과 반환
165186
return result_value;
166187
}
167188
// 모듈 초기화 함수
168-
napi_value Init(napi_env env, napi_value exports)
169-
{
189+
napi_value Init(napi_env env, napi_value exports) {
170190
napi_status status;
171-
// 함수 등록
191+
// 함수 등록
172192
napi_value fn;
173193
status = napi_create_function(env, NULL, 0, Mul, NULL, &fn);
174-
if (status != napi_ok)
175-
{
194+
if (status != napi_ok) {
176195
napi_throw_error(env, NULL, "Failed to create function");
177196
return NULL;
178197
}
179-
// exports 객체에 함수 추가
198+
// exports 객체에 함수 추가. 이로써 JavaScript에서 사용 가능해진다! ////////
180199
status = napi_set_named_property(env, exports, "mul", fn);
181-
if (status != napi_ok)
182-
{
200+
if (status != napi_ok) {
183201
napi_throw_error(env, NULL, "Failed to set named property");
184202
return NULL;
185203
}
@@ -189,12 +207,26 @@ napi_value Init(napi_env env, napi_value exports)
189207
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
190208
```
191209
210+
`napi`로 시작하는 함수들은 Node-API에서 제공하는 함수이다.
211+
212+
위의 코드를 모두 이해할 필요는 없다. Node-API를 사용해 C Addon을 작성하는 방법을 보여주기 위한 코드이다.
213+
214+
중요한 점은 Rust의 `rust_mul` 함수를 C로 가져와 `Mul` 함수에서 사용하고 있다는 점이다.
215+
192216
### 5. Addon 빌드
193217
194218
```sh
195219
$ node-gyp configure && node-gyp build
196220
```
197221

222+
`node-gyp`를 사용해 Addon을 빌드한다.
223+
224+
![build addon](build-addon.webp 's')
225+
226+
`build` 명령어를 실행하면 `build/Release` 디렉터리에 `mycalc_addon.node` 파일이 생성된다.
227+
228+
> `.node` 파일은 Node.js Addon을 의미한다.
229+
198230
### 6. JavaScript 파일에서 사용하기
199231

200232
```js
@@ -203,11 +235,23 @@ const addon = require("./build/Release/mycalc_addon");
203235
console.log(addon.mul(3, 5));// 15 출력
204236
```
205237

238+
TA-DA! JavaScript 파일에서 C Addon을 사용하고 있다.
239+
240+
이로써 Rust \<-> C \<-> Node.js 간의 연결이 완료되었다.
241+
206242
## 마치며
207243

208-
Node-API를 이해하는 과정에서 애드온에 대한 이해도가 높아졌다.
244+
Rust와 Node-API를 사용해 Node.js Addon을 개발하면서 Addon을 개발하는 과정을 이해할 수 있었다.
245+
246+
글을 요약하면 다음과 같다.
247+
248+
1. N-API는 Node-API 를 의미한다.
249+
2. Node-API를 활용해 JS 레이어를 거치지 않는, 고속 라이브러리를 만들 수 있다.
250+
3. 바이너리 코드로 통합되므로 FFI([외부 함수 인터페이스](https://ko.wikipedia.org/wiki/%EC%99%B8%EB%B6%80_%ED%95%A8%EC%88%98_%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4); 다른 언어 호출)가 가능하다.
251+
252+
Node.js의 내부에 한층 더 깊게 파고들어 간 것 같아 뿌듯하다.
209253

210-
Node.js의 내부에 한층 더 깊게 파고들어간 것 같아 뿌듯하다.
254+
이 글을 통해 Node.js Addon 개발에 흥미가 생겼길 바라며 글을 마무리 하겠다.
211255

212256
## 참고
213257

_posts/js/n-api/so-file.webp

7.68 KB
Loading

0 commit comments

Comments
 (0)