나만의 react boilerplate 만들기 - (1) 환경 설정

2022. 12. 2. 17:19·프론트엔드/개발 환경

React Boilerplate

FE 개발환경에 대해서 공부해보고, CRA에서 불필요한 부분은 덜어내고, 필요한 부분(ex: webpack-bundle-analyzer, MSW)을 추가하여 리액트 개발환경을 설정해봤다.

https://github.com/fe-jhw/react-boilerplate

특징

  • zero-install: 클론 후 패키지 설치 없이 바로 사용할 수 있다.
  • mocking: MSW가 포함되어 api들을 mocking해 사용할 수 있다.
  • behavior-driven-test: react-testing-library를 활용한 유저 행동 기반 UI 테스팅 환경이 구성되어 있다.
  • bundle-analyze: webpack-bundle-analyzer를 활용해 빌드시 번들 분석 정보를 확인할 수 있다.
  • lint-automation: 깃 훅을 사용하여 커밋 이전 자동으로 lint를 실행해 코드 품질을 향상시킨다.

프로젝트 생성

Yarn berry로 프로젝트 생성

yarn init -2

폴더 구조

  • src: 소스

    • __tests__: 테스트 파일

    • mocks: 모킹 api 관련 파일

  • public: 정적파일

  • dist: 빌드된 파일

  • config: 개발환경 설정

    • babel
    • env
    • jest
    • webpack

바벨 설정

// babel.config.js
const presets = [
  [
    "@babel/preset-env",
    {
      targets: "> 0.25%, not dead",
      useBuiltIns: "entry",
      corejs: 3,
    },
  ],
  [
    "@babel/preset-react",
    {
      runtime: "automatic",
    },
  ],
]

const plugins = []
if (process.env["NODE_ENV"] === "development") {
  plugins.push(require.resolve("react-refresh/babel"))
}

module.exports = { presets, plugins }

환경변수를 활용하여 모드에 따라 plugin, preset등을 다르게 주기 위해 config.js형태를 사용했다.

  • presets

    • preset-env: >ES6 -> ES5

      • 타겟 브라우저: 점유 0.25% 이상, not dead

      • useBuiltIns: "entry"로 해서 전체 core-js를 import하는 대신 필요한 모듈만 import하게 했다.

      • corejs 버전은 3: 계속 업데이트 되는 버전

    • prset-react: JSX 변환

      • runtime: "automatic": development모드일 때 자동으로 plugin-transform-react-jsx-self, plugin-transform-react-jsx-source 추가
  • plugins

    • 개발모드일 시 react-refresh추가, 개발모드에서 Hot Reloading을 하게 한다.

웹팩 설정

공통, 개발, 배포 3가지 설정파일로 나누고 webpack merge를 활용했다.

공통: 개발, 배포에서 겹치는 설정들

// webpack.config.common.js
const path = require("path")
const webpack = require("webpack")
const HtmlWebpackPlugin = require("html-webpack-plugin")

const childProcess = require("child_process")

module.exports = {
  entry: ["./src/index.js"],
  module: {
    rules: [
      {
        test: /\.(js|mjs|jsx)$/,
        loader: "babel-loader",
        options: {
          configFile: path.resolve(__dirname, "../babel/babel.config.js"),
        },
      },
      {
        test: /\.(ico|png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        loader: "file-loader",
        options: {
          name: "[name].[ext]",
        },
      },
      {
        test: /\.png$/,
        loader: "url-loader",
        options: {
          publicPath: "./dist/",
          name: "[name].[ext]?[hash]",
          limit: 5000,
        },
      },
    ],
  },

  infrastructureLogging: { level: "error" },

  stats: "minimal",

  resolve: {
    extensions: [".js", ".jsx"],
    alias: {
      "@src": path.resolve(__dirname, "src/"),
    },
  },

  plugins: [
    // new webpack.HotModuleReplacementPlugin(),
    new HtmlWebpackPlugin({
      filename: "index.html",
      template: "public/index.html",
    }),
    new webpack.BannerPlugin({
      banner: `
        Build Date :: ${new Date().toLocaleString()}
        Commit Version :: ${childProcess.execSync("git rev-parse --short HEAD")}
        Auth.name :: ${childProcess.execSync("git config user.name")}
        Auth.email :: ${childProcess.execSync("git config user.email")}
      `,
    }),
  ],

  devtool: "eval-cheap-source-map",
}
  • 로더: babel-loader, style-loader, css-loader, file-loader, url-loader
  • 플러그인: HotModuleReplacementPlugin, HtmlWebpackPlugin, BannerPlugin, dot-env
  • devtool: cheap-eval-source-map

개발: devserver 설정

// webpack.config.dev.js
const path = require("path")
const { merge } = require("webpack-merge")
const common = require("./webpack.config.common.js")

const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin")
const Dotenv = require("dotenv-webpack")

module.exports = merge(common, {
  mode: "development",

  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
    ],
  },

  devServer: {
    historyApiFallback: true,
    port: 3000,
    hot: true,
    // noInfo: true,
  },

  plugins: [
    new Dotenv({
      path: path.resolve(__dirname, "../../.dev.env"),
    }),
    new ReactRefreshWebpackPlugin(),
  ],
})

