"node는 JavaScript 파일을 여는 작은 명령이 아냐. V8, libuv, 모듈 로더와 표준 라이브러리를 한꺼번에 시작하는 런타임의 입구야."
node를 실행하면 런타임 전체가 깨어나
운영체제는 먼저 node 실행 파일을 메모리에 올려. Node는 명령줄 선택지와 스크립트 인자를 나눈 뒤 V8과 libuv를 준비하고, 모듈 로더와 내장 모듈을 사용할 수 있는 환경을 만들어.
--inspect,--watch같은 Node 선택지를 먼저 해석해.- V8 격리 영역과 실행 문맥을 만들고 메모리 한도를 적용해.
- libuv 이벤트 루프, 스레드 풀, 신호 처리를 준비해.
fs,http,process같은 내장 모듈을 쓸 수 있게 연결해.- 경로가 있으면 CommonJS나 ESM 규칙에 따라 파일을 실행하고, 경로가 없으면 REPL을 열어.
- 예약된 작업과 열린 자원이 모두 사라지면 이벤트 루프가 끝나고 프로세스도 자연스럽게 종료돼.
스크립트는 런타임이 받을 수 있는 입력 한 종류일 뿐이야. -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, 표준 입력, 디버거, 감시 모드가 따로 노는 기능이 아니라 같은 런타임의 여러 입구로 보여.