9. 모던 리액트 개발 도구로 개발 및 배포 환경 구축하기

영근·2024년 4월 8일
0

9.1 Next.js로 리액트 개발 환경 구축하기

tsconfig.json 작성하기


{
  "$schema": "https://json.schemastore.org/tsconfig.json",
  // schemaStore에서 제공해주는 정보. 해당 JSON 파일이 무엇을 의미하는지, 또 어떤 키와 어떤 값이 들어갈 수 있는지 알려주는 도구. VSCode같은 IDE에서 자동완성이 가능해진다.
  // .eslintrc, .prettierrc와 같이 JSON 방식으로 설정을 작성하는 라이브러리가 schemaStore에 내용을 제공한다면 편리하게 JSON 설정을 할 수 있다.
  "compilerOptions": {
    // ts를 js로 컴파일할 때 사용하는 옵션
    "target": "es5",
    // ts가 변환을 목표로 하는 언어의 버전(폴리필은 지원하지 않는다.)
    "lib": ["DOM", "DOM.Iterable", "ESNext"],
    // 신규 기능에 대한 API 정보를 확인할 수 있게 되어 에러가 발생하지 않는다. DOM -> window, document 등 브라우저 위주 API에 대한 명세 사용하기 위해
    "allowJs": true,
    // ts가 js파일도 컴파일할지 여부. js와 ts 파일이 혼재됐을 때 사용하는 옵션
    "skipLibCheck": true,
    // 라이브러리에서 제공하는 d.ts에 대한 검사 여부 -> 켜져있다면 d.ts에 에러가 있으면 에러 발생
    // 전체적인 d.ts를 모두 검사하기 때문에 컴파일 시간이 길어져 일반적으로는 off
    "strict": true,
    // ts 컴파일러의 strict mode 제어. 이 모드가 켜지면 아래 옵션도 true로 설정된다.
    // - alwaysStrict : 모든 js 파일에 use strict 추가(권장)
    // - strictNullChecks : 엄격한 null check 활성화. null과 undefined를 명확하게 구분해 사용한다.(권장)
    // - strictBindCallApply : 함수에 대해 사용할 수 있는 call, bind, apply에 대해 정확한 인수를 요구하는 옵션(권장)
    // - strictFunctionTypes : 함수의 타입에 대해 엄격함을 유지한다.(권장)
    // - strictPropertyInitialization : 클래스 내부의 프로퍼티에 값을 할당할 때 타입 틀리면 에러 발생
    // - noImplicitAny : 타입을 명시하지 않은 변수에 any를 자동으로 할당하지 않고 에러 발생
    // - noImplicitThis : This를 추론할 수 없는 상황에서 any를 자동으로 할당하지 않고 에러 발생
    // - useUnknownInCatchVariables : catch 구문 잡은 변수에 any가 아닌 unknown 할당 -> catch 한 것이 반드시 에러라는 보장이 없기 때문(권장)
    "forceConsistentCasingInFileNames": true,
    // 파일 이름의 대소문자 구분을 강제한다.
    "noEmit": true,
    // 컴파일 하지 않고 타입 체크만 한다. Next.js는 swc가 ts 파일을 컴파일하기 때문에 ts는 단순히 타입 검사만 하는 역할을 한다.
    "esModuleInterop": true,
    // CommonJS 방식으로 보낸 모듈을 ES 모듈 방식의 import로 가져올 수 있게 해준다.
    "module": "ESNext",
    // 모듈 시스템 설정 : commonjs - require, esnext - import
    "moduleResolution": "Node",
    // 모듈 해석하는 방식 설정 : node - node_modules 기준, classic - tsconfig.json이 있는 디렉터리 기준
    // node는 모듈이 commonjs일 때만 사용할 수 있다
    "resolveJsonModule": true,
    // JSON 파일을 import할 수 있게 해준다. 이 옵션을 켜면 allowJS 옵션도 자동으로 켜짐
    "isolatedModules": true,
    // 파일에 import나 export가 없다면 단순 스크립트 파일로 인식해 이러한 파일이 생성되지 않도록 막는다.
    "jsx": "preserve",
    // .tsx 파일 내부에 있는 JSX를 어떻게 컴파일할지 설정
    "incremental": true,
    // ts는 마지막 컴파일 정보를 .tsbuildinfo 파일 형태로 만들어 디스크에 저장. -> 다시 컴파일러가 호출되면 해당 정보를 활용해 가장 비용이 적게 드는 방식으로 컴파일을 수행해 더 빠르게 컴파일한다.
    "baseUrl": "src",
    // 모듈을 찾을 때 기준이 되는 디렉터리 지정
    "paths": {
      "#pages/*": ["pages/*"],
      "#hooks/*": ["hooks/*"],
      "#types/*": ["types/*"],
      "#components/*": ["components/*"],
      "#utils/*": ["utils/*"]
    },
    // 경로에 별칭(alias) 지정
    // #hooks/useToggle = src/hooks/useToggle
    // @는 충돌할 수 있으므로 사용을 피한다.
    "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
    // ts 컴파일 대상에서 포함시킬 파일 목록
    "exclude": ["node_modules"]
    // ts 컴파일 대상에서 제외시킬 파일 목록
  }
}
  • jsx : tsx 파일 내부에 있는 JSX를 어떻게 컴파일할지 설정한다.
    • 프로젝트 리액트 버전에 따라 react, react-jsx를 적절히 사용한다.
    • swc가 JSX도 변환해주기 때문에 preserve
