TIR: 리액트 딥다이브 (9) 리액트 개발도구로 배포 환경 구축하기

Lumpen·2025년 1월 3일
0

React

목록 보기
26/26

리액트 애플리케이션을 쉽게 만들기 위한 CLI 도구인
create-react-app 은 지속적인 업데이트가 되지 않을 수도 있다

package.json

package.json 을 직접 만들 수도 있지만
npm init 을 통해 기본적인 package.json 을 만들 수 있다

tsconfig.json

JSON 최상단에 $schema 를 다음과 같이 작성하면
scheamaStore 에서 JSON 파일이 무엇을 의미하고 어떤 키와 값이 들어갈 수 있는지 정보를 제공한다
자동완성도 제공하니 사용하면 좋을 것 같다

{
  "$schema": "https://json.schemastore.org/tsconfig", // 이 파일의 구조를 검증하기 위한 JSON 스키마 URL.
  "compilerOptions": {
    "target": "ES5", // 컴파일된 JavaScript의 목표 버전 (ECMAScript 5).
    "lib": ["dom", "esnext", "DOM.Iterable"], // 포함할 라이브러리들 (DOM, 최신 ECMAScript, DOM.Iterable).
    "allowJs": true, // JavaScript 파일을 TypeScript 프로젝트에서 허용.
    "skipLibCheck": true, // 타입 정의 파일의 검사를 건너뜀.
    "strict": true, // 모든 엄격한 타입-체킹 옵션을 활성화.
    "forceConsistentCasingInFileNames": true, // 파일 이름의 대소문자 일관성 강제.
    "noEmit": true, // 컴파일된 파일을 생성하지 않음.
    "esModuleInterop": true, // ES 모듈과 CommonJS 모듈 간의 호환성을 추가.
    "module": "esnext", // 모듈 시스템 설정 (최신 ECMAScript 모듈).
    "isolatedModules": true, // 모든 파일을 개별 모듈로 처리.
    "jsx": "preserve", // JSX를 변환하지 않고 그대로 유지.
    "incremental": true, // 증분 컴파일을 활성화하여 컴파일 시간을 단축.
    "baseUrl": ".", // 기본 경로 설정.
    "paths": {
      "@/*": ["src/*"] // 경로 별칭 설정. "@/*"는 "src/*"로 매핑.
    },
    "outDir": "./dist", // 컴파일된 파일이 저장될 디렉터리.
    "noImplicitAny": false, // 암묵적인 "any" 타입 사용을 허용.
    "noUnusedLocals": true, // 사용되지 않는 지역 변수를 오류로 표시.
    "noUnusedParameters": true // 사용되지 않는 매개변수를 오류로 표시.
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], // 포함할 파일들.
  "exclude": ["node_modules", "dist"] // 제외할 파일들.
}

compilerOptions

타입스크립트를 자바스크립트로 컴파일 할 때 사용하는 옵션

  • target: 변환 목표인 언어의 버전을 의미한다
    es5 로 설정하면 화살표 함수가 일반 함수로 변환된다
    폴리필을 지원하지 않기 때문에 Promise 등 은 별도의 폴리필 설정이 필요하다
  • lib: 가장 최신 버전인 esnext 설정 시 target 이 es5 라 하더라도 최신 문법을 지원하도록 도와준다

