pnpm 과 vite 로 react + ts 커스텀 보일러 플레이트 프로젝트 생성하기

BinaryWoo_dev·2024년 8월 25일
0

프로젝트 시작

목록 보기
3/3

1. pnpm 설치

먼저 로컬에 pnpm 패키지 매니저가 설치되지 않았을 경우, npm 을 통해 설치해준다.

npm install -g pnpm

2. vite 를 사용하여 react app 생성

아래의 명령어를 실행하여 vite 를 통해 typescript + react 프로젝트를 생성한다.

pnpm create vite <프로젝트명> -- --template react-ts

생성 단계에서 위와 같은 선택창이 나올 경우, Typescript + SWC 를 추천한다.

Typescript + SWC 를 추천하는 이유? SWC란?
참조 자료: https://shape-coding.tistory.com/entry/SWC-SWC%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80

dev mode 실행

정상적으로 생성되었다면 위와 같은 문구가 출력될 것이다.

그러면, pnpm dev 명령어를 실행해보자.

위와 같이 정상적으로 실행되는 것을 확인할 수 있다.

3. 프로젝트 환경 설정

tsconfig 설정

{
  "compilerOptions": {
    "target": "ES2020", // ECMAScript 타겟 버전을 최신으로 설정
    "lib": ["ES2020", "DOM", "DOM.Iterable"], // DOM과 ES 표준 라이브러리를 포함
    "useDefineForClassFields": true, // 클래스 필드의 동작을 최신 사양에 맞게 설정
    "module": "ESNext", // 최신 ESMAScript 모듈 시스템 사용
    "skipLibCheck": true, // 라이브러리의 타입 검사를 생략하여 빌드 속도 향상
    "baseUrl": "./", // 기본 경로 설정

    /* Bundler mode */
    "moduleResolution": "Node", // Node.js 모듈 해석 방식 사용
    "resolveJsonModule": true, // JSON 파일을 import 할 수 있도록 설정
    "isolatedModules": true, // Vite 와 호환되도록 각 파일을 독립된 모듈로 취급
    "esModuleInterop": true, // CommonJS 와 호환성 유지
    "allowImportingTsExtensions": true, // ES 모듈을 CommonJS 방식으로 import 가능
    "moduleDetection": "force",
    "noEmit": true, // Vite 가 빌드를 처리하므로 Typescript 에서 파일을 생성하지 않음
    "jsx": "react-jsx", // React  17+ 를 사용하기 위한 설정

    /* Linting */
    "strict": true, // 엄격한 타입 검사 활성화
    "forceConsistentCasingInFileNames": true, // 파일명 대소문자 일관성 검사
    "noUnusedLocals": true, // 사용되지 않은 변수 검사
    "noUnusedParameters": true, // 사용되지 않은 파라미터들 검사
    "noFallthroughCasesInSwitch": true,
    "types": ["vite/client", "jest", "@testing-library/jest-dom"] // Vite, Jest, RTL 타입 추가
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

4. Jest 설치 및 환경 설정

설치

$ pnpm install --save-dev jest @types/jest jest-environment-jsdom @testing-library/jest-dom  @testing-library/react @testing/library/jest-dom @testing-library/user-event ts-jest

jest.setup.ts

// jest.setup.ts
import '@testing-library/jest-dom';
import '@jest/globals';

jest.config.ts

// jest.config.ts
import type { Config } from 'jest';

const config: Config = {
  collectCoverage: true, // 취합 커버리지 가능
  coverageDirectory: 'coverage',
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  testMatch: [
    '**/__tests__/**/*.?([mc])[jt]s?(x)',
    '**/?(*.)+(spec|test).?([mc])[jt]s?(x)',
  ],
  collectCoverageFrom: [
    'src/**/*.{ts,tsx}',
    '!**/node_modules/**',
    '!**/vendor/**',
  ],
  coveragePathIgnorePatterns: ['/node_modules', '.*/config.*'],
  coverageThreshold: {
    global: {
      branches: 50, // 조건문 ( if, switch 등)의 각 분기가 테스트된 비율
      functions: 50, // 전체 함수 중 테스트된 함수들의 비율
      lines: 50, // 전체 코드 라인 중 테스트된 라인의 비율
      statements: 50, // 모든 종류의 문장 테스트 비율. (조건문, 일반 코드라인 포함)
    },
    'src/shared': {
      branches: 60,
      functions: 60,
      lines: 60,
      statements: -3, // 적용되지 않은 명령문이 3개 이상인 경우, jest 실패
    },
    'src/pages': {
      branches: 60,
      functions: 60,
      lines: 60,
      statements: -1, // 적용되지 않은 명령문이 3개 이상인 경우, jest 실패
    },
    'src/app': {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 90, // 적용되지 않은 명령문이 3개 이상인 경우, jest 실패
    },
    'src/features': {
      branches: 60,
      functions: 70,
      lines: 70,
      statements: -3, // 적용되지 않은 명령문이 3개 이상인 경우, jest 실패
    },
    'src/entities': {
      branches: 60,
      functions: 70,
      lines: 70,
      statements: -3, // 적용되지 않은 명령문이 3개 이상인 경우, jest 실패
    },
    'src/widgets': {
      branches: 50,
      functions: 80,
      lines: 68,
      statements: -3, // 적용되지 않은 명령문이 3개 이상인 경우, jest 실패
    },
  },
  transform: {
    // Use babel-jest to transpile tests with the next/babel preset
    '^.+.(js|jsx)$': 'babel-jest',
    '^.+.(ts|tsx)$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.app.json' }],
  },
  transformIgnorePatterns: ['^.+\\.module\\.(css|sass|scss)$', '/dist'],
  testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/dist/'],

  moduleNameMapper: {
    '\\.(jpg|png|gif)$': '<rootDir>/src', // 이미지 파일 경로
    '\\.svg$': '<rootDir>/svgMockup.ts',
    '\\.(css|less|scss)$': 'identity-obj-proxy',
    'lodash-es': 'lodash', // lodash es6 문법 오류 해결

    // jest 테스트 코드에서 ts alias paths 사용 하기 위한 설정
    '@src/(.*)$': '<rootDir>/src/$1',
    '@app/(.*)$': '<rootDir>/src/app/$1',
    '@entities/(.*)$': '<rootDir>/src/entities/$1',
    '@features/(.*)$': '<rootDir>/src/features/$1',
    '@mocks/(.*)$': '<rootDir>/src/mocks/$1',
    '@pages/(.*)$': '<rootDir>/src/pages/$1',
    '@shared/(.*)$': '<rootDir>/src/shared/$1',
    '@store/(.*)$': '<rootDir>/src/store/$1',
    '@styles/(.*)$': '<rootDir>/src/styles/$1',
    '@@types/(.*)$': '<rootDir>/src/types/$1',
    '@widgets/(.*)$': '<rootDir>/src/widgets/$1',
  },
  setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
  moduleDirectories: ['node_modules', 'src'],
  verbose: true,
};