export const Hello = () => <div>Hello</div>

// react : 기본값. React.createElement로 변환된다.

export const Hello = () => React.createElement('h1', null, 'Hello')

// react-jsx
// 리액트17에서 새롭게 등장. react/jsx-runtime 사용해 변환
// React.createElement 사용하지 않음 -> 상단에서 React import할 필요가 없다.

import {jsx as _jsx} from 'react/jsx-runtime',
export const Hello = () => _jsx('div', {children: 'Hello'})

// react-jsxdev : react-jsx에 디버깅 정보 추가

import {jsxDEV as _jsxDEV} from 'react/jsx-dev-runtime'
const _jsxFileName = 'file:///input.tsx'
export const Hello = () => {
    _jsxDEV(
        'div',
        {children: 'Hello'},
        void 0,
        false,
        {fileName: _jsxFileName, lineNumber: 1, columnNumber: 27},
        this,
    )
}

// preserve : 변환하지 않고 그대로 유지
export const Hello = () => <div>Hello</div>

// react-native : react native 에서 사용하는 방식. 변환하지 않는다.
export const Hello = () => <div>Hello</div>

next.config.js 작성하기


/** @type {import('next').NextConfig} */

const nextConfig = {
  reactStrictMode: true,
  // 리액트 strict mode 활성화
  poweredByHeader: false,
  // X-Powered-By 헤더를 제거한다(보안)
  eslint: {
    ignoreDuringBuilds: true,
    // 빌드 시에 ESLint를 무시한다.
  },
};

module.exports = nextConfig;

9.2 깃허브 100% 활용하기

깃허브 액션으로 CI 환경 구축하기

CI(Continuous Integration)

  • 여러 기여자가 기여한 코드를 지속적으로 빌드하고 테스트해 코드의 정합성을 확인하는 과정
  • 저장소에서 코드의 변화가 있을 때마다 전체 소프트웨어의 정합성을 확인하기 위한 작업을 자동으로 실행하는 것이 핵심
    • 자동으로 실행할 작업 : 테스트, 빌드, 정적 분석, 보안 취약점 분석
  • 과거에는 젠킨스를 자주 사용
    • 설치형 솔루션이기 때문에 별도 서버를 구축하고, 서버에 젠킨스를 설치하고, 사용중인 저장소와 연결해야 하는 복잡함
    • 따라서 대안으로 깃허브 액션이 떠오름
  • 깃허브 액션
    • 깃허브를 둘러싼 다양한 이벤트를 기반으로 깃허브 가상 환경에서 사용자가 원하는 작업을 수행