리액트 hot reloading을 위해 ReactRefreshWebpackPlugin을 추가했다. Dotenv를 활용해 .dev.env의 환경변수들을 사용할 수 있게 했다.

배포: production 모드로 파일 번들링

// webpack.config.prod.js
const path = require("path")
const os = require("os")
const { merge } = require("webpack-merge")
const common = require("./webpack.config.common.js")

const Dotenv = require("dotenv-webpack")
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
const BundleAnalyzerPlugin =
  require("webpack-bundle-analyzer").BundleAnalyzerPlugin
const { CleanWebpackPlugin } = require("clean-webpack-plugin")

module.exports = merge(common, {
  mode: "production",

  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
    ],
  },

  output: {
    filename: "static/js/[name].[contenthash:8].js",
    path: path.resolve(__dirname, "../../dist"),
    publicPath: "./",
    chunkFilename: "static/js/[name].[contenthash:8].chunk.js",
  },

  plugins: [
    new Dotenv({
      path: path.resolve(__dirname, "../../.prod.env"),
    }),
    new MiniCssExtractPlugin({ filename: "[name].css" }),
    new BundleAnalyzerPlugin({
      analyzerMode: "static",
      reportFilename: "bundle-report.html",
      openAnalyzer: false,
      generateStatsFile: true,
      statsFilename: "bundle-stats.json",
    }),
    new CleanWebpackPlugin(),
  ],

  optimization: {
    minimize: true,
    minimizer: [
      new CssMinimizerPlugin({
        parallel: os.cpus().length - 1,
      }),
    ],
  },
})
  • bundle-analyzer를 추가해 번들 결과를 확인할 수 있게 했다.

  • dev모드와 다르게 style-loader를 사용하지 않고 MiniCssExtractPlugin와 CssMinimizerPlugin를 활용해 CSS를 각각 다운로드해 빠르게 적용할 수 있게 했다.

Lint, 프리티어 설정

pre-commit hook(커밋 이전 자동실행), lint-staged(변경된 파일에 대해서만 Lint)를 활용해서 커밋 이전 Lint후 실패시 커밋을 취소함으로써 코드 스타일을 맞추고 품질을 향상할 수 있게 했다.

// .eslintrc.js
module.exports = {
  root: true,
  env: {
    browser: true,
    commonjs: true,
    es6: true,
    node: true,
  },
  extends: [
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:react-hooks/recommended",
    "plugin:jsx-a11y/strict", //JSX 엘리먼트의 접근성 규칙들을 체크
    "plugin:import/recommended", //import,export 문법, 경로 린트
    "plugin:testing-library/react", //testing-library 사용한 테스트 코드 린트
    "prettier",
  ],
  plugins: ["react", "react-hooks", "jsx-a11y", "import"],
  settings: {
    react: {
      version: "detect",
    },
  },
  rules: {
    "no-console": "error",
    "import/prefer-default-export": "off",
    "react/react-in-jsx-scope": "off",
    semi: "off",
    "react/jsx-filename-extension": [1, { extensions: [".js", ".jsx"] }],
  },
  overrides: [
    {
      files: ["**/__tests__/**/*", "**/*.{spec,test}.*"],
      env: {
        "jest/globals": true,
      },
      plugins: ["jest", "jest-dom", "testing-library"],
      extends: [
        "plugin:jest/recommended",
        "plugin:jest-dom/recommended",
        "plugin:testing-library/react",
      ],
    },
  ],
  parserOptions: {
    ecmaVersion: 2020,
  },
}

접근성 규칙, import ,export 규칙, 테스트 코드 린트 등이 기본적으로 포함되어 있다.

prettier, eslint 충돌을 방지하기 위해 eslint:recommended를 활용했다.

JEST 설정

const path = require("path")

const config = {
  transform: {
    "^.+\\.(js|jsx)$": [
      "babel-jest",
      { configFile: path.resolve(__dirname, "../babel/babel.config.js") },
    ],
  },
  rootDir: "../..",
  collectCoverageFrom: ["src/**/*.{js,jsx}"],
  testMatch: [
    "<rootDir>/src/**/__tests__/**/*.{js,jsx}",
    "<rootDir>/src/**/*.{spec,test}.{js,jsx}",
  ],
  testEnvironment: "jest-environment-jsdom",
  setupFilesAfterEnv: ["<rootDir>/config/jest/setupTests.js"],
  // modulePaths: [".yarn"],
}

module.exports = config

테스트환경을 default인 node가 아닌 jest-environment-jsdom으로 설정