export default config;

5. 환경 실행 테스트용 코드 작성

// src/App.test.tsx

import { render, screen, fireEvent } from '@testing-library/react';
import { describe, test } from '@jest/globals';
import App from './App';

describe('App', () => {
  test('renders Vite and React logos', () => {
    render(<App />);
    const viteLogo = screen.getByAltText('Vite logo');
    const reactLogo = screen.getByAltText('React logo');
    expect(viteLogo).toBeInTheDocument();
    expect(reactLogo).toBeInTheDocument();
  });

  test('renders title', () => {
    render(<App />);
    const titleElement = screen.getByText('Vite + React');
    expect(titleElement).toBeInTheDocument();
  });

  test('increments count when button is clicked', () => {
    render(<App />);
    const button = screen.getByText('count is 0');
    fireEvent.click(button);
    expect(screen.getByText('count is 1')).toBeInTheDocument();
  });

  test('renders instruction text', () => {
    render(<App />);
    const instructionText = screen.getByText(/Edit/i);
    expect(instructionText).toBeInTheDocument();
  });
});

5. 결과

  • 테스트 코드 실행은 정상적으로 작동하는 것을 확인하였습니다.
  • Coverage data ... was not found 는 아직 해당 경로에 대한 폴더를 생성하지 않아 발생하는 것입니다.
  • 프로젝트의 기본 구조는 기능 분할 설계 (FSD; Feature-Sliced Design)를 기반으로 생성하였습니다. ( 참조 링크 )
profile
매일 0.1%씩 성장하는 Junior Web Front-end Developer 💻🔥

0개의 댓글