깃허브 액션의 기본 개념

  • 러너(runner) : 파일로 작성된 깃허브 액션이 실행되는 서버를 의미한다. 지정하지 않으면 공용 깃허브 서버 이용
  • 액션(action) : 러너에서 실행되는 하나의 작업 단위. yaml 파일로 작성된 내용이 하나의 액션
  • 이벤트(event) : 깃허브 액션의 실행을 일으키는 이벤트
    • pull_request : PR과 관련된 이벤트
    • issues : 이슈와 관련된 이벤트
    • push : 커밋이나 태그 푸시
    • schedule : 특정 시간에 실행되는 이벤트(cron에서 사용되는 시간)
      • cron : 유닉스 계열 운영체제에서 실행되는 시간 기반 잡 스케줄러
  • 잡(jobs) : 하나의 러너에서 실행되는 여러 스텝의 모음. 하나의 액션에서 여러 잡을 생성할 수 있다.
  • 스텝(steps) : 잡 내부에서 일어나는 하나하나의 작업. 병렬로 일어나지 않는다.

    즉, 스텝 -> 잡(병렬 실행) -> 액션 -> 러너에서 실행

깃허브 액션 작성하기

  • 저장소의 루트에 .github/workflows 폴더를 생성하고 내부에 작성한다.
  • .yml, .yaml 확장자로 지정해야 한다.
name : chapter7 build 
run-name : ${{ github. actor }} has been added new commit.

on:
    push:
        branches-ignore:
            - 'main'

jobs:
    build:
        runs-on: ubuntu-latest
        steps:
            - uses: actions/checkout@v3
            - uses: actions/setup-node@v3
              with:
                node-version: 16
            - name: 'install dependencies'
              working-directory: ./chapter7/my-app
              run: npm ci
            - name: 'build'
              working-directory: ./chapter7/my-app
              run: npm run build
  • name: 액션의 이름
  • run-name : 액션이 실행될 때 구별할 수 있는 타이틀명
  • on(필수값) : 언제 이 액션을 실행할지 정의
    • push가 발생하면 실행
    • main 브랜치에 push 발생했을 때는 실행하지 않음
  • jobs(필수값) : 해당 액션에서 수행할 잡
    • jobs.build: jobs에는 하나 이상의 작업이 있는데 그 중 하나가 build
    • jobs.build.runs-on: 어느 환경에서 해당 작업이 실행되는지. 깃허브에서 제공하는 서버 -> ubuntu-latest
    • jobs.build.steps: 해당 잡에서 순차적으로 수행할 작업
      • uses
        • actions/checkout@v3
          • 깃허브에서 제공하는 기본 액션
          • 별도 파라미터를 제공하지 않으면 해당 브랜치의 마지막 커밋을 기준으로 체크아웃한다.
        • actions/setup-node@v3
          • 깃허브에서 제공하는 기본 액션
          • 해당 러너에 Node.js를 설치한다
          • with.node-version.16 : Node.js 16 최신 버전을 설치
      • name : 해당 스텝의 명칭 지정
      • working-directory: 해당 디렉터리에서 수행
      • run: 수행할 작업을 명시

브랜치 보호 규칙

  • 해당 액션이 성공하지 전까지는 main 브랜치에 대한 머지를 막을 수 있다.

유용한 액션과 깃허브 앱 가져다 쓰기

