현록

npm scripts와 npm run 정리

Node 프로젝트를 열어보면 package.json 안에 scripts라는 항목이 자주 보인다.
Next.js 프로젝트에서는 npm run dev를 실행하고, 테스트가 있는 프로젝트에서는 npm test를 실행한다.
처음에는 npm run devnpm start가 npm에 원래 들어있는 고정 명령어처럼 보일 수 있다.

하지만 대부분의 프로젝트 명령어는 package.json scripts에 적힌 값을 npm이 대신 실행하는 구조다.
npm scripts란 프로젝트에서 반복해서 쓰는 명령어에 이름을 붙여두는 약속이라고 보면 된다.
프로젝트마다 dev, build, lint, test가 다르게 동작하는 이유도 각 프로젝트의 scripts 내용이 다르기 때문이다.

npm scripts란

scriptspackage.json 안에 있는 객체다.
프로젝트에서 자주 쓰는 명령어를 짧은 이름으로 등록한다.

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "lint": "eslint .",
    "test": "vitest run"
  }
}

이 설정이 있으면 아래 명령어를 실행할 수 있다.

npm run dev
npm run build
npm run lint
npm test

npm run devscripts.dev에 적힌 next dev를 실행한다.
npm run buildscripts.build에 적힌 next build를 실행한다.
즉, npm run은 npm이 프로젝트의 scripts 목록을 읽고 그 안의 명령어를 실행하는 방식이다.

팀원이 같은 저장소를 내려받으면 같은 이름의 명령어를 사용할 수 있다.
그래서 scripts는 개인 편의 기능을 넘어 협업용 명령어 목록 역할도 한다.

npm run dev가 실행되는 기준

npm run dev는 npm에 내장된 개발 서버 명령어가 아니다.
항상 현재 프로젝트의 package.json에서 scripts.dev 값을 찾는다.

{
  "scripts": {
    "dev": "next dev"
  }
}

이 경우 npm run dev는 실제로 next dev를 실행한다.
Vite 프로젝트라면 dev 값이 vite일 수 있다.
Nuxt 프로젝트라면 dev 값이 nuxt dev일 수 있다.

그래서 어떤 프로젝트에서 npm run dev가 무엇을 하는지 궁금하면 먼저 package.json을 본다.
README보다 scripts가 더 정확한 경우도 많다.

{
  "scripts": {
    "dev": "vite --host 0.0.0.0"
  }
}

이런 프로젝트에서는 npm run dev가 단순히 vite만 실행하는 것이 아니라 host 옵션까지 포함해서 실행한다.
명령어의 실제 의미는 npm이 정하는 것이 아니라 프로젝트가 정한다.

자주 쓰는 scripts

프론트엔드 프로젝트에서 자주 만나는 script 이름은 어느 정도 비슷하다.
다만 이름이 같아도 내부 명령어는 프로젝트마다 다를 수 있다.

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint .",
    "format": "prettier . --write",
    "test": "vitest run"
  }
}

dev는 보통 개발 서버를 실행한다.
build는 배포용 결과물을 만든다.
start는 빌드된 앱이나 서버를 실행한다.
lint는 코드 규칙을 검사한다.
format은 코드 스타일을 정리한다.
test는 테스트를 실행한다.

초보자라면 명령어를 외우기보다 scripts를 읽는 습관이 더 중요하다.
프로젝트의 실제 작업 흐름이 이 목록에 들어있는 경우가 많기 때문이다.

npm start와 npm run start

start script가 정의되어 있으면 npm startnpm run start는 보통 같은 start script를 실행한다고 이해하면 된다.

{
  "scripts": {
    "start": "next start"
  }
}
npm start
npm run start

둘 다 next start를 실행한다.
다만 npm start는 npm CLI에 있는 별도 명령이고, npm 문서 기준으로 루트에 server.js가 있는데 start script가 없으면 node server.js가 기본값으로 사용될 수 있다.
일반적인 프론트엔드 프로젝트에서는 scripts.start를 명시해두는 경우가 많다.

test, stop, restart도 비슷하게 짧은 명령어가 있다.
그래서 npm test는 보통 npm run test와 같은 식으로 이해할 수 있다.

node_modules/.bin을 생략하는 이유

프로젝트에 설치한 CLI 도구는 보통 node_modules/.bin 아래에 실행 파일을 만든다.
예를 들어 eslint, prettier, vitest, next 같은 도구가 여기에 연결될 수 있다.

직접 실행하려면 아래처럼 써야 할 것 같지만, scripts 안에서는 보통 이렇게 쓰지 않는다.

{
  "scripts": {
    "lint": "node_modules/.bin/eslint ."
  }
}

npm은 script를 실행할 때 node_modules/.binPATH에 추가한다.
그래서 scripts 안에서는 아래처럼 짧게 쓸 수 있다.

{
  "scripts": {
    "lint": "eslint ."
  }
}