jsx변환을 위해 babel-jest사용, src폴더 전체 코드로부터 커버리지 수집

테스트 실행전 setupTests.js를 실행하여 모든 테스트코드에서 공통적으로 쓰는 부분들을 한 파일로 묶어줬다.

testing-library의 /react, /jest-dom, /user-event를 설치해둬서 행동기반 테스트 코드를 별도의 설정, 설치 없이 바로 작성할 수 있다.

//setupTests.js
import "@testing-library/react"
import "@testing-library/jest-dom"

MSW 설정

MSW를 기본적으로 포함시켰다

FE 개발시 API가 다 개발되지 않은 상태에서 개발하는 경우가 많았어서, MSW 사용이 생산성을 많이 올려준다고 생각한다.

환경변수 설정

Dotenv를 활용해서 개발, 배포환경시 다른 환경변수를 사용하도록 했다. config/env에 .dev.env, .prod.env로 분리하고, webpack 실행시 각환경에 맞는 환경변수를 불러오도록 했다.

Scripts 명령어들

//package.json
"scripts": {
    "dev": "cross-env NODE_ENV=development webpack server --config ./config/webpack/webpack.config.dev.js --open",
    "build": "cross-env NODE_ENV=production webpack --config ./config/webpack/webpack.config.prod.js",
    "prepare": "husky install",
    "lint-staged": "lint-staged",
    "test": "jest --config ./config/jest/jest.config.js"
  },
  • yarn dev: 개발자 모드로 실행, 브라우저 열기
  • yarn build: 배포 모드로 번들링, 배포 파일은 dist에 저장된다. 번들 분석결과도 같이 저장
  • prepare: husky 설치, git hook을 쉽게 사용하게 해준다
  • lint-staged: 변경된파일만 lint, husky와 조합하여 린트 자동화를 한다.
  • test: 모든 테스트파일에 대해 테스트를 실행

아쉬운 점

  • husky사용시 lint-staged 실행로그가 반복되는 부분이 매우 많게 찍힌다. husky 깃헙 리포지토리에서 관련 이슈를 봤는데, 아직 해결이 안되있어서 추후에 다시 봐야겠다. https://github.com/typicode/husky/issues/968 v6부터 발생했는데 v8인 지금도 해결이 안되어있다. 다른 git hook관련 도구를 찾아봐야겠다.

앞으로 할 일

  • npm에 배포해서 npx fe-jhw-react-boilerplate 로 사용할 수 있게 하기

  • TS 관련 설정

  • TDD 관련 설정

  • 빌드시 최적화 보완하기

저작자표시 (새창열림)

'프론트엔드 > 개발 환경' 카테고리의 다른 글

CRA 분석하기  (1) 2022.12.20
나만의 react boilerplate 만들기 - (2) npx, 배포  (0) 2022.12.08
린트, Prettier 기초 개념 정리  (0) 2022.10.03
Babel 기초 개념 정리  (0) 2022.10.03
Webpack 기초 개념 정리  (0) 2022.10.03
'프론트엔드/개발 환경' 카테고리의 다른 글
  • CRA 분석하기
  • 나만의 react boilerplate 만들기 - (2) npx, 배포
  • 린트, Prettier 기초 개념 정리
  • Babel 기초 개념 정리
정현우12
정현우12
  • 정현우12
    정현우의 개발 블로그
    정현우12
  • 전체
    오늘
    어제
    • 분류 전체보기 (79)
      • 프론트엔드 (56)
        • JavaScript, TypeScript (12)
        • 스타일링 (1)
        • React (13)
        • Next.js (4)
        • 개발 환경 (9)
        • 테스트 (3)
        • 성능 최적화 (11)
        • 함수형 프로그래밍 (2)
        • 구조 (1)
      • 프로젝트 회고 (23)
        • 이미지편집기 개발 (7)
        • 엑셀 다운로드, 업로드 공통 모듈 개발 (4)
        • 사용자 매뉴얼 사이트 개발 (3)
        • 통계자동화 솔루션 개발 (1)
        • 엑셀 편집기 개발 (5)
        • API 플랫폼 (1)
        • 콜센터 솔루션 OB 캠페인 (1)
        • AI 스튜디오 (1)
      • 백엔드 (0)
  • 블로그 메뉴

    • 홈
    • 포트폴리오
    • 태그
  • 인기 글

  • 태그

    회고
    frontend
    엑셀
    라이브러리 선정
    React
    로딩 성능 최적화
    React-boilerplate
    엑셀 에디터
    memoization
    이미지 편집기
    webpack
    렌더링 성능 최적화
    커스텀 훅
    Github Actions
    useReducer
    사용자 매뉴얼 사이트
    웹 성능 최적화
    TypeScript
    Next.js
    JavaScript
  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
정현우12
나만의 react boilerplate 만들기 - (1) 환경 설정
상단으로

티스토리툴바