깃허브가 제공하는 기본 액션

  • actions/checkout
    • 깃허브 저장소를 체크아웃하는 액션
    • 아무 옵션 없이 사용하면 최신 커밋을 불러오지만 ref를 지정해 특정 브랜치, 커밋을 체크아웃할 수 있다.
  • actions/setup-node : Node.js를 설치하는 액션
  • actions/github-script : Github API가 제공하는 기능을 사용할 수 있도록 도와주는 액션
  • actions/stale : 오래된 이슈나 PR을 자동으로 닫거나 더 이상 커뮤니케이션하지 못하도록 닫는다.
  • actions/dependency-review-action : package.json, package-lock.json 등 의존성이 변경됐을 때 실행되는 액션. 보안 or 라이센스에 문제가 있다면 알려준다.
  • github/codeql-action : 깃허브 코드분석 솔루션 codeql을 활용해 취약점을 분석해준다.

calibreapp/image-actions

  • 저장소에 포함돼 있는 이미지를 무손실로 압축해 다시 커밋

lirantal/is-website-vulnerable

  • 특정 웹사이트를 방문해 해당 웹사이트에 라이브러리 취약점이 존재하는지 확인하는 깃허브 액션
  • 실제 배포에 포함되지 않는 devDependencies나 트리쉐이킹으로 인해 사라진 코드는 진단하지 못한다.

Lighthouse CI

  • 라이트하우스를 CI 기반으로 실행할 수 있게 해주는 도구
  • 프로젝트의 URL을 방문해 라이트하우스 검사를 실행 -> 웹사이트 성능 지표 측정

깃허브 Dependabot으로 보안 취약점 해결하기

의존성에 문제가 있다면 이에 대해 문제를 알려주고 해결할 수 있는 PR도 열어준다.

package.json의 dependencies 이해하기

  • 시멘틱 버전 : 주.부.수

    • 주 : 기존 버전과 호환되지 않게 바뀜
    • 부 : 기존 버전과 호환되면서 새로운 기능 추가
    • 수 : 기존 버전과 호환되면서 버그 수정
  • 주의할 점

    • 특정 버전으로 패키지 배포 후에는 수정하지 않고 반드시 새로운 버전으로 배포한다.
    • 주 버전 0은 초기 개발에서 사용한다.
    • 수 버전은 반드시 그 이전 버전 API와 호환되는 버그 수정일 때 올린다.
  • npm 버전 규칙

    • 버전 앞에 기호 없음(react@16.0.0) : 정확히 해당 버전에 대해서만 의존하고 있다.
    • react@^16.0.0 : 16.0.0과 호환되는 버전
      • 0보다 높은 부 버전에 대해서는 호환된다(16.0.0 ~ 17.0.0 미만)
      • 주 버전이 0인 경우에는 수 버전까지만 수용
    • react@~16.0.0 : 패치 버전에 대해서만 호환된다(16.0.0 ~ 16.1.0 미만)

      이는 약속일 뿐이므로, 실제로 이에 맞춰 개발되었다고 확신할 수는 없다.

  • 의존성

    • dependencies : 해당 프로젝트를 실행하는 데 꼭 필요한 패키지
    • devDependencies : 개발 단계에서 필요한 패키지
    • peerDependencies : 호환성으로 인해 필요한 패키지

Dependabot으로 취약점 해결하기

실습 위주의 내용이므로 정리하지 않음


3. 리액트 애플리케이션 배포하기

Netlify

  • 웹 애플리케이션을 배포할 수 있게 해주는 클라우드 컴퓨팅 서비스
  • Next.js 애플리케이션 배포에는 추가 설정이 필요하다.
    • 루트에 netlify.toml 파일 추가
    [[plugins]]
    package = "netlify/plugin-nextjs"
    • publish directory를 /.next로 지정해준다.

Vercel

  • 클라우드 플랫폼 서비스
  • Next.js 배포에 별도 설정이 필요하지 않다.

DigitalOcean

  • AWS와 비슷하게 다양한 클라우드 컴퓨팅 시스템을 제공한다.

4. 리액트 애플리케이션 도커라이즈하기