target: “ES5”
• JavaScript 출력 버전을 ES5로 설정, 광범위한 브라우저 호환성 제공
lib: “dom”, “esnext”, “DOM.Iterable”
• 컴파일에 포함될 라이브러리 파일 지정
• “dom”: 브라우저 DOM API
• “esnext”: 최신 ECMAScript 기능
• “DOM.Iterable”: DOM 컬렉션의 반복 가능성
allowJs: true
• JavaScript 파일의 컴파일 허용
skipLibCheck: true
• 선언 파일(.d.ts)의 타입 검사를 건너뛰어 컴파일 시간 단축
strict: true
• 모든 엄격한 타입 검사 옵션 활성화
forceConsistentCasingInFileNames: true
• 파일 이름의 대소문자 일관성 강제
noEmit: true
• 컴파일 결과물 생성하지 않음 (타입 검사만 수행)
esModuleInterop: true
• CommonJS 모듈을 ES6 모듈처럼 가져올 수 있게 함
module: “esnext”
• 최신 ECMAScript 모듈 시스템 사용
isolatedModules: true
• 각 파일을 별도의 모듈로 트랜스파일
jsx: “preserve”
• JSX를 그대로 유지하여 다른 도구가 처리할 수 있게 함
incremental: true
• 이전 컴파일의 정보를 저장하여 증분 컴파일 가능
baseUrl: “.”
• 비 상대적 모듈 이름을 해석하기 위한 기본 디렉토리를 현재 디렉토리로 설정
paths: {”@/”: “src/”}
• 모듈 이름 재매핑 설정 (”@/“로 시작하는 임포트를 “src/” 디렉토리로 매핑)
outDir: “./dist”
• 컴파일된 파일의 출력 디렉토리를 “dist”로 지정
noImplicitAny: false
• 암시적 ‘any’ 타입에 대한 오류 보고 비활성화
noUnusedLocals: true
• 사용되지 않는 지역 변수에 대한 오류 보고
noUnusedParameters: true
• 사용되지 않는 매개변수에 대한 오류 보고
include: “next-env.d.ts”, “/
.ts”, “/*.tsx”
• 컴파일에 포함될 파일 패턴 지정 (Next.js 환경 선언 파일과 모든 TypeScript 및 TSX 파일 포함)
exclude: “node_modules”, “dist”
• 컴파일에서 제외할 디렉토리 지정 (“node_modules”와 “dist” 디렉토리 제외)

next.config.js

next.config.js 는 버전별로 달라지므로
next 깃허브에서 버전에 맞게 사용 가능한 옵션을 찾아보아야 한다

reactStrictMode: 리액트 엄격 모드 활성화
powerdByHeader: 보안 취약점으로 취급되는 X-Powered-By 헤더 제거
eslint.ingnoreDuringBuilds: 빌드 시 ESLint 제외, 일반적으로는 빌드 시 포함됨

Lighthouse CI

구글에서 제공하는 액션으로 라이트하우스를 CI 기반으로 실행할 수 있도록 도와주는 도구

Dependabot

의존성에 문제가 있을 때 알려주고
가능하면 해결할 수 있는 풀 리퀘스트까지 열어준다
Dependabot 을 활용해 의존성의 보안 위협을 제거할 수도 있다
프로젝트에 사용되는 dependencies 중 보안 이슈가 발생한 패키지에 관한 정보를 알려주고 자동으로 버전을 올려주는 작업까지 수행 할 수 있다

https://github.com/dependabot
repository의 Setting의 Code security and analysis 탭에서 쉽게 Enable 가능하다

의존성과 버전

버전

유의적 버전(semantic versioning) 에 대해 먼저 알아야 한다

유의적 버전
버전은 주.부.수 로 구성돼 있으며
각각의 정의는 다음과 같다
1. 기존 버전과 호환되지 않게 API 가 바뀌면 주 버전을 올린다
2. 기존 버전과 호환되면서 새로운 기능을 추가할 때는 부 버전을 올린다
3. 기존 버전과 호환되면서 버그를 수정했다면 수 버전을 올린다

특정 버전으로 패키지를 배포하고 나면 그 버전의 내용은 절대 변경되지 않아야 한다
변경사항이 있을 때는 항상 버전을 올려야 한다

주버전이 0인 경우에는 초기 개발을 위한 단계로 아무 때나 마음껏 바꿀 수 있다

npm 버전 규칙

react@16.0.0: 정확시 16버전에 호환되는 경우
react@^16.0.0: 16.0.0 부터 17.0.0 미만의 모든 버전을 호환한다는 뜻
react@~16.0.0: 패치 버전에 대해서만 호환되는 버전을 의미한다 16.0.0 부터 16.1.0 미만의 모든 버전

유의적 버전은 개발자들의 약속일 뿐 그 것이 실제로 지켜졌는지는 알 수 없다

의존성

의존성이란 프로젝트를 운영하는 데 필요한 자신 외의 라이브러리를 정의해 둔 목록이다
package.json 은 주로 dependencies 와 devDependencies 로 구성돼 있다
devDependencies 는 개발 단계의 의존성
peerDependencies 는 주로 서비스보다는 라이브러리와 패키지에서 자주 쓰인다
직접적으로 해당 패키지를 import 하지 않지만 호환성으로 인해 필요한 경우 작성한다

최근에는 애플리케이션 실행에 필요한 패키지를 구분하는 것에 의문을 제기하는 경우도 있다

첫 번째 이유는 번들러
dependencies 에 있든 devDependencies 에 있든
node_modules 에 설치되는 것은 같다
여기서 실제 서비스에 배포할지를 결정하는 것은 번들러이다

두 번쨰 이유는 복잡해진 개발 파이프라인이다
과거에는 이렇게 구분하여 설치하는 전략이 주효했지만
과거 방식으로는 현재 빌드조차 할 수 없는 경우가 있다

npm 에 업로드할 패키지를 개발한다면 구분이 중요하다

의존성 취약점 해결

dependabot이 문제점을 발견하면 Security 탭의 Dependabot alerts에 issue를 추가해 주며 repository 메인 화면에서도 다음과 같이 알림창을 띄워주는 것을 확인 할 수 있다

생성된 issue를 확인하면 어떠한 종속성 패키지의 어떤버전에서 보안 이슈가 발생했으며 그 보안 이슈의 위험성 및 어떠한 종류인지 상세하게 설명해 준다
CWE에 등록된 보안 이슈들을 기준으로 명세되며 어떠한 버전으로 업그레이드 해야하는지 알려준다
버튼 하나로 보안 이슈가 발생하지 않는 패키지의 버전 업그레이드 PR을 생성해준다

버튼 하나만 누르면 모든 의존성 보안 이슈를 처리해주는것처럼 설명하였지만 생각보다 잘작동하지 않는 경우가 많이 발생했다

가장 흔한 케이스는 의존성의 의존성에서 문제가 발생했을 경우이다

https://velog.io/@siontama/dependabot%EC%9C%BC%EB%A1%9C-%EC%A2%85%EC%86%8D%EC%84%B1-%EC%B7%A8%EC%95%BD%EC%A0%90-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0

Dependabot 으로 수정하기 어려운 이슈라면 npm 의 overrides 를 활용하면 좋다
만들어진 배경이 보안 이슈 수정에 있어, 오래되거나 문제가 있는 의존성을 집어서 수정하는데 유용하다

도커라이즈

vercel 등으로 배포하는 방식은 쉽고 빠르다
트래픽이 적은 개인 프로젝트나 테스트용으로 적합하지만 사용자에게 서비스하기 위한 웹 애플리케이션을 적용하기에는 적절하지 않을 수 있다
커스터마이징에도 제약이 있다

때문에 직접 서버에 배포해야 하는데 이 때 컨테이너로 만들어 빠르게 배포하는 것이 일반적이다

리액트 앱 도커라이즈

애플리케이션을 도커 이미지로 만드는 과정을 도커라이즈라고 한다
애플리케이션을 신속하게 구축해 배포할 수 있는 상태로 준비하는 것이라고 할 수 있다

도커

도커는 개발자가 모던 애플리케이션을 구축, 공유, 실행하는 것을 도와줄 수 있도록 설계된 플랫폼으로 지루한 설정 과정을 대신해주므로 코드 작성에만 집중할 수 있다

도커는 빠른 배포를 위해 애플리케이션을 컨테이너라는 단위로 패키징하고
컨테이너 내부에서 애플리케이션을 실행할 수 있도록 도와준다
컨테이너를 바탕으로 독립된 환경에서 애플리케이션이 항상 일관되게 실행될 수 있도록 보장해준다

도커 용어

  • 이미지: 도커에서 컨테이너를 만드는 데 사용되는 템플릿을 의미한다
    이미지를 만들기 위해서는 Dockerfile 이 필요하고, 이 파일을 빌드하여 이미지가 만들어진다
  • 컨테이너: 도커의 이미지를 실행한 상태로 독립된 공간을 갖는다 이미지가 목표하는 운영체제, 파일 시스템, 네트워크 등이 할당되어 실행될 수 있다
  • Dockerfile: 어떤 이미지를 만들지를 정의한 파일로 토커라이즈 할 때 가장 먼저 이 파일을 만든다
  • 태그: 이미지를 식별할 수 있는 레이블 값으로 이름: 태그명 형태로 구성돼 있다 (ubuntu: latest)
  • 레포지토리: 이미지를 모아두는 저장소로 다양한 태그로 지정된 이미지가 모여있는 저장소
  • 레지스트리: 레포지토리에 접근할 수 있게 해주는 서비스를 의미한다 대표적인 레지스트리로는 도커 허브가 있다

cli 명령어

  • docker build: Dockerfile 을 기준으로 이미지 빌드를 한다
  • docker push: 이미지나 레포지토리를 도커 레지스트리에 업로드 한다
  • docker tag: 이미지에 태그를 생성한다
  • docker inspect: 이미지나 컨테이너 세부 정보를 출력한다
  • docker run: 이미지를 기반으로 새로운 컨테이너를 생성한다 웹 애플리케이션을 실행하는 이미지라면 컨테이너를 만들어 컨테이너 내부에서 작동하게 할 수 있다
  • docker ps: 현재 가동중인 컨테이너 목록을 확인
  • docker rm: 컨테이너 삭제 명령어

대부분의 cli 명령어는 도커 테스크톱의 GUI 인터페이스에서 제공하는 기능이다

Dockerfile 작성

프론트엔드 애플리케이션이 도커 이미지에서 해야 할 작업을 간단히 요약하면 5단계가 있다
1. 운영체제 설정
2. Node.js 설치
3. npm ci
4. npm run build
5. 실행

이 과정들을 Dockerfile 에 작성

FROM node:18.12.0-alpine3.16 as build

WORKDIR /app

COPY package.json ./package.json
COPY package-lock.json ./package-lock.json

RUN npm ci

COPY . ./

RUN npm run build

FROM node:18.12.0-alpine3.16 as build 는 이미지가 어떤 베이스 이미지에서 실행될지를 결정한다 도커는 다른 이미지 위에 이미지를 생성할 수 있다
node 가 설치되어 있는 이미지 위에서 실행할 것을 의미한다
alpine 은 리눅스를 의미하는데 일반적인 리눅스와 달리 씬 가벼운 리눅스로 컨테이너를 실행하기 위한 운영체제로 주로 사용된다
이 것들은 도커 허브에서 가져오는 것으로 as build 는 build 라는 단계의 스테이지에서만 쓰겠다는 의미

WORKDIR 설정 후
필요한 의존성을 COPY 후 설치,
npm run build 를 실행한다

여기까지가 리액트 빌드에 필요한 Dockerfile 이다

docker build . -t cra:test
명령을 입력하면 해당 위치에서 빌드를 수행, -t 옵션으로 cra:test 라는 이름과 태그를 부여하겠다는 뜻이다

build 후 보면 node.js 만 실행된 것을 볼 수 있다
node.js 가 실행된 이유는 node:18.12.0-alpine3.16 이미지를 보면 알 수 있다
node:18.12.0-alpine3.16 의 Dockerfile 마지막에 CMD ["node"] 가 있기 때문에 node.js 가 실행된 것이다

원하는 것이 node.js 가 실행되고 애플리케이션이 빌드되는 것이 아니기 때문에
빌드 후 실행을 위해서는 다음과 같이 설정을 추가해줘야 한다

  1. 빌드된 웹 애플리케이션을 NGINX 가 서비스 할 수 있도록 설정
  2. 이미지를 실행했을 때 해당 웹페이지에 접근할 수 있어야 한다
  3. 웹페이지 접근에 필요한 빌드 파일만 남겨두고 용량을 최소화 한다
FROM nginx:1.23.2-alpine as start

COPY ./nginx/nginx.conf /etc/nginx/nginx.conf
COPY --from=build /app/build /usr/share/nginx/html

EXPOSE 3000

ENTRYPOINT ["nginx", "-g", "daemon off;"]

FROM nginx:1.23.2-alpine as start 빌드된 정적 파일을 서비스하기 위해 최신 버전의 nginx 가 설치된 알파인 리눅스를 설치

COPY ./nginx/nginx.conf /etc/nginx/nginx.conf 빌드한 파이릉ㄹ nginx 가 서비스할 수 있도록 nginx 설정 파일 복사

COPY --from=build /app/build /usr/share/nginx/html 앞서 설정한 build 단계에서 app/build 만 가져와 현재 단계인 start 의 원하는 위치인 /usr/share/nginx/html 에 복사한다
필요한 리소스만 가져와 start 단계에서 사용할 수 있게 된다

EXPOSE 3000 으로 열어준 포트는 도커 이미지를 실행할 때 호스트 운영체제에서 오픈된다

ENTRYPOINT: 컨테이너 시작 시 어떤 명령을 실행할지 결정한다
Dockerfile 내에서 한 번만 실행할 수 있다
여기서는 nginx 의 데몬을 시작하도록 한다

이렇게 실행하면 이전에 비해 도커 이미지가 훨씬 작아지고
이미지 실행을 위해 앞서 정의한 3000번 포트를 열어야 한다

이미지 실행 후 http://localhost:3000/ 으로 접근하면 애플리케이션이 nginx 를 통해 서비스 되는 것을 확인할 수 있다

next.js 빌드하기

react 와 전체 과정은 비슷하다

profile
떠돌이 생활을 하는. 실업자, 부랑 생활을 하는

0개의 댓글