본문 바로가기
C.W.K.
Stream
Lesson 05 of 05 · published

`node` 명령 하나에 들어 있는 런타임

~12 min · runtime, cli, repl, flags

Level 0노드 입문자
0 XP0/40 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"node는 JavaScript 파일을 여는 작은 명령이 아냐. V8, libuv, 모듈 로더와 표준 라이브러리를 한꺼번에 시작하는 런타임의 입구야."

node를 실행하면 런타임 전체가 깨어나

운영체제는 먼저 node 실행 파일을 메모리에 올려. Node는 명령줄 선택지와 스크립트 인자를 나눈 뒤 V8과 libuv를 준비하고, 모듈 로더와 내장 모듈을 사용할 수 있는 환경을 만들어.

  1. --inspect, --watch 같은 Node 선택지를 먼저 해석해.
  2. V8 격리 영역과 실행 문맥을 만들고 메모리 한도를 적용해.
  3. libuv 이벤트 루프, 스레드 풀, 신호 처리를 준비해.
  4. fs, http, process 같은 내장 모듈을 쓸 수 있게 연결해.
  5. 경로가 있으면 CommonJS나 ESM 규칙에 따라 파일을 실행하고, 경로가 없으면 REPL을 열어.
  6. 예약된 작업과 열린 자원이 모두 사라지면 이벤트 루프가 끝나고 프로세스도 자연스럽게 종료돼.

스크립트는 런타임이 받을 수 있는 입력 한 종류일 뿐이야. -e로 받은 문자열, 표준 입력, REPL에서 친 표현식도 같은 런타임 위에서 돌아가.

REPL은 가장 가까운 실험실이야

인자 없이 node만 실행하면 REPL이 열려. 표현식을 입력하고 바로 결과를 확인할 수 있고, 최상위 await도 쓸 수 있어. .editor로 여러 줄을 편집하고 .save로 실험 내용을 파일에 남길 수도 있지.

정규식 하나가 맞는지, JSON 모양이 어떻게 생겼는지, 특정 내장 함수가 무엇을 돌려주는지 확인하려고 매번 새 파일과 테스트를 만들 필요는 없어. 짧은 질문에는 REPL이 디버거보다 빠르고, 답을 확인한 뒤 재현 가능한 코드를 파일로 옮기면 돼.

자주 쓰는 실행 모드를 손에 익혀

몇 가지 선택지만 알아도 일상적인 실행 대부분을 다룰 수 있어.
  • node script.js는 파일을 실행해.
  • node -e "..."는 문자열을 평가하고 끝내.
  • node -p "..."는 평가한 결과까지 출력해.
  • node --inspect-brk script.js는 디버거 연결을 기다리며 첫 줄에서 멈춰.
  • node --watch script.js는 관련 파일이 바뀌면 프로세스를 다시 시작해.
  • node --env-file=.env script.js는 환경 파일을 읽은 뒤 스크립트를 실행해.
내장 기능이 단순한 요구를 채운다면 별도 의존성을 더하지 않아도 돼. 반대로 복잡한 감시 규칙이나 환경 검증이 필요하면 전용 도구를 골라. "내장이라서 무조건"이 아니라 요구에 맞는 가장 작은 표면을 선택하는 거야.

종료 코드는 다음 프로그램과 맺는 계약이야

Unix 관례에서 종료 코드 0은 성공이고 그 밖의 값은 실패야. 처리하지 않은 예외가 생기면 Node는 실패 코드로 끝나고, process.exitCode = 1처럼 코드가 직접 결과를 정할 수도 있어. CI, 셸의 &&, 상위 프로세스는 이 숫자를 보고 다음 행동을 결정해.

오류를 출력하고도 성공 코드로 끝내면 사람이 아니라 자동화가 잘못된 결정을 내려. 반대로 process.exit()를 너무 일찍 부르면 아직 쓰는 중인 로그나 파일이 끊길 수 있어. 가능하면 process.exitCode만 정하고 열린 작업이 끝나도록 두는 편이 안전해.

Pippa의 고백

예전에는 node를 "JavaScript 파일 돌리는 명령"으로만 생각했어. 아빠가 node -p "process.versions"를 설명해 보라니까 파일 경로도 없는데 왜 실행되는지부터 막히더라. 이제는 실행 파일 자체가 런타임이고 스크립트는 입력 하나라고 봐. 그러니 REPL, 표준 입력, 디버거, 감시 모드가 따로 노는 기능이 아니라 같은 런타임의 여러 입구로 보여.

Code

REPL과 한 줄 실행, 표준 입력 사용하기·bash
# REPL에서 짧게 확인하기
node
> 1 + 1
2
> await fetch('https://api.github.com/zen').then(r => r.text())
'Approachable is better than simple.'
> .editor   # 여러 줄 편집
> .save sketch.js   # 실험 내용을 파일로 저장
> .exit

# -e는 평가하고, -p는 결과까지 출력한다.
node -e "console.log(process.platform)"
# darwin

node -p "Object.keys(process.versions).length"
# 18

# 표준 입력도 런타임의 입력이다.
echo '{"a": 1}' | node -e "
  let raw = '';
  process.stdin.on('data', c => raw += c);
  process.stdin.on('end', () => console.log(JSON.parse(raw).a));
"
디버깅, 감시, 환경 파일을 내장 선택지로 쓰기·bash
# 실제 스크립트를 첫 줄에서 멈추고 디버거 연결하기
node --inspect-brk script.mjs
# 127.0.0.1:9229에서 Inspector 연결을 기다린다.
# Chrome의 chrome://inspect 또는 VS Code로 연결한다.

# 파일이 바뀌면 개발 서버 다시 시작하기
node --watch server.mjs

# 환경 파일을 읽고 감시 모드로 실행하기
node --env-file=.env --watch server.mjs
# process.env.DATABASE_URL에서 값을 읽을 수 있다.

External links

Exercise

node -e"hello"의 SHA-256을 16진수로 출력해 봐. 다음에는 표준 입력에서 문자열을 받아 같은 해시를 출력하는 .mjs 파일로 옮겨. 마지막으로 --watch로 실행하고 파일을 저장했을 때 다시 시작되는지 확인해. 한 줄 실행, 파일 실행, 감시 모드를 모두 같은 런타임에서 써 보는 연습이야.
Hint
한 줄 실행에서는 node:cryptocreateHash("sha256")을 가져와 update()digest("hex")를 이어. 파일에서는 for await (const chunk of process.stdin)으로 표준 입력을 모으면 이벤트 처리 함수를 직접 연결하지 않아도 돼.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고
💛 by 똘이warm

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.