리액트 앱을 도커라이즈하는 방법

도커란?

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

  • 도커 용어

    • 이미지 : 컨테이너를 만드는데 사용되는 템플릿. Dockerfile이 필요하다.
    • 컨테이너 : 이미지를 실행한 상태. 독립된 공간으로, 이미지가 목표하는 운영체제, 파일 시스템, 각종 자원 및 네트워크가 할당되어 실행될 수 있다.
    • Dockerfile : 어떤 이미지를 만들지 정의하는 파일. 이 파일을 빌드하면 이미지를 만들 수 있다.
    • 태그 : 이미지를 식별할 수 있는 레이블 값. 이름:태그명 형태(ubuntu:latest)
    • 리포지터리 : 이미지를 모아두는 저장소
    • 레지스트리 : 리포지터리에 접근할 수 있게 해주는 서비스
  • 도커 cli 명령어

    • docker build
      • Dockerfile을 기준으로 이미지 빌드
      •   docker build -t foo:bar ./
        • 현재 ./에 있는 Dockerfile을 기준으로 빌드하고, 해당 이미지명에 foo:bar 라는 태그를 붙인다.
    • docker push
      • 이미지나 리포지터리를 도커 레지스트리에 업로드
    • docker tag
      • 이미지에 태그를 생성
      • 기존 이미지에 새로운 태그를 생성. 기존 태그를 수정하는 것이 아님
    • docker inspect
      • 이미지나 컨테이너의 세부 정보 출력
      docker inspect {이미지명 | 컨테이너명}
    • docker run
      • 이미지를 기반으로 새로운 컨테이너를 생성
    • docker ps
      • 현재 가동중인 컨테이너 목록을 확인
    • docker rm
      • 컨테이너 삭제
      docker rm {이미지명}

Dockerfile 작성하기

FROM node:18.12.0-alpine3.16 as build
# FROM : 이 이미지가 어떤 베이스 이미지 위에서 실행될 지 결정
# node:18.12.0 : Node.js 18.12.0 버전이 설치되어 있는 이미지
# alpine3.16 : alpine3.16 버전의 운영 체제 위에서 실행되는 이미지
# alpine : 알파인 리눅스. 도커 허브에서 가져온다.
# as build : 이 베이스 이미지는 빌드 단계에서만 사용한다.

WORKDIR /app
# WORKDIR : 작업을 수행하고자 하는 기본 디렉터리

COPY package.json ./package.json
COPY package-lock.json ./package-lock.json
# COPY : 파일을 복사하는 명령어
# 복사하는 위치는 앞서 설정한 ./app

RUN npm ci
# RUN : 컨테이너에서 명령어 실행
# npm ci : 의존성 설치

COPY . ./
# 모든 리소스 복사

RUN npm run build
# 애플리케이션 빌드

애플리케이션 실행을 위해 코드 추가

FROM nginx:1.23.2-alpine as start
# 최신 버전의 NGINX가 설치된 알파인 리눅스 설치

COPY ./nginx/nginx.conf /etc/nginx/nginx.conf
# 빌드한 파일을 NGINX가 서비스할 수 있도록 설정 파일 복사
COPY --from=build /app/build /usr/share/nginx/html
# --from=build : as build로 선언한 단계
# build단게에서 /app/build를 가져와 /usr/share/nginx/html 에 복사한다.

EXPOSE 3000
# 3000번 포트를 연다
# 도커 이미지를 실행할 때 호스트 운영체제에서 오픈된다

ENTRYPOINT ["nginx", "-g", "daemon off;"]
# 컨테이너가 실행됐을 때 어떤 명령을 실행할지 정한다
# Dockerfile 내부에서 단 한 번 실행된다
# NGINX의 데몬을 시작하도록 했다

도커로 만든 이미지 배포하기

실습 위주라 정리하지 않음

profile
Undefined JS developer

0개의 댓글