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