---
title: Web 개발 환경에 대한 이해 - NPM
date: 2022-03-24
tags: webdev, 공부
excerpt: NPM 이란?
---
# 프론트엔드 개발 환경의 이해 : NPM
Node.js는 백엔드를 구현하는 기술이라고 생각했던 때가 있었다. 그러나 요즘은 FE 공고에도 Node.js 기술이 우대사항이 되고 있는것을 볼 수 있었다. 웹 어플리케이션을 개발하는데 있어서 직접적으로 사용하는것은 아니지만 개발 환경을 구성하고 이해하는데에는 Node.js를 모른다면 언젠가 막히는 때가 온다고 한다.

---

## 1. 프론트엔드 개발에 Node.js가 필요한 이유
### 최신 스펙으로 개발 할 수 있다
자바스크립트 스펙의 빠른 발전에 비해 브라우져의 지원 속도는 항상 뒤쳐진다. 아무리 편리한 스펙이 나오더라도 이것을 구현해 주는 중간다리의 역할, 예를 들자면 바벨과 같은 도구의 도움 없이는 부족하다. 더불어 웹팩, NPM 같은 노드 기술로 만들어진 환경에서 사용할 때 비로소 자동화된 프론트엔드 개발 환경을 갖출 수 있다.

마찬가지로 Typescript, SASS 같은 고수준 프로그래밍 언어를 사용하려면 전용 **트랜스파일러**가 필요하다. 물론 이것 역시 Node.js 환경이 뒷받침 되어야 우리가 말하는 프론트엔드 개발 환경을 만들 수 있다.

### 빌드 자동화
과거처럼 코딩 결과물을 브라우져에 바로 올리는 경우는 흔치 않다고 한다. <br>
파일을 압축하고, 코드를 난독화하고, 폴리필을 추가하는 등 개발 이외의 작업을 거친 후에야 배포를 하게 된다. <br>
Node.js는 이러한 일련의 빌드 과정을 이해하는데 많은 역할을 하고 있다. 또한 라이브러리 의존성을 해결하고, 각종 테스트를 자동화하는데도 사용된다.

### 개발 환경 커스터마이징
각 프레임워크에서 제공하는 도구를 사용하면 손쉽게 개발환경을 갖출 수 있도록 지원하고 있다. <br>
React.js의 CRA(create-react-app), Vuejs의 vue-cli를 사용한다면 손쉽게 개발환경을 갖출 수 있는 것이다. <br>
그러나 개발 프로젝트는 각자의 목적이나 사용방법에 있어서 형편이라는 것이 있어서 툴을 그대로 사용할 수 없는 경우도 빈번하다. <br> 어쩌면 자동화된 도구를 사용할 수 없는 환경이 다가올수도 있는데, 그 때에 직접 환경을 구축해야 할 수도 있다. <br>
커스터마이징을 하려면 반드시 Node.js 지식이 필요하다. <br>


위에서 배경하에 Node.js는 프론트엔드 개발에서 필수 기술로 자리매김하고 있다. <br>
이번에는 Node.js 기술을 바탕으로 프론트엔드 개발 환경을 이해하고 직접 구성해보면 좋을 것 같다.

--------