이 덕분에 전역 설치에 기대지 않고 프로젝트에 설치된 버전의 도구를 실행할 수 있다.
패키지가 dependencies에 있는지 devDependencies에 있는지 헷갈린다면 dependencies와 devDependencies 차이를 같이 보면 좋다.

인자 전달 방식

script에 추가 인자를 넘길 때는 --를 사용한다.
-- 뒤에 있는 값은 npm이 해석하지 않고 실행되는 script 쪽으로 넘긴다.

{
  "scripts": {
    "test": "vitest run"
  }
}
npm run test -- --watch

이 명령어는 vitest run --watch처럼 동작한다.
--watch가 npm 옵션이 아니라 vitest 옵션이라는 점을 명확히 하기 위해 --를 끼워 넣는 것이다.

자주 쓰는 옵션이라면 script 이름을 따로 만드는 편이 더 읽기 좋다.

{
  "scripts": {
    "test": "vitest run",
    "test:watch": "vitest"
  }
}

이렇게 두면 npm run test:watch처럼 의미 있는 이름으로 실행할 수 있다.

pre와 post script

npm은 같은 이름 앞에 prepost를 붙인 script를 특별하게 처리한다.
예를 들어 build 앞뒤로 실행할 명령어를 만들 수 있다.

{
  "scripts": {
    "prebuild": "npm run lint",
    "build": "next build",
    "postbuild": "node scripts/check-output.js"
  }
}

npm run build를 실행하면 prebuild, build, postbuild 순서로 실행된다.
빌드 전에 lint를 돌리거나, 빌드 후 산출물을 검사하는 용도로 쓸 수 있다.

다만 너무 많은 동작을 숨겨두면 명령어 하나가 무겁고 예측하기 어려워진다.
초보자에게는 build, lint, test처럼 직접 실행할 수 있는 script를 분리해두는 구성이 더 읽기 좋다.

CI에서 쓰는 scripts

CI에서는 사람이 직접 명령어를 기억하지 않아도 되도록 scripts를 조합해서 사용한다.
예를 들어 아래처럼 검증 명령어를 모아둘 수 있다.

{
  "scripts": {
    "lint": "eslint .",
    "test": "vitest run",
    "build": "next build",
    "check": "npm run lint && npm test && npm run build"
  }
}

CI 설정에서는 npm run check만 실행하면 된다.
프로젝트 내부의 실제 검사 흐름이 바뀌어도 CI 파일을 크게 바꾸지 않아도 된다.

의존성을 설치하는 단계에서는 npm install보다 npm ci를 쓰는 경우가 많다.
이 차이는 npm ci와 npm install 차이에 정리해두었다.
설치 옵션을 프로젝트에 고정해야 한다면 .npmrc 파일이란?도 함께 연결해서 보면 좋다.

확인 순서

처음 보는 프로젝트에서 어떤 명령어를 실행해야 할지 모르겠다면 아래 순서로 보면 된다.

  1. package.jsonscripts를 확인한다.
  2. README의 실행 방법과 scripts 이름이 맞는지 본다.
  3. 의존성이 설치되어 있지 않다면 npm install이나 npm ci가 필요한지 확인한다.
  4. CI나 배포 문서가 있다면 어떤 script를 기준으로 자동화되는지 본다.

로컬 개발만 한다면 npm run dev가 출발점인 경우가 많다.
반복 가능한 설치와 검증 흐름까지 맞추려면 npm ci, npm test, npm run build 같은 명령어가 함께 쓰인다.

정리

npm scripts는 프로젝트 명령어에 이름을 붙여두는 공간이다.
npm run devscripts.dev에 적힌 명령어를 실행한다.
npm startnpm test는 자주 쓰는 script를 짧게 실행하는 명령어로 이해할 수 있다.

npm은 script 실행 시 node_modules/.binPATH에 넣어준다.
그래서 eslint, next, vitest 같은 로컬 CLI를 scripts 안에서 바로 사용할 수 있다.

추가 인자를 넘길 때는 npm run script -- 인자 형태를 사용한다.
명령어가 길어지거나 자주 반복된다면 새로운 script 이름을 만들어두는 편이 좋다.

참고 자료