## 2. Node.js 설치
노드 설치는 무척 간단하다. 자신의 운영체제에 맞게 설치파일을 다운받으면 된다. <br>
[Nodejs.org](https://Nodejs.org) 사이트에서 노드 최신 버전을 다운로드 받아 보자. <br>

두 가지 버전을 선택하여 설치할 수 있다. 왼쪽 짝수 버전, 오른쪽이 홀수 버전이다. 안정적이고 장기간 지원하는 것이 짝수 버전이고, 불안정할 수 있지만 최신 기능을 지원하는 것이 홀수 버전이다. 리눅스 배포버전과 비슷한 관례를 따른다고 한다. <br>

- 짝수 버전 : 안정적, 신뢰도 높음 (LTS)
- 홀수 버전 : 최신 기능

이왕이면 오른쪽에 있는 최신 버전을 선택해서 다운로드 받아보자. 개발 환경에 대한 이해니까! (만약 Node.js로 서버를 구성하는 경우라면 어떤 버전을 사용할지 신중하게 선택해야 한다.)

![img](/assets/img/web_dev/nodejs.png)

설치화면이 나오고 버튼을 몇 번 클릭하면 아래 경로로 Node.js와 NPM이 설치가 완료된다. <br>

 - Node.js : `\usr\local\bin\node`
 - NPM : `\usr\local\bin\npm`

설치가 완료된 후, 터미널을 열어서 명령어를 통해 확인해 볼 수 있다.
```
$ node
> 1 + 2
3
```

`>` 프롬프트와 함께 입력 대기화면이 나온다. 정수 계산이나 `console.log()` 같은 내용도 실행 할 수 있다.

이것을 노드 REPL(read-eval-print loop)이라고 부르는데, 자바스크립트 코드를 입력하고 즉시 결과를 확인할 수 있는 프로그램이다. 파이썬이나 PHP 같은 언어도 제공하는 기능이다.

`.exit` 명령을 실행하거나 `ctrl + c`를 연속 두 번 입력하면 REPL 프로그램에서 빠져 나올 수 있다. <br>

이번에는 `--version` 옵션을 추가해서 설치된 버전을 확인해 본다.
```
$ node --version
v17.3.1
```
NPM(Node Package Manage)도 확인 할 수 있다.
```
$ npm
Usage: npm <command>

$ npm --version
8.3.0
```
이렇게 각 Node.js와 NPM을 설치하고 각 설치된 버전을 확인해 볼 수 있다.

---

## 3. 프로젝트 초기화
개발 프로젝트는 외부 라이브러리를 다운로드 받고 빌드하는 등 일련의 명령어를 자동화하여 프로젝트를 관리하는 도구가 존재한다. PHP의 컴포저(Composer)나 자바의 그래들(Gradle) 같은 툴이 그러한 프로그램이라고 할 수 있다. <br>
NPM은 자바스크립트 기반 프로젝트의 빌드 도구인 셈이다. NPM을 이용해 프론트엔드 개발 프로젝트를 세팅해보자.

### 3-1. INIT
NPM은 다양한 하위 명령어를 제공하는데, 그 중 `init` 명령어를 사용하면 프로젝트를 생성 할 수 있다.
```shell
$ npm init

package name: (npm sampple)
version:
description:
entry point:
test command:
git repository:
keywords:
author:
license:
```

위의 내용에서 볼 수 있듯이 패키지 이름, 버전 등 프로젝트와 관련된 정보들을 얻기 위한 몇 가지 질문들을 볼 수 있다.<br>
빈칸으로 남겨 놓으면 괄호 안에 기본 값으로 세팅할 수 있다. 모든 질문에 답하면 명령어를 실행한 폴더에 package.json 파일이 생성된다.<br>
모두 기본값으로 세팅하려면 `npm init -y` 명령어로 질문을 스킵하고 package.json 파일을 생성할 수 있다. <br>

### 3-2. Package.json
Node.js는 package.json 파일에 프로젝트의 모든 정보를 기록한다.

 - name: 프로젝트 이름
 - version: 프로젝트 버전 정보
 - description: 프로젝트 설명
 - main: 노드 어플리케이션일 경우 진입점 경로. 프론트엔드 프로젝트일 경우 사용하지 않는다.
 - scripts: 프로젝트 명령어를 등록할 수 있다.초기화시 test 명령어가 샘플로 등록되어 있다
 - author: 프로그램 작성자
 - license: 라이센스

 각 항목의 의미는 위의 내용과 같다.

--------

 ## 4. 프로젝트 명령어
생성한 프로젝트는 package.json에 등록한 스크립트(script)를 이용해 실행한다. <br>
어플리케이션 빌드, 테스트, 배포, 실행 등의 명령어를 등록하여 실행하는데에는 다음과 같다. <br>

```shell
$ npm test

Error: no test specified
npm ERR! Test failed.  See above for more details.
```

에러 메세지를 출력하고 그 다음 줄에 npm 에러가 발생한다. 이것은 npm 스크립트에 등록된 쉘 스크립트 코드를 실행했기 때문이다.

package.json
```javascript
{
    "script": {
        "test": "echo \"Error: no test specified\" && exit 1"
    }
}
```
echo 명령어로 메세지 "Error: no test specified"를 출력한뒤 에러 코드 1을 던지면서 종료하는 동작이다. 에러 코드 1을 확인하면 에러("npm ERR!...")를 출력하게된다.

NPM에서 사용할 수 있는 명령어는 몇 가지나 될까? 확인해보자면 아래를 살펴보자.
```shell
Usage: npm <command>

where <command> is one of:
    access, adduser, audit, bin, bugs, c, cache, ci, cit,
    clean-install, clean-install-test, completion, config,
    create, ddp, dedupe, deprecate, dist-tag, docs, doctor,
    edit, explore, get, help, help-search, hook, i, init,
    install, install-ci-test, install-test, it, link, list, ln,
    login, logout, ls, org, outdated, owner, pack, ping, prefix,
    profile, prune, publish, rb, rebuild, repo, restart, root,
    run, run-script, s, se, search, set, shrinkwrap, star,
    stars, start, stop, t, team, test, token, tst, un,
    uninstall, unpublish, unstar, up, update, v, version, view,
    whoami
```
엄청나게 많다. 보통 사용하는 명령어는 start, test, install, uninstall이다.
 - start : 어플리케이션 실행
 - test : 테스트
 - install : 패키지 설치
 - uninsatll : 패키지 삭제

명령어를 추가할 수도 있다. 빌드를 위한 'build' 스크립트는 package.json의 scripts에 build 키를 등록하여 사용할 수 있다.
```javascript
{
    "scripts": {
        "build" : "여기에 빌드 스크립트를 등록한다."
    }
}
```

커스텀으로 등록한 스크립트는 다음과 같이 실행한다.
```shell
$ npm run build
```

프론트엔드 개발을 위해 등록해야할 스크립트는 두 가지 정도가 있겠다.
 - build : 소스 빌드
 - lint : 소스 컨벤션 검사

---

## 5. 패키지 설치
### 5-1. CDN을 이용한 방법
외부 라이브러리를 가져다 쓰는 것은 무척 자연스러운 일이다. 간단한 방법은 CDN(Content Delivery Network)으로 제공하는 라이브러리를 직접 가져 오는 방식이다. 리액트의 주소를 html에서 로딩한다.
```javascript
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
```

CDN의 서버 장애로 인해 외부 라이브러리를 사용할 수 없다면 어떻게 될까?? <br>
아무리 우리 어플리케이션 서버가 정상이더라도 필수 라이브러리를 가져오지 못한다면 웹 어플리케이션은 정상적으로 동작하지 않을 것이다. <br>

### 5-2. 직접 다운로드 받는 방법
그렇다면 라이브러리 코드를 우리 프로젝트 폴더에 다운받아 놓는건 어떨까? CDN을 사용하지 않기 때문에 장애와 독립적으로 웹 어플리케이션을 제공할 수 있을 것 같다. <br>

하지만 이런 상황도 있다. 라이브러리는 계속해서 업데이트 될 것이고, 우리 프로젝트에서도 최신 버전으로 교체해야 한다. 매번 직접 다운로드하는 것은 매우 귀찮은 일이 될 것이다. 버전에 따라 하위 호환성 여부까지 확인하려면 실수할 여지가 많다. <br>

라이브러리를 어느 한 곳에서 업데이트하고 하위 호환되는 안전한 버전만 다운받아 사용할 수 있다면 어떨까? <br>

### 5-3. NPM을 이용한 방법
NPM은 이런 방식으로 패키지를 관리한다. `npm install` 명령어로 외부 패키지를 우리 프로젝트 폴더에 다운로드 해보자.
```shell
$ npm install react
```
최신 버전의 react를 NPM 저장소에서 찾아 우리 프로젝트로 다운로드 하는 명령어다. package.json에는 설치한 패키지 정보를 기록한다.
```javascript
{
    "dependencies": {
        "react": "^16.12.0"
    }
}
```
버전 16.12.0을 설치했다는 의미이다.

### 5-4. 유의적 버전
"^16.12.0"이 의미하는 바는 무엇일까? <br>

위 질문에 답하기 전에 버전 관리에 대해서 생각해 볼 필요가 있다. 만약 프로젝트에서 사용하는 패키지의 버전을 엄격하게 제한한다면 어떨까? 프로젝트를 버전업 하는데 꽤 힘들 수 있다. **사용하는 패키지를 전부 버전업해야 하기 때문**이다. 어쩌면 우리 프로젝트는 현재 버전에 갇혀 버릴지도 모른다.

그럼 프로젝트에서 사용하는 패키지 버전을 느슨하게 풀어 놓으면 문제가 해결될까? 오히려 여러 버전별로 코드를 관리해야하는 혼란스러움을 겪게될 수 있다.

버전 번호를 관리하기 위한 규칙이 필요한데 이 체계를 **"유의적 버전"**이라고 한다. NPM은 이 유의적 버전(Sementic Version)을 따르는 전제 아래 패키지 버전을 관리한다.

유의적 버전은 주(Major), 부(Minor), 수(Patch) 세 가지 숫자를 조합해서 버전을 관리한다. 위에 설치한 react의 버전은 v16.12.0인데 주 버전이 16, 부 버전이 12, 수 버전이 0인 셈이다.

 - 주 버전(Major Version): 기존 버전과 호환되지 않게 변경한 경우
 - 부 버전(Minor version): 기존 버전과 호환되면서 기능이 추가된 경우
 - 수 버전(Patch version): 기존 버전과 호환되면서 버그를 수정한 경우

### 5-5. 버전의 범위
NPM이 버전을 관리하는 방식은 유의적 버전 명시뿐만 아니라 버전의 범위를 자신만의 규칙으로 관리한다. 가장 단순한 것이 특정 버전을 사용하는 경우다.
```
1.2.3
```

특정 버전보다 높거나 낮은 범위를 나타내는 방법은 다음과 같다.
```
>1.2.3
>=1.2.3
<1.2.3
<=1.2.3
```
마지막으로 틸드(~)와 캐럿(^)을 이용해 범위를 명시한다.
```
~1.2.3
^1.2.3
```

틸트(~)는 마이너 버전이 명시되어 있으면 패치버전만 변경한다. 예를 들어 ~1.2.3 표기는 1.2.3 부터 1.3.0 미만 까지를 포함한다. 마이너 버전이 없으면 마이너 버전을 갱신한다. ~0 표기는 0.0.0부터 1.0.0 미만 까지를 포함한다.

캐럿(^)은 정식버전에서 마이너와 패치 버전을 변경한다. 예를 들어 ^1.2.3 표기는 1.2.3부터 2.0.0 미만 까지를 포함한다. 정식버전 미만인 0.x 버전은 패치만 갱신한다. ^0 표기는 0.0.0부터 0.1.0 미만 까지를 포함한다.

보통 라이브러리 정식 릴리즈 전에는 패키지 버전이 수시로 변한다. 0.1에서 0.2로 부버전이 변하더라도 하위 호환성을 지키지 않고 배포하는 경우가 빈번하다. ~0로 버전 범위를 표기한다면 0.0.0부터 1.0.0미만까지 사용하기 때문에 하위 호완성을 지키지 못하는 0.2로도 업데이트 되어버리는 문제가 생길수 있다.

반면 캐럿을 사용해 ^0.0으로 표기한다면 0.0.0부터 0.1.0 미만 내에서만 버전을 사용하도록 제한한다. 따라서 하위 호완성을 유지할 수 있다. (자세한 내용은 [여기](/_posts/devlog/webdev/2022-03-26-web-npmcarrot.md)를 참고)

NPM으로 패키지를 설치하면 package.json에 설치한 버전을 기록하는데 캐럿 방식을 이용한다. 초기에는 버전 범위에 틸트를 사용하다가 캐럿을 도입해서 기본 동작으로 사용했다. 그래서 우리가 설치한 react는 ^16.12.0 표기로 버전 범위를 기록한 것이다.

---

## 6. 정리
Node.js 기술을 기반으로 하는 프로트엔드 개발 환경 구축을 위해 Node.js와 NPM을 설치했다.

npm init 명령어를 사용하면 package.json에 정보를 기록하고 프로젝트를 초기화 한다.

NPM이 제공하는 기본 명령어와 커스텀 명령어 추가방법을 알아보았다.

npm install로 외부 패키지를 다운로드 할 수 있고, 버전을 관리하는 방식에 대해 살펴 보았다.