관련 포스트
HTML button과 a 태그 차이 thumbnail
HTML button과 a 태그 차이
HTML button과 a 태그의 차이를 이동과 동작의 의미, href, type 속성, 폼 제출, 키보드 접근성, 잘못된 사용 패턴으로 정리합니다.
HTTP GET과 POST 차이 thumbnail
HTTP GET과 POST 차이
HTTP GET과 POST의 차이를 초보자 기준으로 정리합니다. 조회와 변경, query string과 request body, 캐시, safe, idempotent, form과 fetch 사용 기준을 함께 봅니다.
npm outdated와 npm update 차이 thumbnail
npm outdated와 npm update 차이
npm outdated와 npm update의 차이를 초보자 기준으로 정리합니다. Current, Wanted, Latest의 의미와 semver 범위 안에서 업데이트되는 방식, package-lock.json 변화까지 함께 봅니다.
npm install -g와 npx 차이 thumbnail
npm install -g와 npx 차이
npm install -g와 npx의 차이를 초보자 기준으로 정리합니다. 전역 설치, 로컬 설치, 일회성 실행, 프로젝트 scripts에 넣는 기준까지 함께 봅니다.
package.json에서 ^와 ~ 차이 thumbnail
package.json에서 ^와 ~ 차이
package.json 의존성 버전 앞에 붙는 ^와 ~의 차이를 초보자 기준으로 정리합니다. semantic versioning, 버전 범위, 0.x 예외, package-lock.json과의 관계까지 함께 봅니다.
npx와 npm exec 차이 thumbnail
npx와 npm exec 차이
npx와 npm exec가 어떤 명령어인지 초보자 기준으로 정리합니다. 로컬 패키지 실행, 원격 패키지 임시 실행, --package 옵션, -- 인자 전달 차이까지 함께 봅니다.
dependencies와 devDependencies 차이 thumbnail
dependencies와 devDependencies 차이
package.json의 dependencies와 devDependencies 차이를 초보자 기준으로 정리합니다. npm install과 npm install -D, 배포 환경 설치, package-lock.json과의 관계까지 함께 봅니다.
npm ci와 npm install 차이 thumbnail
npm ci와 npm install 차이
npm ci와 npm install의 차이를 초보자 기준으로 정리합니다. clean install의 의미, package-lock.json 조건, CI에서 npm ci를 쓰는 이유, .npmrc 플래그 주의점까지 함께 봅니다.
.npmrc 파일이란? thumbnail
.npmrc 파일이란?
storybook을 사용해보려고 하다 마주한 이슈의 해결법을 알아보다가 등장한 .npmrc라는 파일에 대해 공부해보았다. .npmrc 파일이란? .npmrc 파일은 npm에 대한 config 파일이다. (npm에 대한 rc 파일) 프로젝트별 registry, install 옵션, 인증 토큰처럼 npm CLI가 읽는 설정을 관리할 때 사용한다.
스토리북이란? thumbnail
스토리북이란?
Storybook은 UI 컴포넌트를 독립적으로 개발하고, 문서화하고, 테스트하기 위한 프론트엔드 워크샵입니다. Storybook 10.4 기준 설치 흐름과 stories, 문서화, 테스트 활용 방식을 정리합니다.
TTV와 TTI 차이 thumbnail
TTV와 TTI 차이
TTV와 TTI의 차이를 초보자 기준으로 정리합니다. 사용자가 화면을 보는 시점, 상호작용 가능한 시점, FCP, LCP, INP, Core Web Vitals와의 관계까지 함께 봅니다.
Maria DB 외부 접속 설정하기 thumbnail
Maria DB 외부 접속 설정하기
안녕하세요. 오늘은 Maria DB 초기 세팅 시, 외부에서 접속이 안될 때 매뉴얼을 작성해보겠습니다. dotenv 패키지를 통해서 환경변수로 관리한다면, 별도의 추가 작업을 할 일이 없으실 겁니다.
package-lock.json 파일의 역할 thumbnail
package-lock.json 파일의 역할
안녕하세요. 오늘은 node 환경의 개발자라면 한번쯤 궁금했을만한 package-lock.json의 역할에 대해 알아보겠습니다. 우리는 node 환경에서 개발을 할 때 다양한 패키지들을 설치하여 활용하곤 합니다. 우리가 설치하는 패키지 또한 다른 npm 패키지를 활용하여 만든 패키지들이고 이들 또한 설치를 하게 됩니다. 이렇게 직간접적으로 설치된 패키지들은 대부분 호환성을 "^1.1.5"와 같이 표현하여, 범위로 지정해두고 있습니다.
Linux 환경 배포 자동화 체험해보기 thumbnail
Linux 환경 배포 자동화 체험해보기
안녕하세요. 요즘 포트폴리오를 만들면서 서버 상에 자주 반영할 일이 생겼는데, 매번 명령어들을 타이핑하는 것이 비효율적이라 생각이 들어 배포 자동화를 생각해보게 되었습니다. 현재 레벨에서는 단순히 명령어들만 단축시켜도 효율적이라 생각이 들어 간단한 쉘 스크립트만 작성하였습니다. 정말 간단하니 여러분도 도전해보시길 바랍니다.
협업 필수품. Prettier thumbnail
협업 필수품. Prettier
안녕하세요. 오늘은 Prettier이라는 도구에 대해 알려드리고자 합니다. 개발자는 각자의 코딩스타일이 존재합니다. 그러다보니 같은 프로젝트에서도 작성하는 소스마다 스타일이 제각기 다르기 일쑤입니다. 그럴 때 도입하면 좋은 것이 Prettier입니다. 프로젝트 root 폴더에 .prettierrc 라는 파일을 생성한 뒤, 위 예시와 같이 원하는 옵션을 JSON 형식으로 작성해주면 됩니다.